State SynchronizationClient-side Callbacks

Client-side Schema Callbacks

When applying state changes coming from the server, the client-side is going to trigger callbacks on local instances according to the changes being applied.

In order to register callbacks to Schema instances, you must access the instances through a “callbacks handler”.

Overview

Get the callback handler

client.ts
import { Client, getStateCallbacks } from "colyseus.js";
 
// initialize SDK
const client = new Client("ws://localhost:2567");
 
// join room
const room = await client.joinOrCreate("my_room");
 
// get state callbacks handler
const $ = getStateCallbacks(room);

Register the callbacks

client.ts
$(room.state).listen("currentTurn", (currentValue, previousValue) => {
    // ...
});
 
// when an entity was added (ArraySchema or MapSchema)
$(room.state).entities.onAdd((entity, sessionId) => {
    // ...
    console.log("entity added", entity);
 
    $(entity).listen("hp", (currentHp, previousHp) => {
        console.log("entity", sessionId, "changed hp to", currentHp);
    })
});
 
// when an entity was removed (ArraySchema or MapSchema)
$(room.state).entities.onRemove((entity, sessionId) => {
    // ...
    console.log("entity removed", entity);
});
⚠️

C#, C++, Haxe - When using statically typed languages, you need to generate the client-side schema files based on your TypeScript schema definitions. See generating schema on the client-side.

How to use

On Schema instances

Listen

Listens for a single property change within a Schema instance.

client.ts
$(room.state).listen("currentTurn", (currentValue, previousValue) => {
    console.log(`currentTurn is now ${currentValue}`);
    console.log(`previous value was: ${previousValue}`);
});

Removing the callback: The .listen() method returns a function that, when called, removes the attached callback:

client.ts
const unbindCallback = $(room.state).listen("currentTurn", (currentValue, previousValue) => {
    // ...
});
 
// stop listening for `"currentTurn"` changes.
unbindCallback();

Bind To

Bind properties directly to targetObject, whenever the client receives an update from the server.

Parameters:

  • targetObject: the object that will receive updates
  • properties: (optional) list of properties that will be assigned to targetObject. By default, every @type()’d property will be used.
client.ts
$(room.state).players.onAdd((player, sessionId) => {
    const playerVisual = PIXI.Sprite.from('player');
    $(player).bindTo(playerVisual);
});

On Change

The On Change callback is invoked whenever a direct property of a Schema instance is modified.

  • Triggers only for direct property changes: It does not cascade or propagate changes from nested properties within the Schema.
  • The callback fires after the changes have been applied to the Schema instance. This means you’re dealing with the updated instance when the callback executes.
client.ts
$(room.state).entities.onAdd((entity, sessionId) => {
    // ...
    $(entity).onChange(() => {
        // some property changed inside `entity`
    })
});

On Maps or Arrays

On Add

Register the onAdd callback is called whenever a new instance is added to a collection.

By default, the callback is called immediately for existing items in the collection.

client.ts
$(room.state).players.onAdd((player, sessionId) => {
    console.log(player, "has been added at", sessionId);
 
    // add your player entity to the game world!
 
    // detecting changes on object properties
    $(player).listen("field_name", (value, previousValue) => {
        console.log(value);
        console.log(previousValue);
    });
});

On Remove

The onRemove callback is called with the removed item and its key on holder object as argument.

client.ts
$(room.state).players.onRemove((player, sessionId) => {
    console.log(player, "has been removed at", sessionId);
 
    // remove your player entity from the game world!
});

Client-side Schema Generation

⚠️

Not required when using JavaScript SDK or Defold SDK - The following section is only required when using statically typed languages in your front-end, such as C#, Haxe, etc.

The schema-codegen is a command-line tool designed to convert your server-side schema definitions into compatible client-side schemas.

To decode the state on the client side, its local schema definitions must be compatible with those on the server.

Usage:

To see the usage, From your terminal, cd into your server’s directory and run the following command:

Terminal
npx schema-codegen --help

Output:

Terminal
schema-codegen [path/to/Schema.ts]
 
Usage (C#/Unity)
    schema-codegen src/Schema.ts --output client-side/ --csharp --namespace MyGame.Schema
 
Valid options:
    --output: fhe output directory for generated client-side schema files
    --csharp: generate for C#/Unity
    --cpp: generate for C++
    --haxe: generate for Haxe
    --ts: generate for TypeScript
    --js: generate for JavaScript
    --java: generate for Java
 
Optional:
    --namespace: generate namespace on output code

Example: Unity / C#

Below is a real example to generate the C# schema files from the demo Unity project.

Terminal
npx schema-codegen src/rooms/schema/* --csharp --output ../Assets/Scripts/States/"
generated: Player.cs
generated: State.cs

Using npm scripts:

For short, it is recommended to have your schema-codegen arguments configured under a npm script in your package.json:

package.json
"scripts": {
    "schema-codegen": "schema-codegen src/rooms/schema/* --csharp --output ../Assets/Scripts/States/"
}

This way you can run npm run schema-codegen rather than the full command:

Terminal
npm run schema-codegen
generated: Player.cs
generated: State.cs
Last updated on