Skip to content

狀態同步 » 架構

還沒使用 TypeScript?

強烈建議您使用 TypeScript 以便更好地定義 Schema 結構並提高整體開發體驗. TypeScript 支持的 "實驗性修飾器" 會在本手冊內大量使用.

如何定義可同步結構

  • Schema 結構由服務器定義, 用於房間狀態同步.
  • 只有以 @type() 修飾的字段才會被用於同步.
  • (可同步 Schema 結構僅應用於狀態相關的數據.)

定義 Schema 結構

// MyState.ts
import { Schema, type } from "@colyseus/schema";

export class MyState extends Schema {
    @type("string") currentTurn: string;
}
// MyState.ts
const schema = require('@colyseus/schema');
const Schema = schema.Schema;

class MyState extends Schema {
}
schema.defineTypes(MyState, {
  currentTurn: "string"
});

"這個 @type() 關鍵字是什麽? 我之前從未見過!"

您看見的在本頁大量使用的 @type() 是一個即將推出的 JavaScript 功能, 還沒有被 TC39 正式認可. type 其實只是一個從 @colyseus/schema 模塊導入的函數. 在屬性層級調用帶有 @ 前綴的 type, 意味著我們將其作為一個 屬性修飾器 進行調用. 在這裏查看修飾器方案.

在您的 Room 內使用狀態

// MyRoom.ts
import { Room } from "colyseus";
import { MyState } from "./MyState";

export class MyRoom extends Room<MyState> {
    onCreate() {
        this.setState(new MyState());
    }
}

使用 Schema

  • 只有服務器端有權修改 Schema 數據
  • 客戶端要包含以 schema-codegen 生成的與服務器端同樣的 Schema 定義. (如果使用 JavaScript SDK 則此條為可選項)
  • 為了從服務器獲得更新, 需要 在客戶端把回調附加在 schema 實例上.
  • 客戶端永遠不應主動修改 schema - 因為在收到來自服務器的下一個心跳就會把它更新覆蓋掉.

基本類型

基本類型為數字, 字符串和布爾型.

類型 描述 範圍
"string" utf8 字符串 最大 4294967295 字節
"number" 又稱為 "正整數". 自動定義數字類型. (編碼時可能會多用1個字節) 取值範圍 018446744073709551615
"boolean" truefalse 取值為 01

特定數值類型:

類型 描述 範圍
"int8" 有符號 8-bit 整數 -128127
"uint8" 無符號 8-bit 整數 0255
"int16" 有符號 16-bit 整數 -3276832767
"uint16" 無符號 16-bit 整數 065535
"int32" 有符號 32-bit 整數 -21474836482147483647
"uint32" 無符號 32-bit 整數 04294967295
"int64" 有符號 64-bit 整數 -92233720368547758089223372036854775807
"uint64" 無符號 64-bit 整數 018446744073709551615
"float32" 單精度浮點數 -3.40282347e+383.40282347e+38
"float64" 雙精度浮點數 -1.7976931348623157e+3081.7976931348623157e+308

復雜類型

復雜類型由 Schema 嵌套而成. 它們也可以包含 集合類型 (array, map 等).

import { Schema, type } from "@colyseus/schema";

class World extends Schema {
    @type("number") width: number;
    @type("number") height: number;
    @type("number") items: number = 10;
}

class MyState extends Schema {
    @type(World) world: World = new World();
}
const schema = require('@colyseus/schema');
const Schema = schema.Schema;

class World extends Schema {
}
schema.defineTypes(World, {
  width: "number",
  height: "number",
  items: "number"
});

class MyState extends Schema {
    constructor () {
        super();

        this.world = new World();
    }
}
schema.defineTypes(MyState, {
  world: World
});

集合類型

ArraySchema

ArraySchema 是一個可同步版本的內置 JavaScript Array 類型.

示例: 自定義 Schema 類型 數組

import { Schema, ArraySchema, type } from "@colyseus/schema";

class Block extends Schema {
    @type("number") x: number;
    @type("number") y: number;
}

class MyState extends Schema {
    @type([ Block ]) blocks = new ArraySchema<Block>();
}
const schema = require('@colyseus/schema');
const Schema = schema.Schema;
const ArraySchema = schema.ArraySchema;

class Block extends Schema {
}
schema.defineTypes(Block, {
  x: "number",
  y: "number"
});

