Filter 是什么意思?
Filter 这个词在检索和匹配类的程序中经常见到.
比如下面这个网站.

0_1672746177268_00.jpg

Type to filter... 这里我们填写关键词,

0_1672746210946_01.jpg

匹配程序精准地帮我们找到了需要的内容.
Colyseus 里面有两处与 Filter 有关的地方. 一个是 Matchmaking 用的 filterBy(); 一个是 Schema 用的 @filter 装饰器.


RegisteredHandler.filterBy()

这次我们用 Colyseus Server + Cocos Creator Client 来做展示.

首先建立服务器环境.

0_1672914626753_19e93398-283a-41cb-8a16-49dd31537d5b-image.png

不知道这个菜单哪里来的的话请参考这里.

新建 Cocos 项目, 安装 Colyseus 客户端 SDK.

0_1672746222679_1.jpg

如果发现导入时报错,

0_1672746232472_2.jpg
要在 tsconfig.json 文件中开启 allowSyntheticDefaultImports 参数.

0_1672746249762_3.jpg
好了, 准备工作结束, 让我们来连接房间看看.

0_1672747117496_5.jpg

joinOrCreate 函数的第一个参数是房间名, 第二个参数是自定义的房间参数.
这里我们自定义一个叫做 mode 的参数, 值为 duo, 表示是双人游戏房间.

this.room = await this.client.joinOrCreate("my_room", { mode: "duo" });

0_1672746263798_4.jpg

在服务器端, 我们定义了房间名 my_room, 然后后面跟着一个 filterBy, 里面有两个过滤参数, "mode" 和 "level".

gameServer.define('my_room', MyRoom).filterBy(["mode", "level"]);

它的意思是, 在进行房间匹配的时候, 把所有房间根据 "mode" 和 "level" 两个参数过滤, 只有这两个参数 完全匹配 的玩家才可以匹配进入相应房间.
还记得客户端连接时的需求吗? 玩家要进入 "mode" 为 "duo" 的房间, "level" 没有指定.
匹配器会根据 server 上已有的没有锁定的房间, 过滤出 "mode" 为 "duo" 的, "level" 为 null 的房间, 如果存在, 优先进入; 如果不存在, 新建一个这样的房间再进入.
如果客户端没有要求参数, 如何匹配房间呢? 匹配器会寻找 "mode" 为 "null" 而且 "level" 为 "null" 的房间优先匹配进入; 对于 "mode" 为 "duo" 的房间, 它是 不考虑在内 的.
除了 filterBy, 常用的还有一个 sortBy, 两个联合使用又是什么意思呢?

gameServer.define('my_room', MyRoom).filterBy(["mode"]).sortBy({level:-1});

sortBy 的意思是排序. 根据参数值升序 (+) 或者降序 (-) 排序, 然后进行优先匹配.
上面那条语句的意思是, 寻找 mode 一致的, 按 level 从大到小排列, 选最大的, 进行匹配.
可以看出, filterBy 的作用就是告诉匹配器, 如何寻找, 找什么样的房间, 来给客户端进行分配.


Schema 的 @filter 装饰器

众所周知(?), Schema 是会自动同步到房间的每一个客户端中的, 是 "服务器权威" 模式的基础.
但是有时候, 我们需要某个客户端只能知道 Schema 的一部分而非全部.
典型的需求就是牌类游戏, 每个客户端只能了解自己的手牌而不能知道其他人的牌.

让我们开发一个简单的猜骰子点数大小的游戏. 游戏分三个步骤:

  • 玩家进入服务器房间, 服务器开始摇骰子出一个点数, 此时玩家虽然知道已摇出点数, 但是不能知道点数是多少;
  • 玩家猜测点数大小并发送到服务器;
  • 服务器开点数, 并把结果发回给客户端.

0_1673095144215_game.jpg

点数 1~3 是 "小", 4~6 是 "大".

游戏的 Schema 设定是关键所在:

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

export class Dice extends Schema {
    @type("string") status: string = "rolling";
    @type("string") result: string = "";
    @filter(function (this: Dice, client: Client, value: Dice['number'], root: Schema) {
        return this.status == "opened";
    })
    @type("uint8") number: number;

}

首先导入 filter 装饰器, 才能使用. 这个游戏我们要过滤的是骰子点数, 也就是最后的 number. 过滤方法是看当前状态是否已经到了开点数的 status.
status 分为 "要骰子", "猜点数", "开点数" 三个阶段.

0_1673095592433_filtered.jpg

"猜点数" 阶段虽然客户端知道有 "number" 这个数据, 但是值是 undefined.

当服务器在任何地方执行了 this.state.status = "opened"; 这条语句, 过滤自动失效, 此后客户端就能知道具体点数了.

0_1673095858075_result.jpg

代码和手册参考:
https://github.com/CocosGames/ColyseusFilters
https://docs.colyseus.io/colyseus/state/schema/#filtering-data-per-client