feat: boulders
This commit is contained in:
parent
9aa0ec6a3f
commit
680e08aadc
7 changed files with 169 additions and 11 deletions
|
|
@ -6,6 +6,7 @@ import grassUrl from "../assets/grass.png"
|
|||
import leafUrl from "../assets/leaf.png"
|
||||
import needleUrl from "../assets/needle.png"
|
||||
import npcUrl from "../assets/npc.png"
|
||||
import rockUrl from "../assets/rock.png"
|
||||
import wallUrl from "../assets/wall.png"
|
||||
|
||||
export type Textures = {
|
||||
|
|
@ -14,6 +15,7 @@ export type Textures = {
|
|||
bark: Texture
|
||||
leaf: Texture
|
||||
needle: Texture
|
||||
rock: Texture
|
||||
wall: Texture
|
||||
crate: Texture
|
||||
npc: Texture
|
||||
|
|
@ -21,17 +23,18 @@ export type Textures = {
|
|||
|
||||
/** Load every game texture up front. Call once before starting the loop. */
|
||||
export async function loadTextures(): Promise<Textures> {
|
||||
const [floor, grass, bark, leaf, needle, wall, crate, npc] = await Promise.all([
|
||||
const [floor, grass, bark, leaf, needle, rock, wall, crate, npc] = await Promise.all([
|
||||
loadTexture(floorUrl),
|
||||
loadTexture(grassUrl),
|
||||
loadTexture(barkUrl),
|
||||
loadTexture(leafUrl),
|
||||
loadTexture(needleUrl),
|
||||
loadTexture(rockUrl),
|
||||
loadTexture(wallUrl),
|
||||
loadTexture(crateUrl),
|
||||
loadTexture(npcUrl),
|
||||
])
|
||||
return { floor, grass, bark, leaf, needle, wall, crate, npc }
|
||||
return { floor, grass, bark, leaf, needle, rock, wall, crate, npc }
|
||||
}
|
||||
|
||||
function loadTexture(url: string): Promise<Texture> {
|
||||
|
|
|
|||
41
app/level.ts
41
app/level.ts
|
|
@ -1,6 +1,7 @@
|
|||
import { Color } from "../engine/render/Color"
|
||||
import type { CloudLayer, SkyConfig } from "../engine/render/Sky"
|
||||
import type { Mesh } from "../engine/scene/Mesh"
|
||||
import { Boulder } from "../engine/scene/Boulder"
|
||||
import { Terrain } from "../engine/scene/Terrain"
|
||||
import { Tree } from "../engine/scene/Tree"
|
||||
|
||||
|
|
@ -31,6 +32,8 @@ export type Level = {
|
|||
* each takes its own texture in one draw call. */
|
||||
oakFoliage: Mesh
|
||||
spruceFoliage: Mesh
|
||||
/** Scattered boulders (rock texture). */
|
||||
boulders: Mesh
|
||||
colliders: Aabb[]
|
||||
npcPosition: { x: number; y: number; z: number }
|
||||
terrain: Terrain
|
||||
|
|
@ -71,6 +74,12 @@ const TREE_COUNT = 500
|
|||
const TREE_SEED = 0x5EED
|
||||
const TREE_REACH = 0.6
|
||||
|
||||
/** Boulders: how many to scatter, their seed, and how far out they reach
|
||||
* (fraction of the world). Sizes range small pebble .. big boulder. */
|
||||
const BOULDER_COUNT = 70
|
||||
const BOULDER_SEED = 0xB0142
|
||||
const BOULDER_REACH = 0.7
|
||||
|
||||
/** Outdoor ground mesh resolution. A fixed grid over the whole world, so cell
|
||||
* size (and cost) is set here, not by the world's size: bigger `outer` gives
|
||||
* chunkier terrain, not more triangles. `GROUND_UV` sets texture tiles/unit. */
|
||||
|
|
@ -156,7 +165,11 @@ export function buildLevel(): Level {
|
|||
const spruceFoliage = mesh()
|
||||
scatterTrees(trunks, oakFoliage, spruceFoliage, colliders)
|
||||
|
||||
return { floor, walls, crate, ground, trunks, oakFoliage, spruceFoliage, colliders, npcPosition, terrain: TERRAIN, sky }
|
||||
// Scatter boulders across the terrain; big ones get colliders.
|
||||
const boulders = mesh()
|
||||
scatterBoulders(boulders, colliders)
|
||||
|
||||
return { floor, walls, crate, ground, trunks, oakFoliage, spruceFoliage, boulders, colliders, npcPosition, terrain: TERRAIN, sky }
|
||||
}
|
||||
|
||||
/** Place `TREE_COUNT` trees around the room on walkable grass: each sits on the
|
||||
|
|
@ -188,6 +201,32 @@ function scatterTrees(trunks: Mesh, oakFoliage: Mesh, spruceFoliage: Mesh, colli
|
|||
}
|
||||
}
|
||||
|
||||
/** Scatter `BOULDER_COUNT` boulders across the terrain, sizes biased toward
|
||||
* small. Each sits on the ground; big ones drop a blocking collider so you
|
||||
* can't walk through them (little rocks stay passable). */
|
||||
function scatterBoulders(boulders: Mesh, colliders: Aabb[]): void {
|
||||
const rand = mulberry(BOULDER_SEED)
|
||||
const maxDist = TERRAIN.outer * BOULDER_REACH
|
||||
let placed = 0
|
||||
for (let guard = 0; placed < BOULDER_COUNT && guard < BOULDER_COUNT * 20; guard++) {
|
||||
const angle = rand() * Math.PI * 2
|
||||
const dist = ARENA + 4 + rand() * (maxDist - ARENA - 4)
|
||||
const x = Math.cos(angle) * dist
|
||||
const z = Math.sin(angle) * dist
|
||||
if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 2) {
|
||||
continue
|
||||
}
|
||||
// Square the roll so most rocks are small, a few are big.
|
||||
const radius = 0.35 + rand() * rand() * 2.2
|
||||
const position = { x, y: Terrain.height(TERRAIN, x, z), z }
|
||||
Boulder.build({ position, radius, seed: (rand() * 0xFFFFFFFF) | 0 }, boulders)
|
||||
if (radius > 0.7) {
|
||||
colliders.push({ minX: x - radius, maxX: x + radius, minZ: z - radius, maxZ: z + radius, top: position.y + radius * 0.7, standable: false })
|
||||
}
|
||||
placed++
|
||||
}
|
||||
}
|
||||
|
||||
/** Deterministic 0..1 generator (mulberry32) for tree placement. */
|
||||
function mulberry(seed: number): () => number {
|
||||
let a = seed >>> 0
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ async function main(): Promise<void> {
|
|||
Rasterizer.draw(fb, level.floor, textures.floor, viewProj, config)
|
||||
Rasterizer.draw(fb, level.walls, textures.wall, viewProj, config)
|
||||
Rasterizer.draw(fb, level.crate, textures.crate, viewProj, config)
|
||||
Rasterizer.draw(fb, level.boulders, textures.rock, viewProj, config)
|
||||
Rasterizer.draw(fb, level.trunks, textures.bark, viewProj, config)
|
||||
Rasterizer.draw(fb, level.oakFoliage, textures.leaf, viewProj, config)
|
||||
Rasterizer.draw(fb, level.spruceFoliage, textures.needle, viewProj, config)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue