第 8 章 · 玩家进入房间后,系统要给他什么
这一章解决:在写任何同步代码之前,先把「一位玩家从进入到离开,房间要给他什么」拆清楚,避免后面把 per-player 状态硬塞进全局字段。
先看一下
闭环 1 里 lobbyReady 是全局一份 bool:A 按 Ready 后整个房间是 READY,B 按 Ready 又翻回 NOT READY。两位玩家都按过,房间却显示未准备。类型 bool 是合适的;问题在于它在 GameLoopMaster 上只有一份。每位玩家应当有自己的一份 Ready 状态。
把这个观察往上推一层:每位玩家在房间里有一组属于他自己的状态,Ready 只是其中一项。这一章把它们一次性列出来。
这一章会拿到什么
- 一份「玩家进入到离开」六阶段生命周期图,每阶段配一类要给的东西
- 六类 per-player 状态的对照表,每类附「适合放哪、为什么不能放全局」
- 一份「玩家上下文检查表」,写第 9 章
VRCPlayerObject之前用它做需求清单
依赖前面
- 第 1 章四象限里的「私有」与「共享 × 持续」两档
- 第 5 章迟入恢复的四类判断
- 闭环 1 的缺陷 2(
lobbyReady全局共用)
一位玩家在房间里走的六个阶段
Section titled “一位玩家在房间里走的六个阶段”把第 0 章设想里的 4 人合作防守原型当画布。新玩家从打开 VRChat 到离开实例,会经过六个阶段。每个阶段对系统都有一类诉求。每个阶段标记的是「这位玩家此刻需要被记的什么东西」,不一定对应一块 UI。
进入实例 ─→ 身份建立 ─→ 加载等待 ─→ 大厅 ─→ 比赛 ─→ 离开 / 重连 │ │ │ │ │ │OnPlayer playerId OnPlayer isReady HP/score OnPlayerJoined 可用 Restored teamId skillCD Left阶段 1:进入实例
Section titled “阶段 1:进入实例”VRChat 在玩家加入时调用所有 UdonBehaviour 的 OnPlayerJoined(VRCPlayerApi player)。这一刻系统要决定:
这位玩家在这局里叫什么 ID。VRChat 的 playerId 跨客户端可比,直接用。这位玩家是不是 Master 一般可以暂不关心,因为 Vol.2 不再围绕 IsMaster 设计权威(第 3 章已经讲过)。这位玩家要不要拿到一份 per-player 对象,是第 9 章的主题。
阶段 2:加载等待
Section titled “阶段 2:加载等待”启用了 VRCEnablePersistence 的世界,玩家进来后还要等持久数据从云端拉回。OnPlayerRestored(VRCPlayerApi player) 是这一刻的回调。在它触发之前,玩家的持久字段不能被读,否则可能读到默认值。
本卷不展开持久化。Vol.5 是它的主轴。这里只标位置:有持久化的世界多一个等待阶段,没有的世界跳过。
阶段 3:大厅
Section titled “阶段 3:大厅”玩家进了大厅 UI。要给他的:一个能切换的 Ready 状态,只属于他自己;一个能切换的队伍 / 角色(如果有);以及看到其他玩家 Ready / 队伍状态的视图。闭环 1 的缺陷 2 卡在这一步:Ready 没分离。
阶段 4:比赛
Section titled “阶段 4:比赛”InGame 期间,玩家自己要被记的字段通常包括:当前角色 / 装备 / 技能冷却(在合作防守里就是炮塔类型、剩余血量、技能 CD);个人分数 / 击杀 / 助攻;他向 GameState 发出的请求队列。这些字段不是大家共享的全局状态,是「这位玩家自己拥有的状态」,其他玩家只读、不写。
阶段 5:结算
Section titled “阶段 5:结算”InGame 结束。每位玩家有自己的结算视图:本局战绩、是不是 MVP(这一项是 GameState 算的,每位玩家只读)、长期奖励(接 Vol.5)。
阶段 6:离开 / 重连
Section titled “阶段 6:离开 / 重连”VRChat 在玩家离开时调用 OnPlayerLeft(VRCPlayerApi player)。系统要决定:这位玩家拥有的 per-player 对象怎么办(第 9 章会讲,VRCPlayerObject 自动销毁);这位玩家拥有的非 per-player 对象怎么办(第 14 章三类恢复路径);他在 GameState 里留下的请求 / 排队 / 队伍位置怎么办。
如果他重连回来,会话内重连应当能恢复(第 14 章),跨会话重连走持久化(Vol.5)。
房间要给玩家的六类东西
Section titled “房间要给玩家的六类东西”把六阶段折叠一下:不论哪个阶段,玩家最多要被记六类东西。
| 类别 | 例子 | 适合放哪 | 为什么不能放全局 |
|---|---|---|---|
| 1. 身份记录 | playerId、displayName 缓存 | PlayerObject 或 GameState 玩家列表 | 全局存一个 int playerId 就只能记一位 |
| 2. 准备状态 | isReady、isLoaded、isAFK | PlayerObject(per-player) | 闭环 1 缺陷 2 已经演示过 |
| 3. 队伍 / 角色 | team=Red, role=Engineer | PlayerObject | 全局只能存房间里第一个选完的那位 |
| 4. 输入通道 | 按键请求、技能请求、加入队伍请求 | PlayerObject 上的请求字段 + GameState 处理 | 第 12 章请求式架构展开 |
| 5. 个人 UI | 鼠标在哪、面板开没开、本地提示 | 完全本地(档「私有」) | 同步浪费带宽,其他玩家看不见无意义 |
| 6. 持久恢复信号 | OnPlayerRestored 后的 isRestored 标志 | PlayerObject 上的标志位 | 全局标志会被多位玩家撞 |
类别 4 是第 12 章的主题。类别 5 纯本地,不进入网络层。其余四类都需要 per-player 的同步存储,第 9 章的 VRCPlayerObject 会成为它们的物理载体。
玩家上下文检查表
Section titled “玩家上下文检查表”写第 9 章 VRCPlayerObject 之前先答一遍下面 7 题。每题对应一类要决定的事,答完就有了 PlayerObject 字段集合的初稿。
[ ] 身份:除了 playerId,本局还要不要记 displayName 缓存? (Owner 离开后还想显示名字时需要)[ ] 准备:Ready 是 bool 还是 byte(多档准备)?[ ] 队伍:有没有队伍 / 角色选择?数量多少?[ ] 比赛态:玩家自己有几个会变的字段(HP、score、CD、装备)?[ ] 输入:玩家会发起哪几类请求(按按钮、放技能、加入队伍)?[ ] 个人 UI:哪些 UI 状态严格本地?[ ] 持久:本卷一律先填「无」,等 Vol.5 再回来。把答案贴进项目里的 design/player-context.md(如果还没有就建一个)。后续每多想出一类 per-player 字段,先回这张表上加一行,再去改 PlayerObject 代码。
挑一两条试试。每条不要直接翻第 9 章,先自己想一遍再对答案。
- 把六阶段图画到自己当前在做的某个 VRChat 项目上。每阶段问一句「这一步系统要记这位玩家什么」。空白的阶段意味着这种玩法不需要 per-player 状态。
- 把闭环 1 的
lobbyReady在脑里挪到「PlayerObject 上的bool isReady」。NetToggleReady要写成什么样?这次写操作的 Owner 是谁? - 给阶段 4「比赛」想一组炮塔技能 CD 字段。如果你把它放进 GameState 而不是 PlayerObject,会发生什么具体的现象?
本章不写代码,给一份审计矩阵:拿一个现成的 VRChat 项目(自己的或闭环 1)按下面四问扫一遍。
| 检查项 | 期望 | 实际 |
|---|---|---|
全局字段里有没有「单数 per-player」嫌疑(如 bool isReady、int currentPlayerId 当唯一存储) | 没有,或被识别出来 | … |
OnPlayerJoined 里有没有立刻读 PlayerObject 字段 | 没有 | … |
OnPlayerLeft 里有没有清掉这位玩家的 GameState 痕迹 | 有,或被识别为缺失 | … |
启用了 VRCEnablePersistence 的字段读取是否在 OnPlayerRestored 之后 | 是,或被识别为缺失 | … |
- 能把六阶段生命周期默写出来,并指出每阶段对应的 VRChat 回调
- 能解释为什么
lobbyReady写成全局bool是错位 - 能区分类别 5(个人 UI 本地)和类别 1–4(per-player 同步)
- 能复述
OnPlayerJoined不是「立刻可用」的两条理由
- VRChat Creator Docs · Networking and Synchronization:
OnPlayerJoined/OnPlayerLeft与同步总览。 - VRChat Creator Docs · VRCPlayerObject:PlayerObject 生命周期、Owner 锁定、
OnPlayerRestored的官方说明。 - VRChat Creator Docs · Persistence:本卷只引边界,细节在 Vol.5。
- 闭环 1 · 单 Master Owner 的极简一局:缺陷 2 的现场。
- 附录 · 术语表:
Player Lifecycle、PlayerId、Per-Player State的客观定义。