adevra/unity-multiplayer-agent-skills

unity-multiplayer-mobile-webgl

- Build real-time multiplayer games in Unity (C) targeting mobile and WebGL2 browsers using Colyseus or Nakama game servers with WebSocket transport.

Ver código-fonte
Documento original do Skill

Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.

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:

csharp
#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

LibraryCostWebGLKey strength
NativeWebSocketFreeBundled with Colyseus SDK, v2.x auto-dispatches to main thread
Best WebSockets~€18WSS, compression, profiler integration
unity-websocketFreeMonoBehaviour API, built-in ping/RTT, zero WebGL code changes
Unity Transport (UTP)FreeOfficial 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

GenrePatternWhat syncs
FPS / ActionClient-side prediction + server rewindState + input
MMO / RPGSnapshot interpolationState only
RTS / FightingDeterministic lockstep / rollbackInput only
Turn-basedRequest-responseActions only
Casual / SocialColyseus auto-sync or Nakama relayState or messages

→ Deep dive on each pattern: read references/architecture-patterns.md

Quick-start: Colyseus + Unity

Server (TypeScript):

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#):

csharp
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):

typescript
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#):

csharp
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:

  1. Reduce what you send — Delta compression, quantization, smallest-three quaternion encoding. Only sync changed data. Use NetworkVariable for persistent state, RPCs for events.
  2. Reduce how often you send — 20-30 ticks/second is optimal. Use interpolation to smooth gaps. Colyseus defaults to 20Hz patchRate.
  3. Reduce who receives — Interest management / area-of-interest filtering. Far entities get lower update rates or are culled entirely.
  4. Use binary serialization — MessagePack for C# is the best general choice (10-100x faster than JSON, compact). Avoid JSON in production hot paths.
  5. Pool everything — Object pooling for network messages, byte arrays, collections. WebGL GC runs only once per frame; mid-frame allocations risk OOM.
  6. 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:

FileWhen to read
references/architecture-patterns.mdChoosing network topology, state sync strategy, or ECS networking
references/colyseus-integration.mdSetting up Colyseus server, Unity SDK, rooms, schema, matchmaking
references/nakama-integration.mdSetting up Nakama server, Unity SDK, auth, multiplayer, storage
references/websocket-networking.mdChoosing/configuring WebSocket library, reconnection logic
references/webgl2-constraints.mdUnderstanding WebGL2 limitations, conditional compilation patterns
references/performance-optimization.mdNetwork bandwidth, serialization, GC, mobile battery, object pooling
references/deployment-guides.mdDocker, Nginx, SSL, Redis, scaling, managed hosting options

Asset templates index

FilePurpose
assets/colyseus-room-template.tsProduction-ready Colyseus room with auth, reconnection, clock sync
assets/nakama-match-handler-template.tsAuthoritative Nakama match with tick loop, presence tracking
assets/unity-colyseus-manager.csUnity singleton managing Colyseus connection, room join, state callbacks
assets/unity-nakama-manager.csUnity singleton managing Nakama client, socket, auth, match lifecycle
assets/unity-websocket-client.csStandalone WebSocket client with exponential backoff reconnection