跳转到内容

理解 D · 选哪条通道

约 7 分钟 难度:3

这一章抬升一层:第四部六章 + 闭环 3 + 对照小练 C 走完后,把通道选择从「七选一」抬升到「五个压力推出哪一条」。下一步进入第五部「玩法范式」。


通道选择不是「找最好那条」,是「让这一段同步背负的压力把通道推出来」。

第 21 章决策表是七条通道的横向对比。但读者真正要做的不是在七条之间比,而是把当前需求的五种压力(迟入 / 字节 / 频率 / 调试 / 扩展)想清楚,剩下的通道几乎自动浮现。


每一段同步同时承受这五种压力。每种压力的强度决定通道往哪一边偏。

迟入玩家进来时这段状态需不需要看到。

  • 必须看到:通道要支持状态恢复。事件类(SendCustomNetworkEvent / 参数化)出局,剩下五条同步字段类。
  • 不需要看到:所有通道都行。瞬时反馈(飘字、音效、特效)通常落在事件类。

单次同步的字节大小。

  • 几个 byte:直接 [UdonSynced] 简单类型,最省事。
  • 几十到几百 byte:基础类型 + 数组 + JSON 都行。
  • 几 KB:JSON 字符串是首选;如果还需要高频改动,拆字段或上字节打包。
  • 几十 KB:必须拆字段或走 manual sync 多对象分摊;不能用单字段。

每秒改几次。

  • 每秒一次以下:什么都行。
  • 每秒几次到几十次:避免 JSON 字段(CPU 解析代价),也避免把整对象当作一个大字段反复重发,倾向命令同步「最近一条」或字段分散。
  • 每帧改动:物理类用 VRC Object Sync;非物理慎用,要算 11 KB/s 总预算。

这一段出 bug 时调试有多难。

  • 调试简单:状态字段直接读 Debug View 就能看;JSON 字段读字符串。这两类适合复杂业务。
  • 调试中等:字段拆得多但每个字段名清晰。本卷闭环 1/2/3 都属此类。
  • 调试困难:位掩码 / 字节打包 / 自定义二进制格式。要在性能瓶颈上才用。

这段需求未来会怎么演化。

  • 不会变:选代码最简单那条。
  • 可能加字段:JSON 天然支持,拆字段要每加一个字段改一处同步定义。
  • 可能扩状态规模(小棋盘到大棋盘、单人到 16 人):命令同步在状态规模扩大时字节代价低。
  • 可能加历史 / 回放需求:命令日志(第 22 章)是顺路加的,状态同步不行。

把五种压力的高 / 低组合落到推荐通道:

高压力组合推荐通道例子
迟入要看 + 字节小 + 频率低[UdonSynced] 基础类型闭环 1/2/3 的 phase / totalScore
迟入要看 + 字节中 + 频率中[UdonSynced] 数组闭环 1/2/3 的 lastProcessedRequestId[]
迟入要看 + 字节大 + 频率低 + 易扩展VRCJson 字段复杂配置、棋盘 metadata、关卡数据
迟入要看 + 字节高 + 频率高命令同步 + 快照多人战场状态(位置走 VRC Object Sync,HP 走命令)
不需迟入 + 字节小 + 频率任意参数化网络事件飘字、音效、按钮反馈
per-player 状态VRCPlayerObject玩家个人分、Ready 状态、角色选择
物理VRC Object Sync抓取的物体、被丢的弹药

这张表不是替代第 21 章决策表,是从「压力」侧而非「通道」侧切入。两张表对照看。


把闭环 3 的几个字段拿出来逐个回看,验证「压力推出通道」的工作方式。

phase:3 种取值(LOBBY/INGAME/RESULT)。迟入要看(必须)、字节极小(1 byte)、频率每局几次。压力组合 → [UdonSynced] 基础类型 → 选了 byte

totalScore:迟入要看、字节小(4 byte)、频率每场几十次。组合 → [UdonSynced] 基础类型 → 选了 int

isReady(在 PlayerLobbyState 上):迟入要看(必须)、per-player 属性、字节极小。组合 → VRCPlayerObject + [UdonSynced] bool

stateVersion(闭环 3 新增):不与 UI 关联但需要稳定单调递增。压力是「让其他字段的 gating 工作」,不是常规 UI 同步。选了 [UdonSynced] intOnPreSerialization 写入。

lastHandleScoreTime[](闭环 3 新增):per-player 冷却时间记忆。迟入不必看(玩家 A 离开后 D 加入不需要看到 A 上次什么时候得分)但 Owner 转移需要看(新 Owner 要继承谁还在冷却)。组合 → [UdonSynced] 数组。

每个字段的通道选择都能从压力推出来。这一过程在写新功能时也一样:先想清楚这一段同步背负的压力,通道几乎自动出现。


第 19 章已经讨论过反向选法的代价。这里再总结一次。

反向选法 1:先决定「我要全部走命令同步省字节」,写到迟入恢复时发现状态不能恢复,硬补一份快照同步。结果是「命令 + 状态」两份都同步,没节省字节,反而多写一段同步代码。

反向选法 2:先决定「我要全部走 JSON 简单」,写到一段字段每秒改 30 次时发现 CPU 卡,硬拆字段。结果是 JSON + 拆字段两套都有。

反向选法 3:先决定「我要全部走参数化事件」,写到迟入玩家看不到任何东西时硬补一份持久化字段。结果同反向 1。

正向选法是:先想压力,再选通道。每一段同步都过一遍五种压力,根据组合自然落到一条通道上。每条通道都有它该承担的场景。


第三部讲了游戏循环(大厅 / 开局 / 计分 / 旁观)。第四部讲了通道选择。这两部加起来给了「一局游戏怎么组织 + 状态怎么同步」的完整答案。

第五部接下来讲玩法范式(回合制 / 轻实时)。范式的差异不是「不同的同步技术」,是「同一套架构(PlayerObject + GameState + 命令版本号)在不同节奏感下的两种参数化」。回合制让 phase 变化频率低、字段稳定;轻实时让 phase 变化频率高、字段动态。每种范式选通道时仍然套五种压力。

第六部体感(预测、补偿、可接受的不一致)会在第四部决策表的基础上加一层「事件类通道是体感优化的载体,不是状态层」。读完第六部回过头来看第四部,会有「事件类的 16 KB 上限到底为什么够用」的体会:因为体感优化只用瞬时反馈,瞬时反馈只用几十 byte 就够。

闭环 3 之后玩法层面够用,第八部主项目(合作防守世界)会把第三 + 第四 + 第五 + 第六部一次性串起来。本理解章是这一串起来的入口。


挑一条试。

  • 拿一段你目前正在做的同步代码(不一定是闭环 3 的),按五种压力打分(每种 1–3 分),看推出来的通道和你实际选的是不是一致。不一致的话原因是什么?
  • 闭环 3 的 lastHandleScoreTime[] 改成「不同步、各客户端自己管」会怎样?这一字段背负的压力清单变不变?决策结果变不变?
  • 第 22 章的命令日志加到闭环 3 上时,命令日志背负的压力清单是什么?为什么本闭环没接它?

  • 能默写五种压力(迟入 / 字节 / 频率 / 调试 / 扩展)
  • 能用五种压力推出闭环 3 各字段的通道选择
  • 能识别三种「先选通道再补缺口」的反向选法
  • 能解释为什么没有「最佳通道」
  • 能说出第五部范式与第四部通道的关系