meat/game/actors/mobs/Frog.ts

77 lines
2.5 KiB
TypeScript
Raw Normal View History

import { Terrain, type Terrain as Ground } from "../../../engine/world/Terrain"
import { Mesh, type Mesh as Geometry } from "../../../engine/scene/Mesh"
import type { Material } from "../../../engine/render/Material"
import type { ActorDefinition } from "../../../engine/scene/Actor"
import { Mob, type MobState } from "../Mob"
2026-08-07 19:11:43 +02:00
import { ellipsoid, nextRand, wanderHeading } from "./mobkit"
// Everything about the frog: squat, ground-bound, sits then springs a ballistic hop.
export type Frog = ActorDefinition<MobState, Ground>
export namespace Frog {
export function create(material: Material): Frog {
const mesh = Mesh.create()
build(mesh)
return {
prototype: {
groups: [{ mesh, material }],
radius: 0.7,
minY: -0.7,
maxY: 1.3,
},
update,
transform: Mob.transform,
collider: (state) => Mob.collider(state, 0.7, 0.6, state.grounded),
}
}
}
2026-08-07 19:11:43 +02:00
const LEASH = 5
const REST_MIN = 0.7
const REST_SPAN = 1.8
const HOP_SPEED = 1.6
const HOP_IMPULSE = 3.2
const GRAVITY = 14
function build(mesh: Geometry): void {
2026-08-07 19:11:43 +02:00
// Wide squat body, two eye bumps on the top-front, two hind haunches. UVs:
// the frog texture is green skin on the left, a dark eye tone on the right.
ellipsoid(mesh, 0, 0.26, 0, 0.5, 0.28, 0.52, 6, 4, 0, 0.68, 0, 1)
ellipsoid(mesh, 0.24, 0.5, 0.26, 0.13, 0.13, 0.13, 4, 3, 0.75, 0.98, 0, 1)
ellipsoid(mesh, -0.24, 0.5, 0.26, 0.13, 0.13, 0.13, 4, 3, 0.75, 0.98, 0, 1)
ellipsoid(mesh, 0.3, 0.2, -0.26, 0.2, 0.2, 0.26, 4, 3, 0, 0.68, 0, 1)
ellipsoid(mesh, -0.3, 0.2, -0.26, 0.2, 0.2, 0.26, 4, 3, 0, 0.68, 0, 1)
}
function update(mob: MobState, dt: number, terrain: Ground): void {
2026-08-07 19:11:43 +02:00
if (mob.grounded) {
mob.timer -= dt
mob.position.y = Terrain.height(terrain, mob.position.x, mob.position.z)
if (mob.timer > 0) {
return
}
// Launch a hop: pick a heading (pulled homeward past the leash), then convert
// it into a forward+upward ballistic velocity.
mob.heading = wanderHeading(mob, LEASH, 0.9)
mob.vx = Math.sin(mob.heading) * HOP_SPEED
mob.vz = Math.cos(mob.heading) * HOP_SPEED
mob.vy = HOP_IMPULSE
mob.grounded = false
return
}
mob.vy -= GRAVITY * dt
mob.position.x += mob.vx * dt
mob.position.y += mob.vy * dt
mob.position.z += mob.vz * dt
const ground = Terrain.height(terrain, mob.position.x, mob.position.z)
if (mob.position.y <= ground && mob.vy < 0) {
mob.position.y = ground
mob.vx = 0
mob.vy = 0
mob.vz = 0
mob.grounded = true
mob.timer = REST_MIN + nextRand(mob) * REST_SPAN
}
}