多人大厅比看起来复杂
组队大厅看起来只是几个头像和一个准备按钮,但真实状态很多:谁是房主,谁已准备,谁在换角色,谁掉线,谁版本不一致,谁正在接受邀请,匹配是否已经提交,房间是否锁定。只要状态处理不清,玩家就会看到按钮乱闪、成员列表跳动、准备状态回退。
很多项目把大厅写成 UI 回调集合。点邀请按钮调接口,点准备按钮改按钮,收到成员变化就刷新列表。早期能跑,后面加语音、跨服、观战、机器人补位、队伍任务,就会变得很难维护。多人大厅更适合有一个明确的状态模型。
状态模型是中心
客户端应该维护一份 LobbyState:房间 ID、成员列表、房主 ID、自己权限、准备状态、匹配状态、邀请状态、错误状态。服务端事件和本地操作都先进入状态模型,再由 UI 渲染。UI 不直接决定真相,只提交意图。
flowchart TD
A[本地操作: 准备/邀请/踢人] --> B[LobbyController]
C[服务端事件: 加入/离开/状态变化] --> B
B --> D[LobbyState 归并]
D --> E{状态是否合法?}
E -->|否| F[请求全量快照修正]
E -->|是| G[UI 渲染差异]
G --> H[成员列表/按钮/提示]
这种结构能处理乱序事件。比如客户端先收到某成员准备事件,再收到他加入事件,状态模型可以判断缺少成员基础信息,触发全量快照,而不是让 UI 创建一个半空成员。
本地 pending 很重要
玩家点击准备后,按钮应该立即反馈,但不能立刻把权威状态改成已准备。更好的做法是进入 pending:按钮显示提交中,禁止重复点击,收到服务端确认后转成已准备,失败则回退并提示原因。
邀请、换角色、踢人、开始匹配都类似。pending 状态让玩家知道操作被接收,也防止重复请求。没有 pending,弱网下玩家会连续点,服务端返回顺序一乱,UI 就容易错。
房主权限会变化
房主离开时,服务端可能转移房主。客户端要把权限看成状态,而不是进入房间时固定。新房主会看到开始匹配、踢人、转让等按钮,普通成员看不到。权限变化时 UI 要平滑更新,不能保留旧按钮。
房主操作还要处理并发。房主点开始匹配的同时,有成员取消准备或掉线,服务端可能拒绝开始。客户端要能展示具体原因,而不是只说失败。
匹配开始后要锁房间
一旦提交匹配,房间进入匹配中状态。此时很多操作应该被限制:换角色、邀请新成员、踢人、改队伍目标。否则服务端和客户端状态会越来越复杂。玩家如果取消匹配,也要等服务端确认后再解锁。
匹配成功到进战斗之间还有过渡状态。有人加载慢、有人断线、资源版本不一致,都要显示出来。大厅不能过早销毁,否则失败时没有地方回退。
小结
多人大厅的核心不是按钮,而是状态一致性。把本地意图、服务端事件、pending 状态、权限变化和异常恢复都归到 LobbyState,客户端才能在弱网和多人并发操作下保持可信。
大厅状态一致性要留缓冲
多人大厅的状态变化密集:有人加入、离开、换职业、改皮肤、准备、取消准备、房主转移、匹配开始、邀请过期。客户端如果每收到一个事件就立即重排 UI,玩家会看到头像跳动、按钮闪烁、准备状态回退。
更稳的做法是把服务端事件写入大厅状态模型,再由 UI 按节奏刷新。短时间内连续事件可以合并,比如加入后立刻准备,只需要展示最终状态。关键动作仍要及时反馈,比如自己点准备后立即显示 pending,再等待服务端确认。
一致性也包含失败路径。邀请失败、房间已满、版本不一致、成员掉线、匹配取消,都要有明确状态。不要让大厅停在半透明按钮和无限转圈里。玩家在组队时耐心很低,因为他还在等其他人。
成员列表要做差异更新
多人大厅成员列表不大,但变化频繁。每次收到事件就全量重建列表,会造成头像闪烁、语音状态重置、准备动画重复播放。更好的做法是按成员 ID 做差异更新:新增成员创建条目,离开成员播放退出动画后移除,状态变化只更新对应字段。
差异更新还要处理排序。房主固定第一位,其他成员按加入顺序或队伍位置排列。如果每次事件都重新排序,玩家会觉得列表跳。排序规则要稳定,只有房主变化、位置交换或明确队伍调整时才移动条目。
头像和皮肤资源加载也要绑定成员版本。成员 A 离开后条目复用给成员 B,如果 A 的头像异步回调晚到,不能覆盖 B。ItemView 绑定时记录 memberId 和 version,回调返回时校验,长列表里的经验在大厅同样适用。
邀请链路要有过期和撤销
邀请不是发出去就结束。被邀请人可能已进别的房间、版本不一致、服务器不同、邀请过期、房间已满、房主取消、网络断开。客户端要展示邀请状态:已发送、等待中、已接受、已拒绝、已过期、失败原因。
如果邀请没有过期时间,玩家可能点到几分钟前的旧邀请,进入一个已经不存在或目标变化的房间。服务端应返回邀请 ID 和过期时间,客户端本地显示倒计时或过期态。房间状态变化后,比如开始匹配,也要撤销未处理邀请。
邀请入口还要防骚扰。短时间内重复邀请同一玩家,应有冷却;被拒绝后不要立即再次弹出;黑名单和隐私设置要在客户端表现上尊重。否则组队系统会变成打扰系统。
准备状态和匹配状态要分开
很多混乱来自把“已准备”和“正在匹配”混成一个按钮状态。准备表示成员同意开始,匹配表示房间已经向服务端提交队列。准备阶段成员还能取消或调整,匹配阶段通常要锁定。
客户端状态可以分成 Idle、ReadyPending、Ready、MatchSubmitting、Matching、MatchFound、EnteringGame、Failed。每个状态允许的操作不同。比如 Matching 中不能换角色,MatchFound 后不能踢人,EnteringGame 时只能显示加载状态。
失败后也要回到明确状态。匹配失败是回到 Ready 还是 Idle,取决于服务端规则和产品设计。不要让按钮显示“取消匹配”,实际房间已经不在匹配队列。
全量快照是最后保险
事件流可能丢失或乱序,尤其弱网和重连后。多人大厅需要全量快照作为修正手段。当客户端发现事件版本不连续、成员不存在却收到状态变化、房主 ID 不在成员列表中,就应该请求全量快照。
全量快照回来后,要和本地 pending 操作协调。比如自己刚点准备,还没收到确认,快照里仍然未准备。客户端可以保留 pending 显示一小段时间,等待确认;如果服务端明确拒绝,再回退。否则快照会让玩家看到按钮来回跳。
重连后优先拉快照,而不是重放所有历史事件。大厅状态规模小,全量同步成本低,可靠性更高。
UI 文案要反映真实状态
多人大厅里,按钮文案比想象中重要。“准备”“取消准备”“匹配中”“进入中”“等待房主”“版本不一致”都对应不同状态。如果客户端为了省事只显示一个灰色按钮,玩家不知道自己能不能操作,也不知道在等谁。
状态文案要具体到可行动。成员版本不一致,就提示需要更新;队友未准备,就显示等待某成员;房主正在匹配,就显示等待匹配结果;邀请过期,就允许重新邀请。具体文案能减少语音或聊天里的困惑。
本地乐观更新要有限度
自己点准备后立即显示 pending 是好的,但不要把所有成员状态都乐观更新。比如房主点开始匹配,客户端不能假设一定成功并直接切 Loading。服务端可能因为成员未准备、版本不一致、房间已解散而拒绝。关键状态必须等服务端确认。
乐观更新适合低风险、可回退的视觉反馈,不适合权威流程推进。按钮按下、加载动画、短暂禁用可以本地做;进入匹配队列、扣门票、锁定阵容必须等确认。
聊天和语音状态不要绑死大厅
多人大厅经常集成文字聊天和语音。成员离开、重连、进战斗时,聊天和语音状态也要同步。不要让大厅销毁后语音频道还留着,或者重连后 UI 显示成员离开但语音仍在线。
语音状态可以作为成员状态的一部分展示,但底层连接由语音模块管理。大厅状态变化通知语音模块加入、离开或静音,语音模块回报连接状态。两者通过事件协作,不要互相直接改内部状态。
跨服和版本差异要早提示
如果组队支持跨服,房间状态还要包含服务器、区服、版本和玩法规则。玩家接受邀请前,客户端就应该判断是否可加入。不要等进入房间后才发现版本不同或玩法未开放。
版本差异尤其常见。新版本玩家邀请旧版本玩家,旧版本可能看不到新玩法。服务端应返回不可加入原因,客户端用清楚文案展示,并提供更新入口。这样比“加入失败”更能减少困惑。
大厅要能承受社交压力
多人大厅不是单机菜单,它承载的是几个人的共同等待。一个人的状态显示错误,会影响整队判断;一个人的按钮卡住,其他人也会等。客户端要把“我自己的操作反馈”和“全队状态可见”同时做好。
因此大厅日志也要按房间维度记录。成员变化、房主变化、准备提交、匹配提交、失败原因都带 roomId 和事件版本。线上出现“队友明明准备了我这里没显示”时,客户端和服务端可以对齐同一房间时间线,而不是各查各的本地日志。
观战和补位会放大复杂度
如果大厅后续支持观战、机器人补位或中途加入,状态模型更要提前留扩展点。观战者不是普通成员,机器人可能没有网络状态,中途加入需要额外加载阶段。与其在 UI 里不断加特殊判断,不如在成员模型里明确 memberType、capability 和 lifecycle。这样新角色进入大厅时,既能复用列表,又不会污染普通成员逻辑。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。