Engine recipes

Drop-in setups for the most common HTML5 game stacks.

The SDK is engine-agnostic — you can wire it into anything that runs in a browser. This page collects the canonical setup for the engines GAMEE devs use most. Every recipe follows the same shape:

  1. Init the SDK in your boot path.
  2. Subscribe to start, pause, resume, mute, unmute.
  3. Call gameReady() the moment the engine is up.
  4. Translate SDK events into the engine’s lifecycle hooks.

The vanilla recipe below is the canonical baseline; the engine-specific recipes just translate steps 1–4 into the engine’s idioms.


Vanilla JS / Canvas (the baseline)#

<!doctype html>
<canvas id="c"></canvas>
<script src="/vendor/gamee-sdk/gamee-sdk.iife.js"></script>
<script>
  const { gamee } = Gamee;

  let running = false;
  let last = 0;

  async function boot() {
    await gamee.init({ capabilities: [] });

    gamee.on('start', () => {
      running = true;
      last = performance.now();
      requestAnimationFrame(loop);
    });
    gamee.on('pause', () => {
      running = false;
    });
    gamee.on('resume', () => {
      running = true;
      last = performance.now();
      requestAnimationFrame(loop);
    });
    gamee.on('mute', () => audio.mute());
    gamee.on('unmute', () => audio.unmute());

    gamee.gameReady();
  }

  function loop(now) {
    if (!running) return;
    const dt = (now - last) / 1000;
    last = now;
    update(dt);
    render();
    requestAnimationFrame(loop);
  }

  boot();
</script>

The other recipes show the same five touchpoints inside each engine.


Phaser 3#

import { gamee } from '@gamee/sdk';
import Phaser from 'phaser';

class MainScene extends Phaser.Scene {
  preload() {
    /* assets */
  }
  create() {
    /* build the world */
  }
  update(_t: number, dt: number) {
    // Phaser passes dt in ms; divide by 1000 if you want seconds.
  }
}

async function boot() {
  await gamee.init({ capabilities: ['saveState'] });

  const game = new Phaser.Game({
    type: Phaser.AUTO,
    parent: 'game',
    width: window.innerWidth,
    height: window.innerHeight,
    scene: [MainScene],
    scale: { mode: Phaser.Scale.RESIZE },
  });

  // Map platform events to Phaser scene control.
  gamee.on('start', () => game.scene.start('MainScene'));
  gamee.on('pause', () => game.scene.getScenes(true).forEach((s) => s.scene.pause()));
  gamee.on('resume', () => game.scene.getScenes(false).forEach((s) => s.scene.resume()));
  gamee.on('mute', () => (game.sound.mute = true));
  gamee.on('unmute', () => (game.sound.mute = false));

  gamee.gameReady();
}

boot();

CDN script-tag variant — same logic, no bundler:

<script src="https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.min.js"></script>
<script src="/vendor/gamee-sdk/gamee-sdk.iife.js"></script>
<script>
  // Use `Gamee.gamee` and the global `Phaser`.
</script>

PixiJS#

PixiJS games drive their loop from app.ticker. Stop the ticker on pause, start it on resume.

import { gamee } from '@gamee/sdk';
import * as PIXI from 'pixi.js';

const app = new PIXI.Application();

async function boot() {
  await app.init({ resizeTo: window });
  document.body.appendChild(app.canvas);

  await gamee.init({ capabilities: [] });

  // Build the world here.
  // ...

  gamee.on('start', () => app.ticker.start());
  gamee.on('pause', () => app.ticker.stop());
  gamee.on('resume', () => app.ticker.start());
  gamee.on('mute', () => audio.mute());
  gamee.on('unmute', () => audio.unmute());

  gamee.gameReady();
}

boot();

Three.js#

Three has no built-in scene-pause hook — you guard the render loop yourself.

import { gamee } from '@gamee/sdk';
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector('canvas')! });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);

let running = false;
let last = 0;

function loop(now: number) {
  if (!running) return;
  const dt = (now - last) / 1000;
  last = now;
  // update(dt)
  renderer.render(scene, camera);
  requestAnimationFrame(loop);
}

async function boot() {
  await gamee.init({ capabilities: [] });

  gamee.on('start', () => {
    running = true;
    last = performance.now();
    requestAnimationFrame(loop);
  });
  gamee.on('pause', () => {
    running = false;
  });
  gamee.on('resume', () => {
    running = true;
    last = performance.now();
    requestAnimationFrame(loop);
  });
  gamee.on('mute', () => audio.mute());
  gamee.on('unmute', () => audio.unmute());

  gamee.gameReady();
}

boot();

Babylon.js#

Babylon’s engine.runRenderLoop is the natural pause/resume hook.

import { gamee } from '@gamee/sdk';
import { Engine, Scene, ArcRotateCamera, Vector3 } from '@babylonjs/core';

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const engine = new Engine(canvas, true);
const scene = new Scene(engine);
new ArcRotateCamera('cam', 0, 1, 5, Vector3.Zero(), scene).attachControl(canvas, true);

const tick = () => scene.render();

async function boot() {
  await gamee.init({ capabilities: [] });

  gamee.on('start', () => engine.runRenderLoop(tick));
  gamee.on('pause', () => engine.stopRenderLoop(tick));
  gamee.on('resume', () => engine.runRenderLoop(tick));
  gamee.on('mute', () => Engine.audioEngine!.setGlobalVolume(0));
  gamee.on('unmute', () => Engine.audioEngine!.setGlobalVolume(1));

  gamee.gameReady();
}

window.addEventListener('resize', () => engine.resize());
boot();

Construct 3#

In Construct 3, exported HTML5 projects can mount a JavaScript module via the Scripting feature. Wire the SDK from the On start of layout script and toggle the runtime on pause/resume.

// scripts/main.js — set "Scripts in event sheet" → On start of layout.
import { gamee } from '@gamee/sdk';

runOnStartup(async (runtime) => {
  await gamee.init({ capabilities: ['saveState'] });

  gamee.on('start', () => runtime.setSuspended(false));
  gamee.on('pause', () => runtime.setSuspended(true));
  gamee.on('resume', () => runtime.setSuspended(false));
  gamee.on('mute', () => runtime.objects.Audio.setSilent(true));
  gamee.on('unmute', () => runtime.objects.Audio.setSilent(false));

  // Forward Construct's score system to the platform on every change:
  runtime.addEventListener('tick', () => {
    const score = runtime.globalVars.Score;
    if (score !== last) {
      gamee.updateScore({ score, playTime: runtime.gameTime, checksum: 'c3' });
      last = score;
    }
  });

  gamee.gameReady();
});

let last = 0;

If you can’t add an npm dependency to your Construct project, switch to the IIFE script tag in index.html and use Gamee.gamee instead of the import.


Choosing your import style#

Every recipe above uses the npm import. The same code works with all three import styles documented on the install page:

  • npm + ESM — what’s in the recipes.
  • <script> IIFE — replace import { gamee } from '@gamee/sdk' with const { gamee } = Gamee and load the CDN script first.
  • CDN ESM — replace the import URL with 'https://esm.sh/@gamee/[email protected]'.

When in doubt, start with the IIFE — zero build setup, fastest to test against the emulator (top-right button on every page).

Switch to a desktop

The GAMEE SDK emulator and docs are built for screens wider than 1280 px. Open this page on a laptop or desktop browser to get the full experience.

If you're already on a wide screen, drag your window wider and this banner will disappear.