Making a game

Designing a seat

The player row is the thing the seat plays as, never a row it spawns.

A seat possesses one row, its player row, and every act it sends is that row's. This page covers building that row, on its own or holding rows of other kinds.

Choosing the path

  1. If the seat plays one thing for its whole visit and nothing of it outlives the seat (a gun, a hero, a plane, a poker chair), the player row is that thing. Every field of the thing is a field of the player row.
  2. If the seat gets in and out of things (a vehicle, a mount, a turret, a crane), plays a row that must outlive the seat, or sometimes only watches, the player row is the driver or rider and takes hold of rows of other kinds with player's hold functions.

What a seat is sent

  • Every seat is sent every player row, which is how other seats see your car.
  • The seat's own row carries mine: true, which no other seat is sent. Find it with possessed(rows), never the first player row.
  • A field only its own seat may read, such as a hand of cards, is listed in the scene's security block, { "player": { "hole": "me" } }, with masks: [] on the player prefab.
  • The row's seat field is its slot. Find a start or anything else per player by me.data.seat.

The simple path

  1. Put every changing field of the thing on the player prefab (position, velocity, health, ammo, laps) and its fixed numbers in the player definition.
  2. In before, stand the row once on its slot's start actor and mark it placed. Never spawn a row for it.
  3. Run the thing in its hooks. A rule shared with another kind, such as a rival that drives like the seat's car, is a function of a game plugin each kind calls on its own row.
  4. Respawn by resetting the same row's fields.
  5. Give player an entry in any plugin table keyed by kind, such as world.box2d.kinds.

One thing, several modes

A thing that walks and swims, or is a fighter and a ghost, is one player row with a mode field. Each hook runs that mode's rule, each act does nothing in a mode it is not for, and the mode changes only in a rule or an act of the row.

Holding rows of other kinds

  1. Give the player prefab held, null when it holds nothing. The held row stands in the scene's actors or is made by another rule; the player row never spawns it.
  2. Write the act that takes hold: it checks what the game cares about (in reach, slow enough) and calls take(me, w, row), which refuses a row another player row holds. The same act calls letGo(me) to get out.
  3. Pass the controls on with relay({ box3dvehicle: ['pedal', 'steer', 'brake'] }) among the player acts. The held kind's control acts are never in its open.
  4. In the held kind's rule, ask holderOf(w, me) each step and rest its controls when it has no holder.
  5. Where the player row is a body, set its position from the held row every step while it holds.
  6. In the drawing, read holding(rows) each frame and switch the bound keys and the camera on the frame it changes.
import { holding, letGo, relay, take } from '#plugins/player/player.sim.ts'

export const player: Kind = {
  acts: {
    use(me, w) {
      const car = holding(w, me)
      if (car) { if (slowEnough(car, w)) letGo(me); return }
      const it = nearest(me, w)
      if (it) take(me, w, it)
    },
    ...relay({ box3dvehicle: ['pedal', 'steer', 'brake'] }),
  },
}

slowEnough and nearest are the game's own functions.

Acts a seat can send

A seat may send any act its player row has. An act other kinds call to change the row (hurt, award, heal) does nothing when a seat sent it, if (seat >= 0) return, or is a game plugin's function instead. Every act a seat does send checks its arguments' type, finiteness and range.

The drawing

player.client.ts is loaded once for every player row the page is sent. It draws the thing for every row, and binds keys and moves the camera only for the row that is mine.

let ours = false
export function setup(row: Row, plugins: Plugins) {
  // the model, for every seat's row
  ours = row.data.mine === true
  if (ours) plugins.player.bind({ ... })
}
export function remove(plugins: Plugins) {
  // drop the model
  if (ours) plugins.player.unbind()
}

The scene sets world.predict and client.html wraps its link in predict, so the seat's own thing moves on the frame after its key.

Checking it

  1. Run the server and open client.html: press a key and your thing moves on the next frame.
  2. Open a second client.html: each seat sees the other's thing, and each one's keys move only its own.
  3. For a held row: getting in, driving and getting out work, and the row rests the step after it is let go or its seat leaves.