Skip to content

多模式聊天

框架将 文字聊天 拆成多个 独立房间名(Room),同一套客户端消息(如 chat),由 进房时的 options 决定频道:世界、工会、附近、队伍等。与 匹配房 matchmaker_room、对局房 game_room 不同类房间;完整消息名清单仍见 消息协议

浏览器演示(ChatDemo)

服务端仓库静态页 ChatDemo.html(浏览器标题「聊天演示 - 小游码匠」)用于联调 工会 / 队伍 / 附近 等进房参数与 chat / updatePosition

ChatDemo:guildId、teamId、坐标与附近半径、发送 chat 与 updatePosition、日志区

说明
路径源码 src/public/ChatDemo.html,构建后在 dist/public/
打开http://localhost:<PORT>/ChatDemo.html,默认端口 2567PORT 环境变量)
说明页内填写 JWT 后连接;guildIdteamIdx / ynearbyRadius 与进 chat_*_room 的 options 对应;可 发送 chatupdatePosition 验证附近频道。

多开窗口、使用 不同 teamId / guildId 或坐标,可对照下面四种房间,观察消息是否只出现在预期频道。完整跑通服务与拿 Token 见 快速入门

四种聊天房

房间名典型用途关键 options(与鉴权)
chat_world_room全服世界频道JWT;可选业务字段。支持 Redis Pub/Sub 在多 Colyseus 进程间同步同一条世界消息。
chat_guild_room工会 / 公会内频道JWT + guildId,仅同公会成员会话内互通。
chat_nearby_room地图附近可见聊天JWT + 坐标(如 x / y)及 nearbyRadius 等(与实现一致);需 updatePosition 更新位置。
chat_team_room小队 / 固定队伍频道JWT + teamId;常与 Party 房间码组局共用同一 teamId(如直接取 partyId)。

房间注册与类名对应关系见 项目结构进房鉴权token / accessToken)见 鉴权与权限JWT 与房间鉴权

进房示例

(客户端代码) 分两段:① 四种 joinOrCreate 与 options(与 ChatDemo.html 一致);② 进房后注册 onMessage("chat")send("chat", …),附近房再发 updatePosition。Cocos / Unity / Godot 细节见各 引擎接入 页。

约定tokenaccessToken 二选一即可(与 JWT 与房间鉴权 一致)。teamId 常与 Party / 匹配下发的 partyIdjoinOptions 对齐,见 组队下副本

① 进房 options(四种房间名)

ts
import Colyseus from "colyseus.js";

const client = new Colyseus.Client("ws://127.0.0.1:2567");
const accessToken = "<JWT>"; // HTTP 登录拿到的 accessToken,进房字段名用 token
/** Party / match:found 等载荷里的队伍或房间码,与全队进 chat_team_room 对齐 */
const partyIdFromPartyPayload = "PARTY_AB12";

// 世界
await client.joinOrCreate("chat_world_room", { token: accessToken });

// 工会:必须带 guildId
await client.joinOrCreate("chat_guild_room", {
  token: accessToken,
  guildId: "1001",
});

// 附近:进房时带初始坐标与半径(字段名以服务端 Room 为准;ChatDemo 与下节一致)
await client.joinOrCreate("chat_nearby_room", {
  token: accessToken,
  x: 120,
  y: 45,
  nearbyRadius: 80,
});

// 队伍:全队同一 teamId 即同一聊天会话
await client.joinOrCreate("chat_team_room", {
  token: accessToken,
  teamId: partyIdFromPartyPayload,
});
csharp
using System.Collections.Generic;
using Colyseus.Schema;

// 假设已有 Colyseus.Client client、string accessToken、string partyIdFromPartyPayload

var world = await client.JoinOrCreate<DynamicSchema>(
    "chat_world_room",
    new Dictionary<string, object> { { "token", accessToken } }
);

var guild = await client.JoinOrCreate<DynamicSchema>(
    "chat_guild_room",
    new Dictionary<string, object> {
        { "token", accessToken },
        { "guildId", "1001" },
    }
);

var nearby = await client.JoinOrCreate<DynamicSchema>(
    "chat_nearby_room",
    new Dictionary<string, object> {
        { "token", accessToken },
        { "x", 120 },
        { "y", 45 },
        { "nearbyRadius", 80 },
    }
);

var team = await client.JoinOrCreate<DynamicSchema>(
    "chat_team_room",
    new Dictionary<string, object> {
        { "token", accessToken },
        { "teamId", partyIdFromPartyPayload },
    }
);
gdscript
# 假设已有 Colyseus.Client client、access_token、party_id_from_party_payload(字符串)
# 世界
var _world = client.join_or_create("chat_world_room", {"token": access_token})

