Making a game

Bringing in art

Pictures, sheets, models and fonts into a game and drawn.

Every file a game draws is an entry in its scene's catalogue, world.loader.assets, fetched and decoded by the loader. Code names the entry, never the file's path.

The types

TypeFor
jsonA data file a rule reads, the definitions among them.
sheetAn Aseprite or TexturePacker sheet, or a Tiled .tsj: frames and clips.
textureOne picture, decoded by the scene's renderer.
modelOne self-contained .glb, under render3d.
bitmapfontAn AngelCode .fnt and its pages.
paletteA GIMP .gpl.
pixels, binaryA PNG or bytes a rule reads as data.

Steps

  1. Put the file in games/<game>/assets/, with the script that draws, writes or cuts it kept beside it so it can be made again.
  2. A model or texture the game ships is built, never raw: a glb packed by gltfpack, with meshopt geometry and WebP textures, and a loose texture as a .webp. The raw parts it is made from, source glbs, full-size texture sets and packs, live in the library that PATCHWORK_LIBRARY names, never in the game. The game's games/<game>/assets/<game>.bake.ts makes every built file from the library and the scripts, and yarn bake <game> runs it once yarn health has checked the machine. Node and material names, extras and float positions survive the packing, but a glTF mesh's own name does not, so a mesh found by name is a node of one primitive. The loader reads both with no setup.
  3. Write its entry: type, a url from the repository root with a leading /, preload when a rule reads it on its first step, and the type's own fields (nearest for pixel art on a sheet, facing, palette, for, and space, wrap and anisotropy on a 3D texture).
  4. Give a sheet's frames a pivot, the point that stands on the row's pos; a frame with none stands on its top-left corner.
  5. Name the entry where its drawer reads it: the kind's definition in assets/<game>.json, a plugin's table on world, or the row when it changes.
  6. Draw it with a plugin, or with the kind's own <kind>.client.ts (below).
  7. For a picture the HUD shows, add a second entry with the same url and "for": "ui".
"loader": { "assets": {
  "hero": { "type": "sheet", "url": "/games/&lt;game&gt;/assets/hero.json", "preload": true, "nearest": true },
  "coin.ui": { "type": "texture", "url": "/games/&lt;game&gt;/assets/coin.png", "for": "ui", "preload": true }
} }

Drawing with a plugin

A kind listed in a plugin's table needs no <kind>.client.ts.

  • sprite2d: world.sprite2d.kinds.<kind>, frames of a sheet in render2d's world. The kind's rule moves frame on with play.
  • sprite3d: billboards of a sheet in render3d.
  • anim: world.anim.kinds.<kind>, a model posed by clips from a rig file.
  • particle2d and particle3d: sheets named in an effects file.

Drawing with the kind's own look

  1. In setup, make the sprite or mesh and keep it in a variable at the top of the file.
  2. In tick, ask plugins.loader.get(name), and plugins.loader.load(name) while it hands back nothing. Size a PixiJS sprite with width and height.
  3. For a model, call plugins.loader.model(name) once; each call hands back its own copy.
  4. In remove, destroy the sprite or mesh and leave the texture, which is the loader's.

Checking it

  1. Test the look in <kind>.client.test.ts: the url asked for against the literal path, the size and anchor from the definition.
  2. Open the game's index.html as in Starting a game. The picture is drawn where its row says, at its size, sharp where it should be, and a clip plays at its pace.
  3. A blank page is a preloaded data file that failed, so the scene never started; missing art on a running page is a picture, model or font that failed. The browser's console names the entry and why, as loader <name>: ….