class MyState extends Schema {
    constructor () {
        super();

        this.blocks = new ArraySchema();
    }
}
schema.defineTypes(MyState, {
  blocks: [ Block ],
});

示例: 基本類型 數組

數組元素必須是同一類型數據.

import { Schema, ArraySchema, type } from "@colyseus/schema";

class MyState extends Schema {
    @type([ "string" ]) animals = new ArraySchema<string>();
}
const schema = require('@colyseus/schema');
const Schema = schema.Schema;
const ArraySchema = schema.ArraySchema;

class MyState extends Schema {
    constructor () {
        super();

        this.animals = new ArraySchema();
    }
}
schema.defineTypes(MyState, {
  animals: [ "string" ],
});

array.push()

在一個數組後面添加一個或多個元素, 並返回該數組更新後的長度.

const animals = new ArraySchema<string>();
animals.push("pigs", "goats");
animals.push("sheeps");
animals.push("cows");
// 輸出: 4


array.pop()

移除一個數組的最後一個元素並返回該元素. 該方法會改變數組的長度.

animals.pop();
// 輸出: "cows"

animals.length
// 輸出: 3


array.shift()

移除一個數組的第一個元素並返回該元素. 該方法會改變數組的長度.

animals.shift();
// 輸出: "pigs"

animals.length
// 輸出: 2


array.unshift()

在一個數組的開頭添加一個或多個元素, 並返回該數組更新後的長度.

animals.unshift("pigeon");
// 輸出: 3


array.indexOf()

返回數組中找到給定元素的第一個索引, 如果不存在則返回 -1

const itemIndex = animals.indexOf("sheeps");


array.splice()

移除替換現有元素或 在指定位置 添加新元素來更改一個數組的內容.

// 找到需要移除元素的索引
const itemIndex = animals.findIndex((animal) => animal === "sheeps");

// 移除元素!
animals.splice(itemIndex, 1);


array.forEach()

叠代數組的每個元素.

this.state.array1 = new ArraySchema<string>('a', 'b', 'c');

this.state.array1.forEach(element => {
    console.log(element);
});
// 輸出: "a"
// 輸出: "b"
// 輸出: "c"
State.array1.ForEach((value) => {
    Debug.Log(value);
})
state.array1:each(function(value, index)
    print(index, "=>")
    pprint(value)
end)
for (index => value in state.array1) {
    trace(index + " => " + value);
}

Array 還有更多函數可用

詳見 MDN 文檔.

MapSchema

MapSchema 是一個基於 JavaScript 內置 Array 的可同步版本.

推薦使用 Maps 裏的 id 來追蹤遊戲實體, 比如玩家, 敵人等.

當前僅支持字符串類型的 id

目前, MapSchema 允許您自定義值的類型, 但是鍵的類型必須為為 string.

import { Schema, MapSchema, type } from "@colyseus/schema";

class Player extends Schema {
    @type("number") x: number;
    @type("number") y: number;
}

class MyState extends Schema {
    @type({ map: Player }) players = new MapSchema<Player>();
}
const schema = require('@colyseus/schema');
const Schema = schema.Schema;
const MapSchema = schema.MapSchema;

class Player extends Schema {
}
schema.defineTypes(Player, {
  x: "number",
  y: "number"
});

class MyState extends Schema {
    constructor () {
        super();

        this.players = new MapSchema();
    }
}
schema.defineTypes(MyState, {
  players: { map: Player }
});

map.get()

通過鍵得到 map 的值:

const map = new MapSchema<string>();
const item = map.get("key");

或者

//
// 不建議使用這種方法
//
// 保留這種方法只是為了 @colyseus/schema 的版本向下兼容
// 未來會舍棄這種方法.
//
const item = map["key"];


map.set()

通過鍵來設置 map 的值:

const map = new MapSchema<string>();
map.set("key", "value");

或者

//
// 不建議使用這種方法
//
// 保留這種方法只是為了 @colyseus/schema 的版本向下兼容
// 未來會舍棄這種方法.
//
map["key"] = "value";


map.delete()

通過鍵移除 map 的值:

map.delete("key");

或者

//
// 不建議使用這種方法
//
// 保留這種方法只是為了 @colyseus/schema 的版本向下兼容
// 未來會舍棄這種方法.
//
delete map["key"];


map.size

返回 MapSchema 對象中元素的數量.

const map = new MapSchema<number>();
map.set("one", 1);
map.set("two", 2);

console.log(map.size);
// 輸出: 2


map.forEach()

