meat/engine/world/CharacterController.ts

89 lines
2.3 KiB
TypeScript

import type { Vec3 } from "../math/Vec3"
import { CollisionWorld, type CollisionWorld as World } from "./CollisionWorld"
export type Character = {
position: Vec3
yaw: number
velocityY: number
onGround: boolean
}
export type CharacterInput = {
forward: number
right: number
jump: boolean
run: boolean
}
export type CharacterConfig = {
radius: number
speed: number
runMultiplier: number
gravity: number
jumpSpeed: number
eyeHeight: number
}
export namespace CharacterController {
export function update(
character: Character,
input: CharacterInput,
dt: number,
world: World,
config: CharacterConfig,
): void {
if (input.jump && character.onGround) {
character.velocityY = config.jumpSpeed
character.onGround = false
}
const steps = moveSubsteps(input, dt, config)
for (let i = 0; i < steps; i++) {
moveHorizontal(character, input, dt / steps, config)
CollisionWorld.pushOut(world, character.position, config.radius)
}
character.velocityY -= config.gravity * dt
character.position.y += character.velocityY * dt
const ground = CollisionWorld.groundHeight(world, character.position)
if (character.position.y <= ground) {
character.position.y = ground
character.velocityY = 0
character.onGround = true
} else {
character.onGround = false
}
}
function moveSubsteps(
input: CharacterInput,
dt: number,
config: CharacterConfig,
): number {
const distance =
config.speed *
runFactor(input, config) *
dt *
Math.hypot(input.forward, input.right)
return Math.max(1, Math.ceil(distance / config.radius))
}
function moveHorizontal(
character: Character,
input: CharacterInput,
dt: number,
config: CharacterConfig,
): void {
const speed = config.speed * runFactor(input, config) * dt
const forwardX = Math.sin(character.yaw)
const forwardZ = -Math.cos(character.yaw)
const rightX = Math.cos(character.yaw)
const rightZ = Math.sin(character.yaw)
character.position.x +=
(forwardX * input.forward + rightX * input.right) * speed
character.position.z +=
(forwardZ * input.forward + rightZ * input.right) * speed
}
function runFactor(input: CharacterInput, config: CharacterConfig): number {
return input.run ? config.runMultiplier : 1
}
}