meat/game/actors/Mob.ts
2026-08-24 15:54:44 +02:00

64 lines
2.1 KiB
TypeScript

import type { Vec3 } from "engine/math/Vec3"
import type { Actor } from "engine/scene/Actor"
import type { RenderTransform } from "engine/render/RenderScene"
import type { BoxCollider } from "engine/world/Collider"
import type { Terrain } from "engine/world/Terrain"
/** Runtime actor using shared roaming-creature state. Concrete definitions are
* direct object references supplied by Frog, Bee, Robin, or future content. */
export type Mob = Actor<Terrain>
export type MobState = {
/** Leash anchor (where it was scattered); wandering is pulled back toward it. */
home: Vec3
/** Live feet-center (frog/robin) / body-center (bee), advanced each frame. */
position: Vec3
/** Facing yaw; the mesh's front is local +Z, so world dir = (sin h, 0, cos h). */
heading: number
/** Per-instance size multiplier. */
scale: number
/** Evolving RNG state (mutated by `update`) -- keeps the sim deterministic. */
seed: number
/** Horizontal velocity (frog/robin: mid-hop or -flight; bee: cruise). */
vx: number
vz: number
/** Vertical velocity (frog/robin ballistic hop/flight; bee stays 0, uses a bob). */
vy: number
/** Countdown to the next decision (frog/robin: next hop; bee: next heading change). */
timer: number
/** Behavior scratch clock: the bee's hover-bob phase; the robin's remaining
* powered-flight cruise time (>0 while gliding between perches). */
phase: number
/** Frog/robin: resting on the ground vs airborne (a hop or a flight). */
grounded: boolean
}
export namespace Mob {
export function transform(state: MobState): RenderTransform {
return {
x: state.position.x,
y: state.position.y,
z: state.position.z,
heading: state.heading,
scale: state.scale,
}
}
export function collider(
state: MobState,
radius: number,
height: number,
standable: boolean,
): BoxCollider {
const half = radius * state.scale * 0.7
return {
shape: "box",
minX: state.position.x - half,
maxX: state.position.x + half,
minZ: state.position.z - half,
maxZ: state.position.z + half,
top: state.position.y + height * state.scale,
standable,
}
}
}