agentleFS
Ar9avAr9av/game-exa/skills/multiplayer/SKILL.md

multiplayer

Skill4 starsChanged 7 months agoMITView on GitHub

Adds optional multiplayer to any generated Phaser 3 game. Supports three tiers — local (two gamepads/keyboards, no server), LAN (Colyseus authoritative server, same network), and online (Colyseus + TURN/STUN relay). Optional React/Redux lobby and PeerJS voice/video chat. Trigger only when the user explicitly requests multiplayer ("make it 2-player", "add online co-op", "--multiplayer" flag).

  • Reads credentials
  • Installs packages

5 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not activate automatically for single-player games.

    line 18

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Multiplayer — Optional Networked Play for Phaser 3 Games

Adds network play to any game already generated by the gameforge pipeline. The skill is additive and optional — it patches existing files minimally and creates new files under server/ and src/net/. It never rewrites src/scenes/Game.js from scratch; it injects hooks.

When to use

Trigger on any of:

  • Explicit multiplayer request in the prompt ("2-player", "co-op", "online", "multiplayer")
  • --multiplayer CLI flag passed to gameforge generate
  • --local, --lan, or --online tier flags
  • "add a lobby", "add voice chat" (implies multiplayer)

Do not activate automatically for single-player games. Always confirm tier with the user if ambiguous (local vs LAN vs online costs differ significantly in deployment complexity).

Three tiers

Tier 1 — Local only (no server)

Two players on the same machine or two gamepads/keyboards. No network code, no Colyseus. Player 2 input uses WASD (if P1 uses arrows) or gamepad index 1. State is shared in the same Phaser scene. Fastest to implement; zero infrastructure.

Trigger: --local or "same screen" or "splitscreen" in the prompt.

What gets added:

  • A second player sprite spawned from the same entity pool as P1
  • P2 input wiring (WASD or gamepad 1)
  • Optional split-screen camera (two Phaser cameras, each following one player)
  • Shared HUD showing both HP/score

Tier 2 — LAN (Colyseus, same network)

Authoritative Colyseus server running locally. Both clients connect over WebSocket to ws://localhost:2567. No TURN/STUN needed — NAT traversal assumed handled by router or VPN. Suitable for game jams, LAN parties.

Trigger: --lan or "local network" or "same WiFi" in the prompt.

What gets added:

  • server/ directory with Colyseus server (TypeScript, compiled to server/dist/)
  • src/net/ColyseusClient.js — connects, joins room, forwards input, applies server state
  • src/net/RemotePlayer.js — interpolated sprite for each networked peer
  • patch_game.mjs injects net hooks into src/scenes/Game.js

Tier 3 — Online (Colyseus + TURN/STUN)

Full internet play. Colyseus server hosted on a public URL. ICE servers (TURN/STUN) configured for WebRTC signaling via PeerJS voice/video. Requires a .env with VITE_COLYSEUS_URL and optionally VITE_PEER_HOST.

Trigger: --online or "internet", "play with friends online" in the prompt.

What gets added:

  • Everything in Tier 2
  • .env.example with VITE_COLYSEUS_URL, VITE_PEER_HOST, VITE_PEER_PORT, VITE_PEER_PATH
  • ICE server configuration in ColyseusClient.js
  • Optionally: src/net/VoiceChat.js (PeerJS, triggered by --voice)
  • Optionally: lobby/ React app (Vite + React + Redux, triggered by --lobby)

Player session schema

Each connected player carries this state on the server (authoritative) and mirrored to all clients:

// src/schemas/GameState.ts (server-side)
export class Player extends Schema {
  @type('string')  sessionId: string = '';
  @type('string')  name: string = 'Player';
  @type('number')  x: number = 0;
  @type('number')  y: number = 0;
  @type('number')  hp: number = 3;
  @type('number')  score: number = 0;
  @type('boolean') left: boolean = false;
  @type('boolean') right: boolean = false;
  @type('boolean') up: boolean = false;
  @type('boolean') down: boolean = false;
  @type('boolean') action: boolean = false;
}

input fields (left, right, up, down, action) are written by the client and consumed by the server simulation loop. Position, HP, and score are written by the server and read by clients.

How the host agent should invoke the scripts

Step 1 — init server (Tier 2 or 3 only)

node skills/multiplayer/scripts/init_server.mjs <project-dir> [--port 2567] [--voice] [--lobby]

This reads game-state.json, creates server/ and src/net/, and optionally lobby/.

Options:

  • --port N — Colyseus WebSocket port (default: 2567)
  • --voice — also create src/net/VoiceChat.js (PeerJS)
  • --lobby — also scaffold lobby/ React app

Step 2 — patch Game.js

node skills/multiplayer/scripts/patch_game.mjs <project-dir>

Reads existing src/scenes/Game.js, injects ColyseusClient and RemotePlayer hooks, writes the patched file back. Genre-aware (platformer vs top-down vs shooter).

Step 3 — start server + client

# Terminal 1 — Colyseus server
cd <project-dir>/server && npm install && npm run dev

# Terminal 2 — Phaser client
cd <project-dir> && npm run dev

For local-only tier, skip Step 1 and Step 3 server start. patch_game.mjs --local adds P2 input only.

File structure created

