Going multiplayer
Playing with other people over a network: servers, seats, prediction and lag.
A game is played by more than one person when its manager runs on node behind a socket and each player opens client.html. Every game yarn new-game writes already has both files.
Seats
- In the scene file,
games/<game>/<scene>.scene.json, raise"seats"to the number of players. A scene that lists the player plugin takes that many seats. - Add one
startactor per slot:{ "is": "start", "slot": 1, "pos": [x, y] }. Each connection gets the lowest free slot, and itsplayerrow is spawned with that slot inseat. - A rule that needs to know who is who reads the slot off the
playerrow. A page that reconnects is a new seat and may get the same slot.
A connection that finds every slot of the entry scene taken is refused. A scene that does not list player, such as a menu, takes any number of seats. When a connection closes, its player row ends and its slot is free again.
Running it
- From the repository root, run
node --experimental-strip-types games/<game>/<game>.server.ts. Run it from the root: the server reads the game's files by paths from there. Or, inyarn editor <game>, use the Node panel's Run on node, which restarts the server whenever a file it runs changes. - The port is
scenemanager.portin the manager scene file.yarn new-gamegives each game its own, starting at 9001. Only one server can hold a port. - Open
http://localhost:5173/games/<game>/client.htmlonce per player, each in its own window. Chrome pauses a tab that is not in front, so two tabs in one window do not both play.
index.html is the whole game in one tab with no server. A second tab of it is a second game, not a second player.
What each seat is sent
By default every seat is sent every row, and the other seats' player rows without their mine. Two pieces send less; each limits what a seat is sent, never what the rules know.
- Fields by owner. A top-level
securityblock in the scene file names, for each kind, the group of each field it lists, and theplayerprefab gets amaskslist. A listed field of an owned row is sent only to seats whose masks share the owner's group;meis the owner alone. See player. - Fog of war. Give the scene's
world.visionblock, read by vision. Setslotto"seat",atto the field holding the place ("pos"), andsightto{ "player": <cells> }. A row the seat's rules spawn for its team carriesseattoo. Kinds listed inalwaysare outside the fog. - To draw the fog, list the
visionplugin beforerender2din the scene'spluginsand giveworld.render2dwith the fog's layer among itslayers. Things the fog covers are drawn on a layer of that world.
What a seat may send
- A seat may run an act on its own
playerrow, or on a row whose kind lists the act inopen. Anything else is refused. - An act on the
playerrow that other kinds call to change it, such ashurtoraward, could be sent by the seat. Make it a plain function of a game plugin, or have the act return whenseat >= 0; a hook passes-1. - Every argument arrives from outside the program. Each act checks each argument's type, that a number is finite, and its range.
Running ahead of the server
- Give every scene a seat plays in a
world.predictblock:{ "epsilon": e, "cutoff": c, "halflife": h, "snap": s, "below": b }. The page then runs its own copy of the scene with predict, and the server puts a late press back on its step with rewind. The predict page says what each number does. - The seat's own thing and everything its press moves are drawn on the frame after the key. A sound raised
fromthe seat'splayerrow is heard then too. - A title or menu scene gives no
world.predict, and its presses are drawn a round trip later.
Chat and parties
chat and party need an account per seat (Persisting players) and draw on ui, listed after them in plugins. Chat with party or whisper talk needs chatbox: { channels: 'me' } in security; party needs partyseat: { account: 'me', invite: 'me' }.
Checking it
games/<game>/<game>.server.test.tsserves the manager and dials pages: each page is sent every seat'splayerrow, one page past the last seat is refused, and a closed seat frees its slot. Run it withyarn vitest runon that file.games/<game>/<game>.predict.test.tspresses on predicting pages over clean, home,bad wifi,mobileandawfulwires, and checks that the seat's thing takes every press by the next frame with no corrections.- Play bad networks on node with the simulator, or record a real browser session with
client.html?tap=<name>. - Play it: the server running and
client.htmlin two windows, each thing moving on the frame after its key and each player seeing the other move.