Porting a game
A game that exists elsewhere, brought into Patchwork and proven to play the same.
A port is a new game made in Patchwork with an old one as its specification. Each part of the old game is read for what it does and built again in its home.
Read the old game first
Write down its constants with their units, its state and what changes it, its update in order, what it draws and from which anchor point, its keys, its screens, every Math.random, its timers, its files, what it saves, and its libraries. Measure the frame rate it was played at.
What goes where
| Old game | Port |
|---|---|
| The game loop | Not ported: the pages step the manager |
| The update function | The before, tick and after of the kind whose state each statement changes, in the old order |
| The thing the player controls, its score and lives | The seat's player row |
| State that changes | Fields of a row, starting values in the prefab |
| Constants | The kind's definition in assets/<game>.json, per second |
| Canvas size | prefabs.engine.width and height |
| Where the player starts | A start actor per slot |
| A loop that lays out the level | Run once, its result written as actors entries |
| Key handlers | The table player.client.ts hands the player plugin |
| The draw function | <kind>.client.ts, and the ui plugin for text over the game |
| Images and sounds | loader catalogue entries; sounds raised with audio's sound |
| A title or a next level | A scene |
| A game-over or pause screen | A kind with one actors entry, shown on a ui modal while its row says so |
reset() | An act that ends the cast and spawns it again from the scene's entries |
Math.random() | w.die(), with a seed in the scene's world |
Math.sin, pow, ** | The same call on math |
| A timer or frame counter | A field on the row counted down by dt |
| A physics library | A physics plugin such as box2d |
A speed of 4 pixels a frame, at 60 frames a second, is 240 pixels a second. A canvas rectangle is drawn from its top-left corner, while a row's pos is the thing's middle, so add half the size as each position moves. A kind is never named after the game, a scene or a plugin.
The work in order
- Read the old game and write the list above.
- Run
yarn new-game <name>and its test,yarn vitest run games/<name>/<name>.sim.test.ts. Set the canvas size and place slot 0'sstart. - Fold the generated
bodyinto theplayerrow, and list every kind inmain.sim.ts'ssim. - Write the numbers: definitions, prefabs,
world.seed, the level inassets/main.actors.json, andworld.predict. - Write each kind's rule, keeping the old update's order across
before,tickandafter. Code several kinds share, such as a touch test, goes in a plugin of the game's own. - Bind the keys in
player.client.ts. - Write the looks, and the HUD on the ui plugin.
- Write the screens and starting over.
- Add files and sounds to the catalogue.
- Build anything Patchwork lacks in its home first, as Building a plugin describes.
- Write the tests beside every file and run
yarn tsc. - Run the old game and the port side by side.
Checking it
- Each kind's
<kind>.sim.test.ts, the press test<name>.predict.test.ts, and a replay test under the recorder pass, run one file at a time withyarn vitest run games/<name>/<file>. - A script outside the game folder runs the old game headless, with
Math.randomreplaced by the scene's seeded die, and the port frame for frame from the same keys, and prints the first frame where positions, score, lives or state differ. A rule in the wrong pass shows here. - Both are played in Chrome with the same keys, and the port on
client.htmlagainst its server. - Every difference found is the game's maker's to accept or fix. A fixed seed means every run deals the same rolls, where the old game varied.