叠代 map 中的鍵值對, 以元素插入順序.

this.state.players.forEach((value, key) => {
    console.log("key =>", key)
    console.log("value =>", value)
});
State.players.ForEach((key, value) => {
    Debug.Log(key);
    Debug.Log(value);
})
state.players:each(function(value, key)
    print(key, "=>")
    pprint(value)
end)
for (key => value in state.players) {
    trace(index + " => " + value);
}

Map 還有更多函數可用

詳見 MDN 文檔.

SetSchema

SetSchema 僅支持 JavaScript

目前 SetSchema 只能在 JavaScript 中使用. 尚不支持 Haxe, C#, LUA 和 C++ 客戶端.

SetSchema 是一個基於 JavaScript 內置 Set 的可同步版本.

SetSchema 的用法和 [CollectionSchema] 十分類似, 最大區別在於 Set 的值具有唯一性. JS 的 Set 沒有直接獲取值的方法. (比如像 collection.at())

import { Schema, SetSchema, type } from "@colyseus/schema";

class Effect extends Schema {
    @type("number") radius: number;
}

class Player extends Schema {
    @type({ set: Effect }) effects = new SetSchema<Effect>();
}
const schema = require('@colyseus/schema');
const Schema = schema.Schema;
const SetSchema = schema.SetSchema;

class Effect extends Schema {
}
schema.defineTypes(Effect, {
  radius: "number",
});

class Player extends Schema {
    constructor () {
        super();

        this.effects = new SetSchema();
    }
}
schema.defineTypes(Player, {
  effects: { set: Effect }
});

set.add()

SetSchema 添加元素.

const set = new SetSchema<number>();
set.add(1);
set.add(2);
set.add(3);


set.at()

獲取 index 處的值.

const set = new SetSchema<string>();
set.add("one");
set.add("two");
set.add("three");

set.at(1);
// 輸出: "two"


set.delete()

按值刪除元素.

set.delete("three");


set.has()

檢查集合中是否有該值.

if (set.has("two")) {
    console.log("Exists!");
} else {
    console.log("Does not exist!");
}


set.size

返回 SetSchema 裏元素的長度.

const set = new SetSchema<number>();
set.add(10);
set.add(20);
set.add(30);

console.log(set.size);
// 輸出: 3

Set 還有更多函數可用

詳見 MDN 文檔.

CollectionSchema

CollectionSchema 僅支持 JavaScript

目前 CollectionSchema 只能在 JavaScript 中使用. 尚不支持 Haxe, C#, LUA 和 C++ 客戶端.

CollectionSchema 的用法與 ArraySchema 類似, 需要註意的是, 它不具備某些數組可用的函數.

import { Schema, CollectionSchema, type } from "@colyseus/schema";

class Item extends Schema {
    @type("number") damage: number;
}

class Player extends Schema {
    @type({ collection: Item }) items = new CollectionSchema<Item>();
}
const schema = require('@colyseus/schema');
const Schema = schema.Schema;
const CollectionSchema = schema.CollectionSchema;

class Item extends Schema {
}
schema.defineTypes(Item, {
  damage: "number",
});

class Player extends Schema {
    constructor () {
        super();

        this.items = new CollectionSchema();
    }
}
schema.defineTypes(Player, {
  items: { collection: Item }
});

collection.add()

CollectionSchema 添加元素.

const collection = new CollectionSchema<number>();
collection.add(1);
collection.add(2);
collection.add(3);


collection.at()

獲取 index 處的值.

const collection = new CollectionSchema<string>();
collection.add("one");
collection.add("two");
collection.add("three");

collection.at(1);
// 輸出: "two"


collection.delete()

按值刪除元素.

collection.delete("three");


collection.has()

檢查集合中是否有該值.

if (collection.has("two")) {
    console.log("Exists!");
} else {
    console.log("Does not exist!");
}


collection.size

返回 CollectionSchema 裏元素的長度.

const collection = new CollectionSchema<number>();
collection.add(10);
collection.add(20);
collection.add(30);

console.log(collection.size);
// 輸出: 3


collection.forEach()

叠代 CollectionSchema 中的鍵值對, 以元素插入順序.

collection.forEach((value, at) => {
    console.log("at =>", at)
    console.log("value =>", value)
});

每個客戶端過濾數據

此功能為實驗性質

@filter() / @filterChildren() 為實驗性質, 可能不適合快節奏遊戲.

