Skip to content

Packages ​

Optional, composable pieces you can add to a game. They are subpaths of the quantum-forge-engine package, so using one is an import rather than an install.

Two of them need an optional peer dependency first:

bash
npm install pixi.js   # PixiRenderer
npm install howler    # AudioManager

Available packages ​

PackageImportDescription
Inputquantum-forge-engine/inputKeyboard, mouse, gamepad, touch, gestures, local multiplayer
Collisionquantum-forge-engine/collisionRect, circle, and point tests plus a SpatialGrid
Audioquantum-forge-engine/audioHowler.js wrapper for sounds and music
Particlesquantum-forge-engine/particlesBurst, trail, and explosion effects
Animationquantum-forge-engine/animationTweening with easing functions
Entitiesquantum-forge-engine/entitiesEntity registry with spatial queries; disposes quantum handles on removal
State machinequantum-forge-engine/state-machineConfig-driven finite state machine
Timerquantum-forge-engine/timerGame-aware timers, pause- and scale-aware
Savequantum-forge-engine/saveSlot-based saves with versioning and auto-save
Scenesquantum-forge-engine/scenesStack-based scene lifecycle

Collision ​

Collision is a set of pure functions plus a grid for narrowing candidates.

typescript
import { SpatialGrid, circleOverlaps } from "quantum-forge-engine/collision";

const grid = new SpatialGrid<Enemy>(64);
for (const enemy of state.enemies) grid.add(enemy);

for (const other of grid.getNearby(player.x, player.y)) {
  if (circleOverlaps(player, other)) {
    // handle collision
  }
}

getNearby(x, y) takes coordinates and returns everything in that cell and the eight around it. Confirm real overlaps with rectOverlaps, circleOverlaps, rectCircleOverlaps, or one of the point tests.

Audio ​

typescript
import { AudioManager } from "quantum-forge-engine/audio";

const audio = new AudioManager({ soundVolume: 0.8, musicVolume: 0.4, logger });
await audio.loadSound("hit", "/sounds/hit.mp3");
audio.play("hit");

audio.setMasterVolume(0.8);
audio.mute();    // no argument
audio.unmute();

Browsers block audio until the player interacts with the page. isUnlocked() reports whether that has happened, which is what a "tap to enable sound" prompt keys off.

Particles ​

typescript
import { ParticleSystem } from "quantum-forge-engine/particles";

const particles = new ParticleSystem({ gravity: 200 });

particles.emit({ x, y, count: 20, color: "#a855f7", speed: 100, life: 0.5 });
particles.burst(x, y, Math.PI / 2, 20, "#a855f7");  // positional arguments

// In game loop
particles.update(dt);
particles.render(ctx);              // Canvas 2D
particles.renderToGraphics(this.graphics);  // PixiJS

burst, trail, and explode take positional arguments. emit(config) is the options-object entry point, and its lifetime field is life, in seconds.

Animation ​

typescript
import { Tween, MultiTween, Easing } from "quantum-forge-engine/animation";

const fade = new Tween({
  from: 0,
  to: 1,
  duration: 500,          // milliseconds
  easing: Easing.easeOutCubic,
  onUpdate: (value) => { sprite.alpha = value; },
});
fade.start();

function frame(now: number) {
  fade.update(now);       // pass a timestamp, not a delta
  if (fade.isActive()) requestAnimationFrame(frame);
}

Tween animates one number. For object-shaped animations use MultiTween with a values map:

typescript
const move = new MultiTween({
  values: { x: { from: 0, to: 100 }, y: { from: 0, to: 200 } },
  duration: 500,
  easing: Easing.easeOutCubic,
  onUpdate: (values) => { sprite.x = values.x; sprite.y = values.y; },
});

Easings live on the Easing object. AnimationManager starts animations for you and updates them all from one call.

Entities ​

EntityManager keeps entities by id and answers spatial queries. From engine 2.0 it also ends the life of the quantum handles an entity carries.

typescript
import { ensureLoaded, quantum, type Quantum } from "quantum-forge/quantum";
import { EntityManager } from "quantum-forge-engine/entities";

await ensureLoaded();

interface Ghost {
  id: string;
  x: number;
  y: number;
  here: Quantum<boolean>;
}

const ghosts = new EntityManager<Ghost>();
ghosts.add({ id: "g1", x: 40, y: 60, here: quantum([false, true]).superpose() });

ghosts.get("g1")?.here.probability(true); // 0.5, drawn as alpha
ghosts.remove("g1");                      // disposes g1.here

remove() and clear() dispose the handles stored directly on an entity, under a string or symbol key, and the handles inside an array stored there. Nothing deeper is walked. add() with an id that is already present replaces the entity and disposes the old entity's handles that the new one no longer carries.

Disposing measures, so removing an entity collapses whatever its handles were entangled with. A handle belongs to one entity: if two entities share it, removing either one disposes it for both. Pass { disposeQuantum: false } to remove(), clear() or add() to keep the handles, or to the constructor to change the default. See Lifecycle.

Save ​

typescript
import { SaveManager } from "quantum-forge-engine/save";

const saves = new SaveManager<GameState>({ prefix: "my-game", version: 1 });

saves.save("slot-1", engine.getState());
const data = saves.load("slot-1");

saves.enableAutoSave("autosave", () => engine.getState(), 30); // seconds
saves.update(dt);  // in the game loop, drives auto-save

Every method takes a named slot. Auto-save is turned on with enableAutoSave() rather than a constructor flag, and it advances only when you call update(dt).

Saves are JSON. A quantum handle is a live reference into the WASM simulator, not data, so keep handles out of the state you save. To save quantum state, record it with QuantumRecorder and store the serialized log next to the game state.

Scenes ​

typescript
import { SceneManager, fadeTransition } from "quantum-forge-engine/scenes";

const scenes = new SceneManager<GameContext>({ logger });

await scenes.push("menu", menuScene, context);
await scenes.push("game", gameScene, context, fadeTransition(0.3, drawFade));
await scenes.pop();  // back to menu, which gets onResume()

There is no register() step: you pass the scene object to push() or replace(). A scene implements update(dt) and render(), and may add onEnter, onExit, onPause, and onResume. Drive the stack by calling scenes.update(dt) and scenes.render() each frame.

Powered by Quantum Forge