Skip to content

Start a game ​

Goal ​

Share one Play action between people, the sandbox and playtest.

Files you will edit ​

  • src/main.ts — the page's Play action and existing button.
  • src/game.ts — gameplay latches in the guest.

Steps ​

  1. In page code, register everything the existing Play action does with onStart; never start through another DOM event or guessed key.

    ts
    import { onStart, startGame } from 'gameable/host';
    
    onStart(({ reason }) => {
      document.querySelector('#title')?.classList.add('hidden');
      if (reason === 'player') {
        document.querySelector('canvas')?.requestPointerLock();
      }
    });
    document.querySelector('#play')?.addEventListener('click', () => {
      void startGame({ reason: 'player' });
    });

    Callbacks run once in order; the first runs synchronously. Promises are awaited. Capture skips pointer lock and audio permission. Unregister on disposal.

  2. Gate gameplay with ctx.started: true before systems receive the start event, until init. Keep drawing camera/HUD while waiting. First-input latches must also accept start (reasons: player, capture, playtest).

    ts
    import { defineGame } from 'gameable';
    
    export default defineGame({
      update(ctx) {
        if (!ctx.started) return;
      },
    });

Solo ctx.data saves persist; version them with defineGame({ save: { version, migrate } }).

Verify ​

Press Play. Reload; the sandbox calls await window.__gameable.startGame({ reason: 'capture' }); the playtest agent uses { reason: 'playtest' }. Omitting options defaults to player. Both paths run Play and send one guest event. The promise waits for two rendered frames after enqueue. Repeated calls resolve immediately, even during Play. window.__gameable.started is read-only: true when the event is queued. Callback failures reject once; later calls do not retry. Rebuild compiled guests.

See also ​

Use defineGame({ framework: 'default' }) for default match actors and HUD; ctx.pause.push('menu') pauses until the page calls game.pause.pop('menu'). Travel removes level actors and code spawns; mark actors persist: 'session' to keep them with players, and use ctx.levels.load(ctx, name, { arrival: true }) to require a destination start before teardown.