過濾用來為指定客戶端隱藏部分狀態數據, 防止作弊, 防止玩家獲取全部數據.

數據過濾器回調, 可以針對 每個客戶端每個字段 進行觸發 (如果使用了 @filterChildren, 還可在每個子結構觸發). 如果過濾器回調返回 true, 則該字段數據將會發送給那個指定的客戶端, 否則不發送.

請註意, 只有被過濾字段 (或其子字段) 數據更新時, 過濾器回調才能被觸發. 要想手動觸發請參考 此問題 裏描述的方法.

@filter() 屬性修飾器

@filter() 屬性修飾器可作用於整個 Schema 字段.

下面展示了 @filter() 的函數簽名:

class State extends Schema {
    @filter(function(client, value, root) {
        // client 參數是:
        //
        // 當前將要接受數據的客戶端. 可以通過其
        // client.sessionId, 及其他信息判定是否
        // 要把數據同步給這個客戶端.

        // value 參數是:
        // 被 @filter() 標記過濾的字段值

        // root 參數是:
        // 房間 Schema 實例引用. 方便在是否過濾的
        // 決策過程中
        // 訪問房間狀態.
    })
    @type("string") field: string;
}
const schema = require('@colyseus/schema');
class State extends schema.Schema {}

schema.defineTypes(State, {
    field: "string"
});

schema.filter(function(client, value, root) {
    // client is:
    //
    // the current client that's going to receive this data. you may use its
    // client.sessionId, or other information to decide whether this value is
    // going to be synched or not.

    // value is:
    // the value of the field @filter() is being applied to

    // root is:
    // the root instance of your room state. you may use it to access other
    // structures in the process of decision whether this value is going to be
    // synched or not.
    return true;
})(State.prototype, "field");

@filterChildren() 屬性修飾器

@filterChildren() 屬性修飾器可用於過濾掉數組, 地圖, 集合等內的項目. 它的簽名與 @filter() 基本相同, 但是在 value 之前添加了 key 參數 - 表示 ArraySchema, MapSchema, CollectionSchema 等中的每個項目.

class State extends Schema {
    @filterChildren(function(client, key, value, root) {
        // client 參數是:
        //
        // 當前將要接受數據的客戶端. 可以通過其
        // client.sessionId, 及其他信息判定是否
        // 要把數據同步給這個客戶端.

        // value 參數是:
        // 被 @filter() 標記過濾的字段值

        // root 參數是:
        // 房間 Schema 實例引用. 方便在是否過濾的
        // 決策過程中
        // 訪問房間狀態.
    })
    @type([Cards]) cards = new ArraySchema<Card>();
}
const schema = require('@colyseus/schema');
class State extends schema.Schema {}

schema.defineTypes(State, {
    cards: [Card]
});

schema.filterChildren(function(client, key, value, root) {
    // client 參數是:
    //
    // 當前將要接受數據的客戶端. 可以通過其
    // client.sessionId, 及其他信息判定是否
    // 要把數據同步給這個客戶端.

    // key 參數是:
    // 子字段名

    // value 參數是:
    // 被 @filter() 標記過濾的字段值

    // root 參數是:
    // 房間 Schema 實例引用. 方便在是否過濾的
    // 決策過程中
    // 訪問房間狀態.
    return true;
})(State.prototype, "cards");

示例: 在卡牌遊戲中, 應該只有卡牌的持有者知道每個卡片的數據, 或者在特定條件下才能知道這些數據 (例如攤牌)

參考 @filter() 回調簽名:

import { Client } from "colyseus";

class Card extends Schema {
    @type("string") owner: string; // 用來保存卡牌持有者的 sessionId
    @type("boolean") discarded: boolean = false;

    /**
     * 不要在 `@filter` 函數裏使用箭頭函數
     * (會造成 `this` 指針丟失)
     */
    @filter(function(
        this: Card, // 定義 `@filter` 的類 (這裏 this 就是 `Card` 的實例)
        client: Client, // 要被過濾的客戶端 `client` 實例
        value: Card['number'], // 要被過濾的字段值. (這裏是 `number` 字段的值)
        root: Schema // 房間狀態 Schema 實例
    ) {
        return this.discarded || this.owner === client.sessionId;
    })
    @type("uint8") number: number;
}
const schema = require('@colyseus/schema');

class Card extends schema.Schema {}
schema.defineTypes(Card, {
    owner: "string",
    discarded: "boolean",
    number: "uint8"
});

