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 is | Home |
|---|---|
| A value only the page uses, such as a volume or a theme | A key of the store |
| Used by one game only | games/<game>/plugins/<p>/ or games/<game>/addons/<a>/ |
| Used by a second game, living in the world or the browser | plugins/<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
- 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. - Write the server half,
<p>.sim.ts, when it has rules. It reads what changes off rows and fixed numbers offw.world.<p>, writes only the row it is handed, writes arrays whole, and computes with math. - 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 oftick,afterTickandclose. Everything it makes is let go inclose. - In the scene file, put its fixed numbers in
world.<p>, add it topluginsin the order its header asks, and when it has a row, name its kind in the scene'ssim, give it a prefab of changing fields only, and anactorsentry. - Write a test beside each file and run each with
yarn vitest run <file>, thenyarn tsc. - Play it in
index.htmland onclient.htmlagainst 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": ["<p>"] },
"<p>": { "mods": { "<function>": "<a>" } }
},
"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
- Plugin
pdeclares the function type it wants and asksmod(world, '<p>', '<function>'), ormodsOffor several providers. - Plugin
aprovides it in<a>.<p>.ts: besideawhen both are top-level orais the game's, besidepin the game when onlypis the game's. - The scene switches it on with
world.<p>.mods: { "<function>": "<a>" }. p's header says what it does with no mod named. A browser half checks the provider is listed withprovided, or listed ahead of it withahead.
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.tsboots a scene the test writes itself, drives rows and reads what the plugin wrote.<p>.plugin.test.tsmakes a real page over the loopback and reads what the plugin made.<a>.<p>.test.tsloads a scene that names the mod and checks what it returns.<a>.addon.test.tsattaches the addon, steps, and reads what each seat is sent.yarn tscshows no error in the files written.