| name | unity-multiplayer-mobile-webgl | ||||
|---|---|---|---|---|---|
| description | Build real-time multiplayer games in Unity (C#) targeting mobile and WebGL2 browsers using Colyseus or Nakama game servers with WebSocket transport. Covers client-server architecture patterns, state synchronization (prediction, interpolation, rollback), Schema-based serialization, room/match lifecycle, matchmaking, reconnection, and cross-platform deployment. Use this skill whenever building Unity multiplayer games, integrating Colyseus or Nakama, handling WebGL2 networking constraints, optimizing multiplayer performance for mobile or browser, choosing between game server frameworks, or deploying multiplayer backends. Also trigger when the user mentions real-time networking in Unity, WebSocket client setup, authoritative servers, lobby/room systems, or network state sync — even if they don't say "multiplayer" explicitly. | ||||
| license | Apache-2.0 | ||||
| metadata |
|
This skill guides you through building real-time multiplayer Unity games that ship to both mobile (iOS/Android) and WebGL2 browsers. The two primary server frameworks covered are Colyseus (automatic state sync, room-based) and Nakama (full game backend with auth, storage, matchmaking).
Pick Colyseus when:
- You need automatic state synchronization (schema-based binary deltas)
- Your game is primarily room-based (lobbies, matches, sessions)
- You want the fastest path to a working prototype
- You'll handle auth/storage/leaderboards yourself or don't need them
Pick Nakama when:
- You need built-in authentication (device, email, social, Steam)
- You need persistent storage, leaderboards, tournaments, friends, groups, chat
- You want a production-grade backend from day one
- You're comfortable with manual state management via opcodes
→ For Colyseus details: read references/colyseus-integration.md
→ For Nakama details: read references/nakama-integration.md
WebGL builds cannot use raw TCP/UDP sockets, cannot use C# threads, and cannot host servers. All networking must go through WebSocket (or WebRTC). In production, HTTPS pages require wss:// connections.
The standard cross-platform pattern:
#if UNITY_WEBGL && !UNITY_EDITOR
// WebSocket transport only — no UDP, no threads
m_Driver = NetworkDriver.Create(new WebSocketNetworkInterface());
#else
// Native platforms can use UDP for lower latency
m_Driver = NetworkDriver.Create(new UDPNetworkInterface());
#endif→ Full constraints and workarounds: read references/webgl2-constraints.md
| Library | Cost | WebGL | Key strength |
|---|---|---|---|
| NativeWebSocket | Free | ✅ | Bundled with Colyseus SDK, v2.x auto-dispatches to main thread |
| Best WebSockets | ~€18 | ✅ | WSS, compression, profiler integration |
| unity-websocket | Free | ✅ | MonoBehaviour API, built-in ping/RTT, zero WebGL code changes |
| Unity Transport (UTP) | Free | ✅ | Official Unity package, dual UDP+WebSocket listen |
Avoid WebSocketSharp — it uses System.Net.Sockets and does not work in WebGL builds.
→ Full comparison and code examples: read references/websocket-networking.md
| Genre | Pattern | What syncs |
|---|---|---|
| FPS / Action | Client-side prediction + server rewind | State + input |
| MMO / RPG | Snapshot interpolation | State only |
| RTS / Fighting | Deterministic lockstep / rollback | Input only |
| Turn-based | Request-response | Actions only |
| Casual / Social | Colyseus auto-sync or Nakama relay | State or messages |
→ Deep dive on each pattern: read references/architecture-patterns.md
Server (TypeScript):
import { defineServer, defineRoom, Schema, type, MapSchema } from "colyseus";
class Player extends Schema {
@type("number") x: number = 0;
@type("number") y: number = 0;
}
class GameState extends Schema {
@type({ map: Player }) players = new MapSchema<Player>();
}
class GameRoom {
onCreate() { this.setState(new GameState()); }
onJoin(client) {
this.state.players.set(client.sessionId, new Player());
}
onMessage(client, type, message) {
const player = this.state.players.get(client.sessionId);
if (type === "move") { player.x = message.x; player.y = message.y; }
}
onLeave(client) { this.state.players.delete(client.sessionId); }
}
defineServer({ rooms: { game: defineRoom(GameRoom) } });Unity Client (C#):
using Colyseus;
var client = new Client("ws://localhost:2567");
var room = await client.JoinOrCreate<GameState>("game");
// Listen to player additions
var callbacks = Callbacks.Get(room);
callbacks.OnAdd(state => state.players, (sessionId, player) => {
SpawnPlayer(sessionId, player);
callbacks.Listen(player, p => p.x, (val, prev) => MovePlayer(sessionId));
});
// Send input
await room.Send("move", new { x = transform.position.x, y = transform.position.z });→ Full Colyseus integration guide: references/colyseus-integration.md
→ Server template: assets/colyseus-room-template.ts
→ Client template: assets/unity-colyseus-manager.cs
Server (TypeScript runtime):
const matchInit: nkruntime.MatchInitFunction = (ctx, logger, nk, params) => {
return { state: { players: {}, tick: 0 }, tickRate: 20, label: "" };
};
const matchLoop: nkruntime.MatchLoopFunction = (ctx, logger, nk, dispatcher, tick, state, messages) => {
for (const msg of messages) {
const data = JSON.parse(new TextDecoder().decode(msg.data));
state.players[msg.sender.userId] = { x: data.x, y: data.y };
}
dispatcher.broadcastMessage(1, JSON.stringify(state.players));
return { state };
};Unity Client (C#):
using Nakama;
var client = new Client("http", "127.0.0.1", 7350, "defaultkey",
UnityWebRequestAdapter.Instance);
var session = await client.AuthenticateDeviceAsync(SystemInfo.deviceUniqueIdentifier);
var socket = client.NewSocket(useMainThread: true);
await socket.ConnectAsync(session);
var match = await socket.CreateMatchAsync();
// Send state
await socket.SendMatchStateAsync(match.Id, 0,
System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(position)));
// Receive state
socket.ReceivedMatchState += (matchState) => {
var data = System.Text.Encoding.UTF8.GetString(matchState.State);
ApplyState(matchState.UserPresence.UserId, JsonUtility.FromJson<Position>(data));
};→ Full Nakama integration guide: references/nakama-integration.md
→ Server template: assets/nakama-match-handler-template.ts
→ Client template: assets/unity-nakama-manager.cs
When optimizing for mobile + WebGL2, follow this priority order:
- Reduce what you send — Delta compression, quantization, smallest-three quaternion encoding. Only sync changed data. Use
NetworkVariablefor persistent state, RPCs for events. - Reduce how often you send — 20-30 ticks/second is optimal. Use interpolation to smooth gaps. Colyseus defaults to 20Hz patchRate.
- Reduce who receives — Interest management / area-of-interest filtering. Far entities get lower update rates or are culled entirely.
- Use binary serialization — MessagePack for C# is the best general choice (10-100x faster than JSON, compact). Avoid JSON in production hot paths.
- Pool everything — Object pooling for network messages, byte arrays, collections. WebGL GC runs only once per frame; mid-frame allocations risk OOM.
- Batch on mobile — Minimize cellular radio activations. Front-load transfers, avoid polling patterns. Offer 30 FPS option.
→ Detailed optimization guide: references/performance-optimization.md
Both Colyseus and Nakama deploy via Docker. For production:
- Nginx reverse proxy with WebSocket upgrade headers
- SSL/TLS required for WebGL clients (
wss://) - Colyseus scaling: Redis for presence + driver, PM2 in fork mode
- Nakama scaling: CockroachDB cluster, multi-node Nakama with gossip discovery
→ Full deployment guides: references/deployment-guides.md
Read these as needed — don't load all at once:
| File | When to read |
|---|---|
references/architecture-patterns.md |
Choosing network topology, state sync strategy, or ECS networking |
references/colyseus-integration.md |
Setting up Colyseus server, Unity SDK, rooms, schema, matchmaking |
references/nakama-integration.md |
Setting up Nakama server, Unity SDK, auth, multiplayer, storage |
references/websocket-networking.md |
Choosing/configuring WebSocket library, reconnection logic |
references/webgl2-constraints.md |
Understanding WebGL2 limitations, conditional compilation patterns |
references/performance-optimization.md |
Network bandwidth, serialization, GC, mobile battery, object pooling |
references/deployment-guides.md |
Docker, Nginx, SSL, Redis, scaling, managed hosting options |
| File | Purpose |
|---|---|
assets/colyseus-room-template.ts |
Production-ready Colyseus room with auth, reconnection, clock sync |
assets/nakama-match-handler-template.ts |
Authoritative Nakama match with tick loop, presence tracking |
assets/unity-colyseus-manager.cs |
Unity singleton managing Colyseus connection, room join, state callbacks |
assets/unity-nakama-manager.cs |
Unity singleton managing Nakama client, socket, auth, match lifecycle |
assets/unity-websocket-client.cs |
Standalone WebSocket client with exponential backoff reconnection |