/**
 * 不要在 `@filter` 函數裏使用箭頭函數
 * (會造成 `this` 指針丟失)
 */
schema.filter(function(client, value, root) {
    return this.discarded || this.owner === client.sessionId;
})(Card.prototype, "number");

客戶端

C#, C++, Haxe

在使用強類型語言時, 需要基於 Typescript schema 定義手動生成客戶端 schema 文件. 生成客戶端 schema 的方法.

回調

服務器狀態變更應用到客戶端時, 會根據變更的類型自動觸發本地實例上的回調.

回調通過房間狀態實例觸發. 使用前要確保該實例上已實現回調函數.

onAdd (instance, key)

只有集合 (MapSchema, ArraySchema 等) 可以使用 onAdd 回調. 集合更新後觸發 onAdd 回調, 外加已更新集合的鍵作為參數.

room.state.players.onAdd = (player, key) => {
    console.log(player, "has been added at", key);

    // 在遊戲中加入player!

    // 要想跟蹤地圖上物體的移動, 通常要這麽做:
    player.onChange = function(changes) {
        changes.forEach(change => {
            console.log(change.field);
            console.log(change.value);
            console.log(change.previousValue);
        })
    };

    // 手動強製觸發 "onChange"
    player.triggerAll();
};
room.state.players['on_add'] = function (player, key)
    print("player has been added at", key);

    -- 在遊戲中加入player!

    -- 要想跟蹤地圖上物體的移動, 通常要這麽做:
    player['on_change'] = function(changes)
        for i, change in ipairs(changes) do
            print(change.field)
            print(change.value)
            print(change.previousValue)
        end
    end

    -- 手動強製觸發 "onChange"
    player.trigger_all()
end
room.State.players.OnAdd += (Player player, string key) =>
{
    Debug.Log("player has been added at " + key);

    // 在遊戲中加入player!

    // 要想跟蹤地圖上物體的移動, 通常要這麽做:
    player.OnChange += (changes) =>
    {
        changes.ForEach((obj) =>
        {
            Debug.Log(obj.Field);
            Debug.Log(obj.Value);
            Debug.Log(obj.PreviousValue);
        });
    };

    // 手動強製觸發 "onChange"
    e.Value.TriggerAll();
};

onRemove (instance, key)

只有圖 (MapSchema) 和數組 (ArraySchema) 可以使用 onRemove 回調. 集合更新後觸發 onRemove 回調, 外加已更新集合的鍵作為參數.

room.state.players.onRemove = (player, key) => {
    console.log(player, "has been removed at", key);

    // 從遊戲中移除player!
};
room.state.players['on_remove'] = function (player, key)
    print("player has been removed at " .. key);

    -- 從遊戲中移除player!
end
room.State.players.OnRemove += (Player player, string key) =>
{
    Debug.Log("player has been removed at " + key);

    // 從遊戲中移除player!
};

onChange (changes:DataChange[])

Schema 上的 onChange 和集合結構上的不一樣. 對於 集合結構(數組, 映射等)的 onChange 請參考這裏.

可以註冊 onChange 以跟蹤 Schema 實例屬性的變更. onChange 的參數數組包含已變更的屬性以及變更前的值.

room.state.onChange = (changes) => {
    changes.forEach(change => {
        console.log(change.field);
        console.log(change.value);
        console.log(change.previousValue);
    });
};
room.state['on_change'] = function (changes)
    for i, change in ipairs(changes) do
        print(change.field)
        print(change.value)
        print(change.previousValue)
    end
end
room.State.OnChange += (changes) =>
{
    changes.ForEach((obj) =>
    {
        Debug.Log(obj.Field);
        Debug.Log(obj.Value);
        Debug.Log(obj.PreviousValue);
    });
};

沒有同步過的客戶端不能註冊 onChange 回調.


onChange (instance, key)

Schema 上的 onChange 和集合結構上的不一樣. 對於 SchemaonChange 請參考這裏.

當集合裏的 基本 類型 (string, number, boolean 等) 值更新時, 將觸發此回調.

room.state.players.onChange = (player, key) => {
    console.log(player, "have changes at", key);
};
room.state.players['on_change'] = function (player, key)
    print("player have changes at " .. key);
end
room.State.players.OnChange += (Player player, string key) =>
{
    Debug.Log("player have changes at " + key);
};

對於 非基本 類型 (各種 Schema 集合), 請先註冊 onAdd 再註冊 onChange.

