Build a game

A game is three iframes and one manifest. This page walks the four steps from an empty folder to a running session, with the commands the CLI actually runs.

1. Create

Install the CLI and scaffold a pack. Node 20 or newer. The sample pack you get is the reference implementation for all three iframes.

npm i -g @fzgame/cli
fzgame init my-game
cd my-game
fzgame dev

fzgame dev serves the pack on port 3400 with the kit and a mock runtime, so you can build the game before any server exists.

game/

Player iframe. Draw the game, call the kit.

editor/

Studio iframe. Edits instance config from formSchema.

handler/

Runs on finish. Turns the payload into stored score and result.

fzgame.json

Manifest: identity, entry points, and the four schemas.

2. Ship

Build validates the manifest, vendors the kit into the pack, and zips the three folders. Publish posts that zip as a new version.

fzgame build
fzgame publish --api https://gamehub.example.com --token $FZGAME_TOKEN --space <space-id>

Slug plus version is unique on publish: bump info.version in fzgame.json to ship a new pack. A published version is what the catalog lists and what a space instantiates.

3. Integrate

The host page passes the runtime values in the query string. The kit reads them, so the game never hardcodes an API URL.

apiUrl

Gamehub origin the kit talks to.

gameInstanceId

Instance being played.

playerToken

Player identity that already exists on the host.

preview

Runs the session in memory, no server writes.

import { createKit } from '@fzgame/kit'

const kit = createKit()

await kit.load()      // GET  /pub/game-instances/:id
await kit.connect()   // POST /games/runtime/sessions
await kit.emit('ready')
await kit.emit('start')

await kit.setState({ clicks: 1 })
await kit.emit('score', { score: 1 })
await kit.emit('finish', { score: 1, result: { clicks: 1 } })

Integration on the product side is one iframe pointed at the instance URL with a player token. Sessions are created on connect() and reused while one is active.

4. Play

A session starts CREATED on connect and moves with named events. Terminal states accept nothing, so a finished session is final.

CREATED -> ready -> READY -> start -> PLAYING <-> PAUSED
                                            finish -> FINISHED
                                            abort  -> ABORTED

Scores and state live on the session, and kit.getLeaderboard() reads either one row per player or every finished session. Every game ships a leaderboard screen for the instance it runs in.

The full manifest reference and the kit API are in the repository guide at GUIDE.md.