Use the placeholder assets
Goal
Your game renders the placeholder arena, puts the player and six enemies on its spawn points, and fires a sound — with no content of your own and no asset paths anywhere in game code.
Files you will edit
src/main.tssrc/game.ts
Steps
Merge the pack into the manifest in
src/main.ts. The package ships its own entries and its ownbaseUrl, so the idsenv.arenaandsfx.*become available without a single path appearing in your game.tsimport { parseManifest } from 'gameable/assets'; import { arenaSpawns, PLACEHOLDER_ASSETS_BASE, placeholderManifest } from 'gameable/placeholder'; import { createEngine } from 'gameable/core'; const manifest = parseManifest( { ...placeholderManifest, baseUrl: PLACEHOLDER_ASSETS_BASE }, { baseUrl: PLACEHOLDER_ASSETS_BASE }, ); const engine = await createEngine({ canvas: document.querySelector('canvas')!, manifest, // The spawn table is data, not an asset: hand it to the guest as config. config: { spawns: arenaSpawns }, }); await engine.assets.preload('world'); await engine.start();Use the ids and the spawn points from
src/game.ts. Spawn coordinates arrive as plain numbers throughctx.config, so the guest never imports the package and never sees a URL.tsimport type { GameContext } from 'gameable'; // Set in init, not at module scope: Wizer freezes module state into the // binary, so a handle taken at import time would be the same every run. let arena = 0; export function init(ctx: GameContext): void { arena = ctx.assets.resolve('env.arena'); ctx.spawnWorld(arena); const { player, enemies } = ctx.config.spawns; ctx.spawnPlayer(player.position, player.yaw); for (const enemy of enemies) ctx.spawnEnemy(enemy.position, enemy.yaw); } export function fire(ctx: GameContext): void { // By id, never by path: the registry is the only thing that knows a URL. ctx.audio.play('sfx.shot', { bus: 'sfx', volume: 0.7 }); }
Verify
sh
npm run devYou start at [0, 0, 9] looking down -Z at a 24 x 24 m checkered floor ringed by four 3 m walls, with six pillars, a crate and a ramp between you and the far wall, and six capsules standing on the perimeter. Firing plays a short crack.
Then assert it headlessly:
sh
npm testts
expect(harness.commands('spawn-world')).toContainEqual(
expect.objectContaining({ asset: 'env.arena' }),
);
expect(harness.commands('spawn-enemy')).toHaveLength(6);See also
- Assets and the manifest
- Play a sound
packages/assets-placeholder/README.md— gameable/placeholder