onChange, onAddonRemove互斥的

onChange 回調在 onAddonRemove 期間不會被觸發.

如果想要跟蹤的更新包括 `onAdd` 和 `onRemove`, 請註冊這兩個回調.

.listen(prop, callback)

偵聽單個屬性更新.

.listen() 目前僅可用於 JavaScript/TypeScript.

參數:

  • property: 想要偵聽更新的屬性名稱.
  • callback: 當 property 更新時觸發的回調.

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

.listen() 返回的函數可用於移除偵聽器

const removeListener = state.listen("currentTurn", (currentValue, previousValue) => {
    // ...
});

// 之後, 如果不需要偵聽器了, 可以調用 `removeListener()` 來移除對 `"currentTurn"` 的偵聽.
removeListener();

listenonChange 有什麽區別?

.listen() 方法是專為監聽單個屬性 onChange 的簡化版本. 下面是把 .listen() 寫成 onChange 的樣子:

state.onChange = function(changes) {
    changes.forEach((change) => {
        if (change.field === "currentTurn") {
            console.log(`currentTurn is now ${change.value}`);
            console.log(`previous value was: ${change.previousValue}`);
        }
    })
}


生成客戶端 schema 的方法

schema-codegen 是一個轉譯工具, 用於把服務器端的 schema 定義文件轉換為客戶端可以使用的版本:

要在客戶端正確解碼 state, 客戶端的 schema 定義文件必須與服務器端相兼容.

在使用 JavaScript SDK 時不必使用此工具

只有在客戶端使用強類型語言, 如 C#, Haxe 等時, 才需要使用 schema-codegen.

使用方法

要在終端查看使用方法, 先 cd 進入服務器目錄, 然後運行以下命令:

npx schema-codegen --help

輸出:

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

舉例: Unity / C

下面是用 Unity 演示項目 生成 C# schema 文件的實例.

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

使用 npm 腳本:

簡言之, 推薦您把 schema-codegen 的參數保存在 package.json 文件中的 npm 腳本裏:

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

這樣, 運行 npm run schema-codegen, 就可以代替完整的命令:

npm run schema-codegen
generated: Player.cs
generated: State.cs

版本及向下/向上兼容

通過在現有結構末尾聲明新字段, 可以實現向下/向上兼容, 不應刪除先前的聲明, 而是應該根據需要將其標記為 @deprecated(). 下面是一個例子.

import { Schema, type, deprecated } from "@colyseus/schema";

class MyState extends Schema {
    @type("string") myField: string;
}
import { Schema, type, deprecated } from "@colyseus/schema";

class MyState extends Schema {
    // 標記此字段作廢.
    @deprecated() @type("string") myField: string;

    // 保證不同客戶端版本的服務器兼容.
    @type("string") newField: string;
}
import { Schema, type, deprecated } from "@colyseus/schema";

class MyState extends Schema {
    // 標記此字段作廢.
    @deprecated() @type("string") myField: string;

    // 再次標記此字段作廢.
    @deprecated() @type("string") newField: string;

    // 最新的字段總是保持在最下邊
    @type("string") anotherNewField: string;
}

這對於本地編譯類語言很有用, 如 C#, C++, Haxe 等 - 即使這些客戶端編譯時沒有最新的 schema 定義.


限製和最佳實踐

  • 每個 Schema 結構最多可以保存 64 個字段. 如果需要更多字段, 可以嵌套使用 Schema 結構.
  • NaNnull 數字被編碼為 0
  • null 字符串被編碼為 ""
  • Infinity 數字被編碼為 Number.MAX_SAFE_INTEGER
  • 不支持多維數組. 查看如何將一維數組作為多維數組使用
  • @colyseus/schema 編碼順序按照字段定義順序.
    • 編碼器 (服務器) 和解碼器 (客戶端) 都必須擁有相同的 schema 定義.
    • 字段的順序也要相同.

集合

集合類型 (ArraySchema, MapSchema 等) 裏的元素類型必須相同, 或者基類相同.

支持以下寫法:

class Item extends Schema {/* 基類 Item */}
class Weapon extends Item {/* 武器類 */}
class Shield extends Item {/* 盾牌類 */}

class Inventory extends Schema {
    @type({ map: Item }) items = new MapSchema<Item>();
}

const inventory = new Inventory();
inventory.set("left", new Weapon());
inventory.set("right", new Shield());

Back to top