# 工会
var _guild = client.join_or_create("chat_guild_room", {
    "token": access_token,
    "guildId": "1001",
})

# 附近
var _nearby = client.join_or_create("chat_nearby_room", {
    "token": access_token,
    "x": 120,
    "y": 45,
    "nearbyRadius": 80,
})

# 队伍
var _team = client.join_or_create("chat_team_room", {
    "token": access_token,
    "teamId": party_id_from_party_payload,
})

② 进房后:监听下行 chat、上行发送(含附近 updatePosition

仓库 ChatDemo.html 中上行 chat 使用 { message: string }(不是裸字符串);updatePosition{ x, y }。下列以世界房为例写完整闭环;工会 / 队伍 / 附近仅 进房 options 不同,收发消息名相同

ts
import Colyseus from "colyseus.js";

async function worldChatDemo() {
  const client = new Colyseus.Client("ws://127.0.0.1:2567");
  const room = await client.joinOrCreate("chat_world_room", { token: "<JWT>" });

  room.onMessage("chat", (payload: Record<string, unknown>) => {
    // 下行结构见下文「下行 chat 广播」与消息协议
    console.log("chat", payload);
  });

  room.onError((code, message) => console.error("room error", code, message));

  // 上行:与 ChatDemo 一致
  room.send("chat", { message: "hello world" });

  // 仅 chat_nearby_room:移动后周期性上报坐标,供服务端筛选听众
  // room.send("updatePosition", { x: 120, y: 46 });
}

worldChatDemo().catch(console.error);
csharp
using System;
using System.Collections.Generic;
using Colyseus;
using Colyseus.Schema;
using UnityEngine;

public static async void WorldChatDemo(Colyseus.Client client, string accessToken)
{
    var room = await client.JoinOrCreate<DynamicSchema>(
        "chat_world_room",
        new Dictionary<string, object> { { "token", accessToken } }
    );

    room.OnMessage<Dictionary<string, object>>("chat", payload =>
    {
        payload.TryGetValue("message", out var msg);
        Debug.Log($"chat message={msg}");
    });

    room.Send("chat", new Dictionary<string, object> { { "message", "hello world" } });

    // 附近房:room.Send("updatePosition", new Dictionary<string, object> { { "x", 120 }, { "y", 46 } });
}
gdscript
# 进房后(官方 GDExtension:joined / message_received;与 ChatDemo 负载一致)

func run_world_chat_demo(client: Colyseus.Client, access_token: String) -> void:
    var room := client.join_or_create("chat_world_room", {"token": access_token})
    if not room:
        return
    room.joined.connect(func():
        room.message_received.connect(_on_chat_message)
        room.send_message("chat", {"message": "hello world"})
        # 附近房:room.send_message("updatePosition", {"x": 120, "y": 46})
    )

func _on_chat_message(type: Variant, data: Variant) -> void:
    if str(type) == "chat":
        print("chat ", data)

客户端发送的消息

消息名方向说明
chat客户端 → 服务端发送聊天正文。仓库 ChatDemo.html 使用 { message: string };若您 fork 了服务端,以当前 Room 实现为准。
updatePosition客户端 → 服务端仅附近频道 有意义:负载 { x, y }(与进房初始坐标同一套字段),移动后应节流或定时上报,供服务端筛选听众。

监听下行仍用 room.onMessage("chat", handler);其余房间类型专用消息(如匹配、帧同步)不在本节展开,见 匹配与开房帧同步与游戏房

下行 chat 广播(示例)

其它在线成员会收到结构类似于(字段以仓库为准):

(客户端代码)

ts
{
  channel: "guild", // 或 world / nearby / team 等标识
  message: "hello",
  sender: {
    sessionId: "xxx",
    userId: "u001",
    username: "playerA"
  },
  timestamp: 1710000000000
}

更完整的示例与频道枚举见 消息协议 中的 chat 下行消息示例频道说明

世界频道与多实例

世界频道消息量大、且玩家可能落在 不同 Colyseus 进程。服务端对世界频道使用 Redis 做跨实例广播,因此部署多进程 / 多机时请保证 Redis 可用(环境变量见 配置说明)。工会 / 队伍等频道多以「单房内广播」为主,仍依赖正确传入 guildId / teamId 以隔离会话。

与 Party、副本流程的组合

被动开房、等人阶段全队进 chat_team_room、房主再 party:start 进入 game_room 的完整串联示例见:组队下副本(Party + 队伍聊天)

通用收发约定

任意 Colyseus 房间仍遵循:

  • 上行:room.send("eventName", payload)
  • 下行:room.onMessage("eventName", handler)

建议为每条消息维护 入参 / 出参 / 错误码 说明;高频消息在客户端做 节流或合并,避免压满带宽与序列化开销。