Making a game

Building a plugin

A plugin, an addon or a mod, top-level or a game's own.

This covers a plugin, an addon or a mod: deciding its home, writing its server and browser halves, setting it from the scene file, and testing it.

Which home

It isHome
A value only the page uses, such as a volume or a themeA key of the store
Used by one game onlygames/<game>/plugins/<p>/ or games/<game>/addons/<a>/
Used by a second game, living in the world or the browserplugins/<p>/
Used by a second game, holding a truth (seats, sockets, files, timers)addons/<a>/

Nothing goes in engine/. A setting only one game would set belongs to that game's kind, not to a plugin.

Writing a plugin

  1. Write the header at the top of each file: every field of world.<p> with its unit, its row's fields, every function and what it returns, each mod it asks for, and where it must be listed.
  2. Write the server half, <p>.sim.ts, when it has rules. It reads what changes off rows and fixed numbers off w.world.<p>, writes only the row it is handed, writes arrays whole, and computes with math.
  3. Write the browser half, <p>.plugin.ts, when it draws, listens or plays. It exports <p>() with no arguments, keeps its state inside that function, and returns any of tick, afterTick and close. Everything it makes is let go in close.
  4. In the scene file, put its fixed numbers in world.<p>, add it to plugins in the order its header asks, and when it has a row, name its kind in the scene's sim, give it a prefab of changing fields only, and an actors entry.
  5. Write a test beside each file and run each with yarn vitest run <file>, then yarn tsc.
  6. Play it in index.html and on client.html against the game's server.

A game's own plugin

It is laid out the same in games/<game>/plugins/<p>/. Its browser half is loaded by shipped: list shipped in plugins and name the plugin in world.shipped.plugins. It has no numbers of its own; it reads them off the definition of the kind whose row it is handed. Its only world entry is world.<p>.mods. An actor uses it from tick, never setup.

"world": {
  "shipped": { "plugins": ["&lt;p&gt;"] },
  "&lt;p&gt;": { "mods": { "&lt;function&gt;": "&lt;a&gt;" } }
},
"plugins": ["loader", "scenemanager", "player", "shipped", "render2d"]

Rows and ledgers

A plugin has a row only when its server half has state that changes, and the prefab holds only those fields. Fixed data never goes in prefabs. A row that only appends events for the page to play or draw once is a ledger, and its header says so.

Wrapping a library

A library's own memory, such as a physics world, is kept one per truth in a WeakMap keyed by the plugin's row, built from the rows when none exists, and stepped each step with the results written back onto the rows. Every file it reads is a loader catalogue entry. box3d and box2d are built this way.

Mods

  1. Plugin p declares the function type it wants and asks mod(world, '<p>', '<function>'), or modsOf for several providers.
  2. Plugin a provides it in <a>.<p>.ts: beside a when both are top-level or a is the game's, beside p in the game when only p is the game's.
  3. The scene switches it on with world.<p>.mods: { "<function>": "<a>" }.
  4. p's header says what it does with no mod named. A browser half checks the provider is listed with provided, or listed ahead of it with ahead.

Addons

An addon is addons/<a>/<a>.addon.ts. It attaches to a truth with truth.attach(piece) or truth.onStep(f), keeps its state in a WeakMap keyed by the truth, and reads its numbers from world.<a> or a top-level block of the scene file.

Checking it

  • <p>.sim.test.ts boots a scene the test writes itself, drives rows and reads what the plugin wrote.
  • <p>.plugin.test.ts makes a real page over the loopback and reads what the plugin made.
  • <a>.<p>.test.ts loads a scene that names the mod and checks what it returns.
  • <a>.addon.test.ts attaches the addon, steps, and reads what each seat is sent.
  • yarn tsc shows no error in the files written.