// 1. Define your component types interfaceComponents { position: { x: number; y: number }; velocity: { x: number; y: number }; health: { value: number }; }
// 2. Create a world using the builder. // Component, event, and resource types flow through the chain — you // never hand-annotate a system's callback; it all comes from here. constworld = ECSpresso.create() .withComponentTypes<Components>() .withResource('regenRate', 5) // heals 5 hp/sec — also previews resources // .withEventTypes<{ ... }>() // events slot in the same way .build();
// 3a. Single-query, per-entity iteration → setProcessEach. // `mutates: ['position']` auto-stamps change-detection after each tick // and narrows `velocity` to Readonly at the type level — writing to it // would be a compile error. world.addSystem('integrate-velocity') .setProcessEach( { with: ['position', 'velocity'], mutates: ['position'], }, ({ entity, dt }) => { // entity.components.position is { x: number; y: number } — inferred. entity.components.position.x += entity.components.velocity.x * dt; entity.components.position.y += entity.components.velocity.y * dt; }, );
// 3b. Multi-concern or resource-driven systems → addQuery + setProcess. // Same `mutates` contract; the outer for…of is yours to write. world.addSystem('regen-health') .addQuery('injured', { with: ['health'], mutates: ['health'], }) .withResources(['regenRate']) .setProcess(({ queries, dt, resources: { regenRate } }) => { for (constentityofqueries.injured) { entity.components.health.value += regenRate * dt; } });
// 4. Create entities. Systems default to the `update` phase; see // the Systems guide for `fixedUpdate`, `render`, and phase ordering. constplayer = world.spawn({ position: { x:0, y:0 }, velocity: { x:10, y:5 }, health: { value:80 }, });
mutates in one line: declares which components a system writes — auto-marks them as changed each tick and narrows the rest of with to Readonly<T>. Less boilerplate, more compile-time safety.