feat: bees and frogs
This commit is contained in:
parent
46e7070b6b
commit
16f205babe
13 changed files with 540 additions and 31 deletions
65
app/main.ts
65
app/main.ts
|
|
@ -1,12 +1,19 @@
|
|||
import { RenderConfig } from "../engine/render/RenderConfig"
|
||||
import { Camera } from "../engine/scene/Camera"
|
||||
import type { Mesh } from "../engine/scene/Mesh"
|
||||
import { Mob } from "../engine/scene/Mob"
|
||||
import type { Vec3 } from "../engine/math/Vec3"
|
||||
import { loadTextures } from "./assets"
|
||||
import { buildLevel } from "./level"
|
||||
import { buildLevel, type Level } from "./level"
|
||||
import { EYE_HEIGHT, Player } from "./player"
|
||||
import { createRenderer } from "./renderer"
|
||||
import { chunkFar, visibleChunks, type Scene } from "./renderScene"
|
||||
import { chunkFar, visibleChunks, visibleMobs, type Scene } from "./renderScene"
|
||||
|
||||
const FOV = Math.PI / 3
|
||||
/** How close (world units) a mob must be to the player to get a live collider.
|
||||
* Mobs farther than this can't be touched this frame, so skip them -- keeps the
|
||||
* per-frame collider list (and the player's collision loop) short. */
|
||||
const MOB_COLLIDE_RANGE = 3
|
||||
|
||||
const screen = document.querySelector<HTMLCanvasElement>("#screen")!
|
||||
const ctx = screen.getContext("2d")!
|
||||
|
|
@ -40,12 +47,20 @@ function benchStats(a: number[]): { median: number; p95: number; max: number; me
|
|||
async function main(): Promise<void> {
|
||||
const textures = await loadTextures()
|
||||
const level = buildLevel()
|
||||
// Two canonical mob meshes, built once and shared by every instance (the sim
|
||||
// supplies each mob's per-frame transform).
|
||||
const frogMesh: Mesh = { verts: [], indices: [] }
|
||||
const beeMesh: Mesh = { verts: [], indices: [] }
|
||||
Mob.build("frog", frogMesh)
|
||||
Mob.build("bee", beeMesh)
|
||||
const scene: Scene = {
|
||||
chunks: level.chunks,
|
||||
floor: level.floor,
|
||||
walls: level.walls,
|
||||
crate: level.crate,
|
||||
npc: { position: level.npcPosition, size: { x: 1.1, y: 1.5 } },
|
||||
mobMesh: { frog: frogMesh, bee: beeMesh },
|
||||
mobCount: level.mobs.length,
|
||||
sky: level.sky,
|
||||
textures,
|
||||
}
|
||||
|
|
@ -99,11 +114,13 @@ async function main(): Promise<void> {
|
|||
globalThis.addEventListener("resize", layout)
|
||||
|
||||
if (benchMode) {
|
||||
runBench(renderer, level.chunks, present, benchMode)
|
||||
runBench(renderer, level, present, benchMode)
|
||||
return
|
||||
}
|
||||
|
||||
const player: Player = { position: { x: 0, y: 0, z: 8 }, yaw: 0, pitch: 0, velocityY: 0, onGround: true }
|
||||
// Colliders past this index are the dynamic mob ones, rebuilt every frame.
|
||||
const staticColliderCount = level.colliders.length
|
||||
const keys = new Set<string>()
|
||||
globalThis.addEventListener("keydown", (e) => {
|
||||
keys.add(e.code)
|
||||
|
|
@ -200,6 +217,10 @@ async function main(): Promise<void> {
|
|||
presentMax = 0
|
||||
vsyncMax = 0
|
||||
}
|
||||
for (const m of level.mobs) {
|
||||
Mob.update(m, dt, level.terrain)
|
||||
}
|
||||
rebuildMobColliders(level, player.position, staticColliderCount)
|
||||
Player.update(player, keys, dt, level)
|
||||
const camera: Camera = {
|
||||
position: { x: player.position.x, y: player.position.y + EYE_HEIGHT, z: player.position.z },
|
||||
|
|
@ -209,9 +230,10 @@ async function main(): Promise<void> {
|
|||
}
|
||||
const viewProj = Camera.viewProjection(camera, renderer.fb.width / renderer.fb.height)
|
||||
const visible = visibleChunks(level.chunks, viewProj)
|
||||
const mobDraws = visibleMobs(level.mobs, viewProj)
|
||||
lastVisible = visible
|
||||
lastCamera = camera
|
||||
renderer.dispatch(camera, viewProj, visible, now / 1000)
|
||||
renderer.dispatch(camera, viewProj, visible, now / 1000, mobDraws)
|
||||
inFlight = true
|
||||
if (renderer.done()) {
|
||||
show()
|
||||
|
|
@ -225,7 +247,7 @@ async function main(): Promise<void> {
|
|||
* interval, then reports the distributions (exposed on `window.__BENCH__`). */
|
||||
function runBench(
|
||||
renderer: ReturnType<typeof createRenderer>,
|
||||
chunks: Scene["chunks"],
|
||||
level: Level,
|
||||
present: () => void,
|
||||
mode: string,
|
||||
): void {
|
||||
|
|
@ -281,10 +303,14 @@ function runBench(
|
|||
return
|
||||
}
|
||||
}
|
||||
for (const m of level.mobs) {
|
||||
Mob.update(m, 1 / 60, level.terrain)
|
||||
}
|
||||
const camera = benchCamera(i / 60)
|
||||
const viewProj = Camera.viewProjection(camera, renderer.fb.width / renderer.fb.height)
|
||||
const visible = visibleChunks(chunks, viewProj)
|
||||
renderer.dispatch(camera, viewProj, visible, i / 60)
|
||||
const visible = visibleChunks(level.chunks, viewProj)
|
||||
const mobDraws = visibleMobs(level.mobs, viewProj)
|
||||
renderer.dispatch(camera, viewProj, visible, i / 60, mobDraws)
|
||||
inFlight = true
|
||||
if (renderer.done() && record()) {
|
||||
report()
|
||||
|
|
@ -293,6 +319,31 @@ function runBench(
|
|||
requestAnimationFrame(tick)
|
||||
}
|
||||
|
||||
/** Rebuild the dynamic tail of `level.colliders`: keep the static prefix, then add
|
||||
* a block/stand-on AABB for each mob near the player. Mobs move, so these can't be
|
||||
* baked; frogs are `standable` (hop onto them), bees only block (no mid-air
|
||||
* platform). Only mobs within `MOB_COLLIDE_RANGE` are added -- the rest can't be
|
||||
* reached this frame anyway. */
|
||||
function rebuildMobColliders(level: Level, playerPos: Vec3, staticCount: number): void {
|
||||
level.colliders.length = staticCount
|
||||
for (const m of level.mobs) {
|
||||
const dx = m.position.x - playerPos.x
|
||||
const dz = m.position.z - playerPos.z
|
||||
if (dx * dx + dz * dz > MOB_COLLIDE_RANGE * MOB_COLLIDE_RANGE) {
|
||||
continue
|
||||
}
|
||||
const half = Mob.boundingRadius(m.kind) * m.scale * 0.7
|
||||
level.colliders.push({
|
||||
minX: m.position.x - half,
|
||||
maxX: m.position.x + half,
|
||||
minZ: m.position.z - half,
|
||||
maxZ: m.position.z + half,
|
||||
top: m.position.y + Mob.bodyHeight(m.kind) * m.scale,
|
||||
standable: m.kind === "frog",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue