Unity Multiplayer for Mobile & WebGL2
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).
Decision tree — start here
1. Choose your server framework
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
2. Understand the WebGL2 hard constraints
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
3. Choose your WebSocket library
| 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
4. Pick your architecture pattern
| 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
Quick-start: Colyseus + Unity
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
Quick-start: Nakama + Unity
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
Performance checklist
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
NetworkVariable for 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
Deployment overview
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
Reference files index
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 |
Asset templates index
| 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 |
1---2name: unity-multiplayer-mobile-webgl3description: 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.4license: Apache-2.05---67# Unity Multiplayer for Mobile & WebGL289This 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).1011## Decision tree — start here1213### 1. Choose your server framework1415Pick **Colyseus** when:16- You need automatic state synchronization (schema-based binary deltas)17- Your game is primarily room-based (lobbies, matches, sessions)18- You want the fastest path to a working prototype19- You'll handle auth/storage/leaderboards yourself or don't need them2021Pick **Nakama** when:22- You need built-in authentication (device, email, social, Steam)23- You need persistent storage, leaderboards, tournaments, friends, groups, chat24- You want a production-grade backend from day one25- You're comfortable with manual state management via opcodes2627→ For Colyseus details: read `references/colyseus-integration.md`28→ For Nakama details: read `references/nakama-integration.md`2930### 2. Understand the WebGL2 hard constraints3132WebGL 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.3334The standard cross-platform pattern:35```csharp36#if UNITY_WEBGL && !UNITY_EDITOR37 // WebSocket transport only — no UDP, no threads38 m_Driver = NetworkDriver.Create(new WebSocketNetworkInterface());39#else40 // Native platforms can use UDP for lower latency41 m_Driver = NetworkDriver.Create(new UDPNetworkInterface());42#endif43```4445→ Full constraints and workarounds: read `references/webgl2-constraints.md`4647### 3. Choose your WebSocket library4849| Library | Cost | WebGL | Key strength |50|---------|------|-------|--------------|51| **NativeWebSocket** | Free | ✅ | Bundled with Colyseus SDK, v2.x auto-dispatches to main thread |52| **Best WebSockets** | ~€18 | ✅ | WSS, compression, profiler integration |53| **unity-websocket** | Free | ✅ | MonoBehaviour API, built-in ping/RTT, zero WebGL code changes |54| **Unity Transport (UTP)** | Free | ✅ | Official Unity package, dual UDP+WebSocket listen |5556Avoid **WebSocketSharp** — it uses `System.Net.Sockets` and does not work in WebGL builds.5758→ Full comparison and code examples: read `references/websocket-networking.md`5960### 4. Pick your architecture pattern6162| Genre | Pattern | What syncs |63|-------|---------|-----------|64| FPS / Action | Client-side prediction + server rewind | State + input |65| MMO / RPG | Snapshot interpolation | State only |66| RTS / Fighting | Deterministic lockstep / rollback | Input only |67| Turn-based | Request-response | Actions only |68| Casual / Social | Colyseus auto-sync or Nakama relay | State or messages |6970→ Deep dive on each pattern: read `references/architecture-patterns.md`7172## Quick-start: Colyseus + Unity7374**Server (TypeScript):**75```typescript76import { defineServer, defineRoom, Schema, type, MapSchema } from "colyseus";7778class Player extends Schema {79 @type("number") x: number = 0;80 @type("number") y: number = 0;81}8283class GameState extends Schema {84 @type({ map: Player }) players = new MapSchema<Player>();85}8687class GameRoom {88 onCreate() { this.setState(new GameState()); }89 onJoin(client) {90 this.state.players.set(client.sessionId, new Player());91 }92 onMessage(client, type, message) {93 const player = this.state.players.get(client.sessionId);94 if (type === "move") { player.x = message.x; player.y = message.y; }95 }96 onLeave(client) { this.state.players.delete(client.sessionId); }97}9899defineServer({ rooms: { game: defineRoom(GameRoom) } });100```101102**Unity Client (C#):**103```csharp104using Colyseus;105106var client = new Client("ws://localhost:2567");107var room = await client.JoinOrCreate<GameState>("game");108109// Listen to player additions110var callbacks = Callbacks.Get(room);111callbacks.OnAdd(state => state.players, (sessionId, player) => {112 SpawnPlayer(sessionId, player);113 callbacks.Listen(player, p => p.x, (val, prev) => MovePlayer(sessionId));114});115116// Send input117await room.Send("move", new { x = transform.position.x, y = transform.position.z });118```119120→ Full Colyseus integration guide: `references/colyseus-integration.md`121→ Server template: `assets/colyseus-room-template.ts`122→ Client template: `assets/unity-colyseus-manager.cs`123124## Quick-start: Nakama + Unity125126**Server (TypeScript runtime):**127```typescript128const matchInit: nkruntime.MatchInitFunction = (ctx, logger, nk, params) => {129 return { state: { players: {}, tick: 0 }, tickRate: 20, label: "" };130};131132const matchLoop: nkruntime.MatchLoopFunction = (ctx, logger, nk, dispatcher, tick, state, messages) => {133 for (const msg of messages) {134 const data = JSON.parse(new TextDecoder().decode(msg.data));135 state.players[msg.sender.userId] = { x: data.x, y: data.y };136 }137 dispatcher.broadcastMessage(1, JSON.stringify(state.players));138 return { state };139};140```141142**Unity Client (C#):**143```csharp144using Nakama;145146var client = new Client("http", "127.0.0.1", 7350, "defaultkey",147 UnityWebRequestAdapter.Instance);148var session = await client.AuthenticateDeviceAsync(SystemInfo.deviceUniqueIdentifier);149150var socket = client.NewSocket(useMainThread: true);151await socket.ConnectAsync(session);152var match = await socket.CreateMatchAsync();153154// Send state155await socket.SendMatchStateAsync(match.Id, 0,156 System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(position)));157158// Receive state159socket.ReceivedMatchState += (matchState) => {160 var data = System.Text.Encoding.UTF8.GetString(matchState.State);161 ApplyState(matchState.UserPresence.UserId, JsonUtility.FromJson<Position>(data));162};163```164165→ Full Nakama integration guide: `references/nakama-integration.md`166→ Server template: `assets/nakama-match-handler-template.ts`167→ Client template: `assets/unity-nakama-manager.cs`168169## Performance checklist170171When optimizing for mobile + WebGL2, follow this priority order:1721731. **Reduce what you send** — Delta compression, quantization, smallest-three quaternion encoding. Only sync changed data. Use `NetworkVariable` for persistent state, RPCs for events.1742. **Reduce how often you send** — 20-30 ticks/second is optimal. Use interpolation to smooth gaps. Colyseus defaults to 20Hz patchRate.1753. **Reduce who receives** — Interest management / area-of-interest filtering. Far entities get lower update rates or are culled entirely.1764. **Use binary serialization** — MessagePack for C# is the best general choice (10-100x faster than JSON, compact). Avoid JSON in production hot paths.1775. **Pool everything** — Object pooling for network messages, byte arrays, collections. WebGL GC runs only once per frame; mid-frame allocations risk OOM.1786. **Batch on mobile** — Minimize cellular radio activations. Front-load transfers, avoid polling patterns. Offer 30 FPS option.179180→ Detailed optimization guide: `references/performance-optimization.md`181182## Deployment overview183184Both Colyseus and Nakama deploy via Docker. For production:185186- **Nginx** reverse proxy with WebSocket upgrade headers187- **SSL/TLS** required for WebGL clients (`wss://`)188- **Colyseus scaling**: Redis for presence + driver, PM2 in fork mode189- **Nakama scaling**: CockroachDB cluster, multi-node Nakama with gossip discovery190191→ Full deployment guides: `references/deployment-guides.md`192193## Reference files index194195Read these as needed — don't load all at once:196197| File | When to read |198|------|-------------|199| `references/architecture-patterns.md` | Choosing network topology, state sync strategy, or ECS networking |200| `references/colyseus-integration.md` | Setting up Colyseus server, Unity SDK, rooms, schema, matchmaking |201| `references/nakama-integration.md` | Setting up Nakama server, Unity SDK, auth, multiplayer, storage |202| `references/websocket-networking.md` | Choosing/configuring WebSocket library, reconnection logic |203| `references/webgl2-constraints.md` | Understanding WebGL2 limitations, conditional compilation patterns |204| `references/performance-optimization.md` | Network bandwidth, serialization, GC, mobile battery, object pooling |205| `references/deployment-guides.md` | Docker, Nginx, SSL, Redis, scaling, managed hosting options |206207## Asset templates index208209| File | Purpose |210|------|---------|211| `assets/colyseus-room-template.ts` | Production-ready Colyseus room with auth, reconnection, clock sync |212| `assets/nakama-match-handler-template.ts` | Authoritative Nakama match with tick loop, presence tracking |213| `assets/unity-colyseus-manager.cs` | Unity singleton managing Colyseus connection, room join, state callbacks |214| `assets/unity-nakama-manager.cs` | Unity singleton managing Nakama client, socket, auth, match lifecycle |215| `assets/unity-websocket-client.cs` | Standalone WebSocket client with exponential backoff reconnection |