MonoGame SDK
Install package
Add the Colyseus.MonoGame NuGet package to your project:
dotnet add package Colyseus.MonoGameQuick Example
This example shows how to connect to a room, listen for state changes via Callbacks, and send messages.
using Microsoft.Xna.Framework;
using Colyseus;
using Colyseus.Schema;
public class Game1 : Game
{
Client client;
Room<MyRoomState> room;
protected override async void LoadContent()
{
client = new Client("ws://localhost:2567");
room = await client.JoinOrCreate<MyRoomState>("my_room");
System.Diagnostics.Debug.WriteLine("Joined room: " + room.Id);
// Get state callbacks handler
var callbacks = Callbacks.Get(room);
// Listen for changes on a state property
callbacks.Listen(state => state.currentTurn, (currentValue, previousValue) => {
System.Diagnostics.Debug.WriteLine($"Turn changed: {previousValue} -> {currentValue}");
});
// Listen for players being added
callbacks.OnAdd(state => state.players, (sessionId, player) => {
System.Diagnostics.Debug.WriteLine($"Player joined: {sessionId}");
callbacks.Listen(player, p => p.hp, (currentHp, previousHp) => {
System.Diagnostics.Debug.WriteLine($"Player {sessionId} hp: {currentHp}");
});
});
// Listen for players being removed
callbacks.OnRemove(state => state.players, (sessionId, player) => {
System.Diagnostics.Debug.WriteLine($"Player left: {sessionId}");
});
// Send a message to the server
room.Send("move", new { x = 10f, y = 20f });
// Listen for messages from the server
room.OnMessage<string>("chat", (message) => {
System.Diagnostics.Debug.WriteLine("Chat: " + message);
});
}
protected override void OnExiting(object sender, EventArgs args)
{
if (room != null) room.Leave();
base.OnExiting(sender, args);
}
}Optional: ColyseusGameComponent
The SDK automatically dispatches WebSocket messages internally via an async Task loop, so no extra setup is needed. However, you can optionally register the ColyseusGameComponent to synchronize message dispatching with MonoGame’s Update() cycle:
using Colyseus.MonoGame;
protected override void Initialize()
{
Components.Add(new ColyseusGameComponent(this));
base.Initialize();
}SDK API
Navigate to the Client SDK for API Reference, and select the C# (Unity) tab.
State Schema Codegen
For compiled languages like C#, you need to generate client-side schema classes that match your server’s state structure. Run the following command from your server directory:
npx schema-codegen src/rooms/schema/* --csharp --output ../MyGame/States/See the full State Schema Codegen documentation for more options and details.
DynamicSchema (Skip Code Generation)
If you want to skip the code generation step entirely, you can use DynamicSchema instead of generated schema classes. DynamicSchema builds its type metadata at runtime from the server handshake, so no generated files are needed.
using Microsoft.Xna.Framework;
using Colyseus;
using Colyseus.Schema;
public class Game1 : Game
{
Client client;
Room<DynamicSchema> room;
protected override async void LoadContent()
{
client = new Client("ws://localhost:2567");
room = await client.JoinOrCreate<DynamicSchema>("my_room");
System.Diagnostics.Debug.WriteLine("Joined room: " + room.Id);
// Access state fields dynamically
var players = room.State.Get<MapSchema<DynamicSchema>>("players");
var self = players[room.SessionId];
var hp = self.Get<int>("hp");
// Callbacks use string-based property names instead of expressions
var callbacks = Callbacks.Get(room);
callbacks.Listen<string>("currentTurn", (currentValue, previousValue) => {
System.Diagnostics.Debug.WriteLine($"Turn changed: {previousValue} -> {currentValue}");
});
callbacks.OnAdd<DynamicSchema>("players", (sessionId, player) => {
System.Diagnostics.Debug.WriteLine($"Player joined: {sessionId}");
// Access nested collections
var items = player.Get<ArraySchema<DynamicSchema>>("items");
});
callbacks.OnRemove<DynamicSchema>("players", (sessionId, player) => {
System.Diagnostics.Debug.WriteLine($"Player left: {sessionId}");
});
}
protected override void OnExiting(object sender, EventArgs args)
{
if (room != null) room.Leave();
base.OnExiting(sender, args);
}
}DynamicSchema trades compile-time type safety for convenience. You won’t get IDE autocomplete on state fields, and type errors will only surface at runtime. For production projects, Schema Codegen is recommended.
Message Types Codegen
The same schema-codegen command also generates C# classes for any TypeScript interface containing "Message" in its name. This allows you to use strongly-typed message objects on the client side.
For example, given the following server-side code:
interface MoveMessage {
x: number;
y: number;
}
// ...
messages = {
move: (client: Client, message: MoveMessage) => {
// handle move
}
}Running the codegen command will automatically generate a matching C# class:
public class MoveMessage {
public float x;
public float y;
}You can then use these classes when sending and receiving messages on the client:
room.Send("move", new MoveMessage() { x = 1.0f, y = 2.0f });The same applies to messages sent from the server to the client. For example, given a server-side interface:
interface EventMessage {
type: string;
data: string;
}
// ...
onJoin(client: Client) {
client.send("event", { type: "welcome", data: "Hello!" } as EventMessage);
}You can listen for it on the client with the generated type:
room.OnMessage<EventMessage>("event", (message) => {
System.Diagnostics.Debug.WriteLine(message.type);
System.Diagnostics.Debug.WriteLine(message.data);
});Debugging
Breakpoints
If you set a breakpoint in your application while the WebSocket connection is open, the connection will be closed automatically after 3 seconds due to inactivity. To prevent the WebSocket connection from dropping, use pingInterval: 0 during development:
import { defineServer } from "colyseus";
import { WebSocketTransport } from "@colyseus/ws-transport";
const server = defineServer({
// ...
transport: new WebSocketTransport({
pingInterval: 0, // <--- HERE
}),
// ...
});Make sure to have a pingInterval higher than 0 on production. The default pingInterval value is 3000.
MonoGame
Unity
Godot