组队下副本:被动开房(Party)+ 队伍聊天
常见落地组合:matchmaker_room 的 Party(房间码)组固定队伍,等人或加载阶段用 chat_team_room 做队内沟通,人满后由房主 party:start 进入 game_room(副本 / 对局)。约定与 匹配与开房、消息协议、JWT 与房间鉴权 对齐。
能力对照
| 能力 | 房间 / 入口 | 说明 |
|---|---|---|
| 被动开房、房间码、人满开局 | matchmaker_room | party:create → party:join → party:start,见下文消息表 |
| 队内文字聊天 | chat_team_room | 进房 options 带 teamId,仅同 teamId 玩家互见(TeamChatRoom) |
| 副本战斗 / 帧同步 | game_room | party:start 或匹配成功后由 match:found 引导 joinById |
teamId 与 Party 的衔接:服务端未把 partyId 自动写入聊天;推荐客户端在拿到 partyId 后,全队统一使用 同一字符串 作为 teamId(例如直接使用 partyId),再进入 chat_team_room。这样无需额外「建队」接口即可对齐队伍聊天与 Party 成员。
整体流程(推荐)
mermaid
sequenceDiagram
participant A as 房主客户端
participant M as matchmaker_room
participant T as chat_team_room
participant G as game_room
A->>M: join + party:create
M-->>A: party:created (partyId, partyCode)
Note over A,T: 全队用 partyId 作 teamId 进队伍房
A->>T: joinOrCreate(chat_team_room, { token, teamId: partyId })
A->>M: party:start (满员后)
M-->>A: match:found
A->>G: joinById + joinOptions + token- 登录:HTTP 或小游戏登录拿到 JWT(API 概览、小游戏教程)。
- 匹配房:
joinOrCreate("matchmaker_room", { token }),等待mm:ready。 - 房主:
send("party:create", { modeId, playersPerMatch, region? }),收到party:created后把partyCode发给好友。 - 队员:
send("party:join", { partyCode }),收到party:joined;全员监听party:update看人数。 - 队伍聊天(可选但推荐):全员
joinOrCreate("chat_team_room", { token, teamId: partyId })(partyId来自party:created/party:joined的 payload)。发chat、收下行chat(消息协议)。 - 开局:房主在人数 ≥
playersPerMatch时send("party:start", {});全员收到match:found,再joinById(roomId, { ...joinOptions, token })进入game_room。 - 离开队伍:未开局前可
party:leave;进副本后 Party 已关闭,聊天房可leave或保留至副本结束由客户端策略决定。
Party 消息速查(matchmaker_room)
详见 匹配与开房,此处摘录与「组队下副本」强相关部分。
| 消息 | 方向 | 要点 |
|---|---|---|
party:create | C→S | { modeId, playersPerMatch, region? } |
party:created | S→C | 含 partyId、partyCode、playersPerMatch、leaderUserId |
party:join | C→C | { partyCode } |
party:joined / party:update | S→C | 人数、是否队长 |
party:start | C→S | 仅房主;满员后创建 game_room 并广播 match:found |
party:leave / party:left | 双向 | 退队 |
party:error | S→C | 错误文案 |
match:found | S→C | 进 game_room 的 roomId、joinOptions、reconnectKey 等 |
队伍聊天(chat_team_room)
- 房间名:
chat_team_room(见 消息协议)。 - 进房 必须 带 JWT,且 options 中
teamId与队友一致(建议等于partyId)。 - 上行:
room.send("chat", "文本")或room.send("chat", { message: "文本" })(与BaseChatRoom解析一致)。 - 下行:
channel为team,payload 含sender、message、timestamp等。
示例代码(TypeScript / colyseus.js)
以下用 async/await 串起 Party + 队伍房 + 进副本;可直接迁移到 Cocos(db://colyseus-sdk/colyseus.js)或浏览器调试。请将 ENDPOINT、TOKEN、MODE、PLAYERS 换成真实值。
客户端 Room.onMessage 一般为 (type, (payload) => void)(具体以当前 colyseus.js 版本为准)。
(客户端代码)
ts
import Colyseus from "colyseus.js";
const ENDPOINT = "ws://127.0.0.1:2567";
const TOKEN = "YOUR_JWT_ACCESS_TOKEN";
const MODE = "dungeon_normal";
const PLAYERS = 4;
function onceMessage<T = unknown>(room: Colyseus.Room, type: string, ms = 30000): Promise<T> {
return new Promise((resolve, reject) => {
let dispose: (() => void) | undefined;
const timer = setTimeout(() => {
dispose?.();
reject(new Error(`${type} 超时 ${ms}ms`));
}, ms);
dispose = room.onMessage(type, (payload: T) => {
clearTimeout(timer);
dispose?.();
resolve(payload);
}) as (() => void) | undefined;
});
}
async function partyDungeonWithTeamChat(isLeader: boolean, partyCodeFromFriend?: string) {
const client = new Colyseus.Client(ENDPOINT);
const mm = await client.joinOrCreate("matchmaker_room", { token: TOKEN });
await onceMessage(mm, "mm:ready", 15000);
let partyId = "";
if (isLeader) {
mm.send("party:create", { modeId: MODE, playersPerMatch: PLAYERS });
const created = await onceMessage<Record<string, unknown>>(mm, "party:created");
partyId = String(created.partyId ?? "");
console.log("房间码:", created.partyCode, "partyId:", partyId);
} else {
if (!partyCodeFromFriend) throw new Error("队员需要 partyCode");
mm.send("party:join", { partyCode: partyCodeFromFriend });
const joined = await onceMessage<Record<string, unknown>>(mm, "party:joined");
partyId = String(joined.partyId ?? "");
}
const teamChat = await client.joinOrCreate("chat_team_room", {
token: TOKEN,
teamId: partyId,
});
teamChat.onMessage("chat", (payload: Record<string, unknown>) => {
console.log("[队伍]", payload?.message, (payload?.sender as Record<string, string>)?.username);
});
teamChat.send("chat", { message: "人已齐,准备进本" });
mm.onMessage("party:update", (p: Record<string, unknown>) => {
console.log("队伍人数:", p?.count, "/", p?.playersPerMatch);
});
mm.onMessage("match:found", async (payload: Record<string, unknown>) => {
const roomId = String(payload.roomId ?? "");
const joinOptions = (payload.joinOptions as Record<string, unknown>) ?? {};
console.log("进副本房:", roomId);
const game = await client.joinById(roomId, { ...joinOptions, token: TOKEN });
game.onMessage("frameSync", (p: unknown) => console.log("帧同步", p));
game.onMessage("playerJoined", (p: unknown) => console.log("玩家进房", p));
});
if (isLeader) {
/* 演示:2 秒后强制开局。正式项目请在 UI 上确认 count >= playersPerMatch 再 send */
await new Promise((r) => setTimeout(r, 2000));
mm.send("party:start", {});
}
}
// 房主:void partyDungeonWithTeamChat(true);
// 队员:void partyDungeonWithTeamChat(false, "AB12CD");说明:
- 上面房主
setTimeout仅作演示,正式项目应在收到party:update且count >= playersPerMatch后再由玩家点击「开始」调用party:start。 match:found后务必 合并token进joinById的 options(与 鉴权与权限 一致)。
其它引擎要点(无完整工程粘贴时)
- Cocos:与 Cocos 接入示例 相同,分别
joinOrCreate两个房间;teamId与token放在第二个参数对象中。 - Unity:
JoinOrCreate/JoinById的Dictionary<string, object>中设置token、teamId;监听OnMessage解析 JSON(或使用消息代码生成)。 - Godot:
join_or_create的 options 字典含token与teamId;信号message_received处理chat与match:found(与插件 API 一致即可)。
风险与限制
- Party 与聊天房 相互独立:若队员未进
chat_team_room或teamId不一致,将收不到队内消息。 - Party 数据有 Redis TTL(约 30 分钟),超时需重新建房。
- 生产环境务必关闭或隔离
loadtest_room等无鉴权房间(人数压力测)。