Skip to content

Networking

vgai has no networking wrapper: client code calls Colyseus directly. vgai owns no replication layer of its own — Colyseus is the replication layer — so wrapping it would just be a pass-through the editor never exploited. @colyseus/sdk is dynamically imported (await import('@colyseus/sdk')) so single-player game bundles don’t pull in multiplayer code.

Server-authoritative state syncs down via the Callbacks API; clients send input up
Server-authoritative sync.
const { Client, Callbacks } = await import('@colyseus/sdk');
const client = new Client(serverUrl);
const room = await client.joinOrCreate<ArenaRoomState>('arena_room', joinOptions);

ArenaRoomState types room.state and, through it, every Callbacks-API field below. The server’s Schema classes use @colyseus/schema’s legacy decorators (a server-only tsconfig flag) — the client shouldn’t import them as values. Instead, mirror the server schema’s SHAPE as an ambient, type-only declaration (ships zero runtime code):

import type { MapSchema, Schema } from '@colyseus/schema';
declare class ArenaPlayerSchema extends Schema {
sessionId: string;
x: number; y: number; z: number;
score: number;
}
declare class ArenaRoomState extends Schema {
players: MapSchema<ArenaPlayerSchema>;
phase: string;
}

third-person-arena/network.ts is the worked reference for this pattern.

Callbacks.get(room) returns the Colyseus v0.17 Callbacks API:

  • onAdd(collection, handler) — fires for existing AND future entries in a keyed collection (e.g. players).
  • onRemove(collection, handler) — fires when an entry leaves.
  • onChange(item, handler) — fires when any field of a replicated item changes.
  • listen(property, handler) — per-field listener, for root-level (non-collection) fields like phase or a timer.
  • bindTo(fromSchemaInstance, toPlainObject, properties?) — mirrors named fields onto a plain object immediately and on every change, so you don’t hand-write a per-field copy loop when you just need a snapshot (e.g. for a React HUD).

The typical shape: clients send() input up; the server room mutates authoritative @colyseus/schema state; that state syncs down and your onAdd / onChange / listen handlers update the local scene. Defining a room and connecting a client is walked through in the Multiplayer guide.

The editor never imports Colyseus. It asks a mounted game’s NetworkingAdapter (SystemAdapters.networking) for connection state, room info, and replication stats — createColyseusNetworkingAdapter builds the first-party implementation from accessor callbacks over your game’s live network client. See third-person-arena/network-adapter.ts for the worked example, and the play panel’s network inspector section.