<project>/
├── server/                          # Colyseus authoritative server (Tier 2+)
│   ├── package.json                 # colyseus, @colyseus/monitor, typescript
│   ├── tsconfig.json
│   └── src/
│       ├── index.ts                 # HTTP + WS bootstrap, /colyseus monitor
│       ├── rooms/
│       │   └── GameRoom.ts          # Room<GameState>, 20 Hz sim loop
│       └── schemas/
│           └── GameState.ts         # MapSchema<Player> + Player schema
├── src/
│   └── net/                         # Phaser client networking
│       ├── ColyseusClient.js        # Colyseus.Client, room join, state sync
│       ├── RemotePlayer.js          # Phaser sprite + linear interpolation
│       └── VoiceChat.js             # PeerJS voice/video (--voice only)
└── lobby/                           # React matchmaking UI (--lobby only)
    ├── package.json                 # react, react-redux, @reduxjs/toolkit, vite
    ├── vite.config.ts
    └── src/
        ├── main.tsx
        ├── store.ts                 # Redux store
        ├── slices/
        │   └── lobbySlice.ts        # rooms list, player name, join status
        └── components/
            ├── App.tsx
            ├── RoomList.tsx
            └── PlayerSetup.tsx

How Game.js is patched

patch_game.mjs performs three minimal injections into the existing Game.js:

Injection 1 — imports (top of file)

import ColyseusClient from '../net/ColyseusClient.js';
import RemotePlayer from '../net/RemotePlayer.js';

Injection 2 — end of create()

Inserted just before this.events.emit('scene-ready'):

// --- multiplayer net ---
this._net = new ColyseusClient(this);
this._remotePlayers = new Map();
this._net.onStateChange((state) => this._syncFromServer(state));
// --- end multiplayer net ---

Injection 3 — end of update()

Inserted at the end of the update() body:

if (this._net) this._net.sendInput(this._collectInput());

Injection 4 — new methods appended to class body

_collectInput() {
  return {
    left:   this.cursors.left.isDown  || this.keys?.A?.isDown,
    right:  this.cursors.right.isDown || this.keys?.D?.isDown,
    up:     this.cursors.up.isDown    || this.keys?.W?.isDown,
    down:   this.cursors.down.isDown  || this.keys?.S?.isDown,
    action: this.keys?.SPACE?.isDown  || false,
  };
}

_syncFromServer(serverState) {
  const myId = this._net.sessionId;
  serverState.players.forEach((player, sessionId) => {
    if (sessionId === myId) return; // local player moved by physics
    if (!this._remotePlayers.has(sessionId)) {
      const rp = new RemotePlayer(this, player.x, player.y, player.name);
      this._remotePlayers.set(sessionId, rp);
    } else {
      this._remotePlayers.get(sessionId).syncFrom(player);
    }
  });
  // destroy departed players
  this._remotePlayers.forEach((rp, id) => {
    if (!serverState.players.has(id)) { rp.destroy(); this._remotePlayers.delete(id); }
  });
}

The patch preserves all existing logic; it never removes lines.

PeerJS voice/video layer (--voice)

src/net/VoiceChat.js provides optional peer-to-peer voice and video chat between players. It uses PeerJS as the signaling and connection layer on top of WebRTC.

Activation: pass --voice to init_server.mjs. The Phaser scene calls VoiceChat.init(sessionId, peersArray) after the Colyseus room join is confirmed.

The Colyseus room broadcasts a peer-joined message when a new player arrives. Existing players call voiceChat.call(newPeerId). The new player auto-answers all incoming calls. Video elements are rendered via this.add.dom() in a Phaser scene or mounted to a separate <div> overlay above the canvas.

See references/peerjs-video.md for implementation details.

React lobby (--lobby)

lobby/ is a standalone Vite React app that handles pre-game matchmaking:

  1. Player sets their name.
  2. App fetches room list from http://localhost:2567/matchmake/lobby (Colyseus HTTP API).
  3. Player creates or joins a room.
  4. On join success, the lobby redirects to the Phaser game URL with ?room=<roomId>&session=<sessionId> query params.
  5. ColyseusClient.js reads those params to rejoin the already-assigned room.

Redux store shape:

{ lobby: { playerName, rooms, status, error, roomId, sessionId } }

See references/colyseus-schema.md for the Colyseus HTTP matchmaking API details.

Constraints

  • Max 4 players per room (set in GameRoom.ts maxClients = 4).
  • Tick rate: 20 Hz — this.clock.setInterval(this._simulate.bind(this), 50) in GameRoom.ts.
  • State sync: every 50 ms — Colyseus default patch interval; do not lower below 50ms for browser games.
  • Input authority: clients send input; server computes position. Clients show server position for remote players (no client-side prediction for simplicity — add it in refiner if needed).
  • Local player: client still moves the local player sprite via Phaser physics for zero-latency feel, but server position reconciles every sync (smooth toward server pos if delta > 8px).
  • No split-screen in Tier 2+: with network players, each client renders only their own player via Phaser physics and all remote players via RemotePlayer sprites.
  • ESM throughout: ColyseusClient.js, RemotePlayer.js, VoiceChat.js are all ES modules.
  • TypeScript on server only: the Phaser client remains vanilla JS (matching codesmith convention). Server code is TypeScript compiled with tsc.

References

  • references/colyseus-schema.md — Colyseus Schema decorators, MapSchema, state listeners
  • references/peerjs-video.md — PeerJS API, getUserMedia, video in Phaser

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.