Add a physics body
Goal
A crate that falls, lands on the level geometry, and can be shoved by shooting it. When you are done, npm run dev shows a box that obeys gravity and reports its collisions to your game code.
Files you will edit
src/prefabs.tssrc/game.ts
Steps
Declare the prefab.
prefab()is pure, so it is safe at module scope — the Wizer snapshot captures nothing.ts// src/prefabs.ts import { prefab } from '@aosengine/sdk'; export const Crate = prefab({ name: 'crate', asset: 'crate', // a manifest string id, never a URL body: { shape: 'box', dims: [0.5, 0.5, 0.5], // half extents, metres kind: 'dynamic', mass: 25, friction: 0.6, restitution: 0.1, layer: { debris: true }, mask: { defaultLayer: true, staticGeometry: true, player: true, projectile: true }, flags: { reportContacts: true }, }, });layersays what the crate is;masksays what it collides with. Both sides must agree — a body whosemaskomitsdebriswill pass straight through the crate however the crate is masked.Spawn one in
init. Preallocate the spawn position:initruns once, but the same habit is what keeps systems allocation-free.ts// src/game.ts import { defineGame, physics, spawn, Transform, type GameContext } from '@aosengine/sdk'; import { Crate } from './prefabs'; const crateSpawn = { x: 0, y: 4, z: -6 }; let crate = 0; export default defineGame({ assets: ['arena', 'crate'], init() { crate = spawn(Crate, crateSpawn); }, systems: [shoveCrate], });Push it with a raycast. A hitscan shot is one synchronous query and one deferred impulse command; neither allocates in the steady state.
ts// src/game.ts const SHOVE = 400; // newton-seconds function shoveCrate(ctx: GameContext): void { if (!ctx.input.pressed('Mouse0')) return; const hit = physics.raycast( ctx.camera.position, ctx.camera.forward, 100, undefined, ctx.player, ); if (hit === null || hit.entity !== crate) return; physics.applyImpulse( hit.entity, ctx.camera.forward.x * SHOVE, 0, ctx.camera.forward.z * SHOVE, ); }Read the result. The host writes every non-static body's pose into the stride-14 body buffer each fixed step, and the SDK unpacks it into
Transform, so the crate's position is already there:ts// src/game.ts — inside a system if (Transform.y[crate] < -20) physics.teleport(crate, crateSpawn.x, crateSpawn.y, crateSpawn.z);
Verify
sh
npm run devThe crate falls from four metres, lands on the floor and stops within about a second. Shooting it slides it away from you; it never sinks through the floor and never comes to rest below y = 0. In a test, assert the landing:
ts
expect(Transform.y[crate]).toBeGreaterThan(0.4);
expect(Transform.y[crate]).toBeLessThan(0.6);