跳转到内容

第 7 章 · 网络 Debug 面板与最小测试矩阵

约 7 分钟 难度:1 动手章

第 1-6 章建立了一整套概念:四象限状态、四种通道、Owner / Master、生命周期、迟入恢复、网络预算。这些概念能不能在自己的项目里跑通,必须能动手测。这一章给一份「最小测试矩阵」,后面每一章写完同步代码,都按这份矩阵跑一遍。


VRChat 客户端默认不显示网络调试面板。要打开:

桌面端启动参数

--enable-debug-gui

通过 Steam 启动:右键 VRChat → 属性 → 启动选项,加上这一行。

进入实例后,按住 Right Shift + 反引号 `,再按数字键 1-9 打开对应面板。

第一部反复用到的三个:

面板显示内容
Debug View 6列出所有 networked object:Owner、size、BPS、ping
Debug View 7同 6,按「网络影响」排序
Debug View 8在所有 synced object 上叠加面板:Owner ID、ping、收包质量百分比、held / sleeping 状态

第 6 章讲的字节预算,跑到 Debug View 6 里能看到每个对象实际占用的 BPS。理论估算和实测对比,是排查同步问题的第一步。


VRChat SDK 的 Control Panel 里有个 Build & Test 按钮。它会把当前世界打包成本地客户端可加载的格式,然后启动 VRChat 直接加载。

要测同步必须开两个客户端。两种方法:

方法一:同一台机器开两个客户端。 在 SDK Builder 面板把 Number of Clients 设为 2,再点 Build & Test。Unity 会启动两个 VRChat 客户端,它们会进入同一个本地测试实例。

方法二:Build & Test 配合 Editor Play Mode 辅助观察。 Editor Play Mode 或 ClientSim 适合看本地逻辑、Inspector 和 Console,但不能替代真实客户端的网络同步测试。涉及同步变量、Custom Network Event、Master / Owner 行为时,以 Build & Test 启动的真实 VRChat Client 为准。

几件事要先知道:

  • Editor / ClientSim 里的 Networking.LocalPlayer、实例创建者判断、网络时序都不能完整代表真客户端,涉及实例创建者判断时必须用 Build & Test 的真客户端。
  • 不要用 Editor Play Mode 的结果判断跨客户端送达。官方 Build & Test 文档明确把 Synced Variables、Custom Network Events、多玩家行为归到真实客户端测试场景。
  • 多客户端共用一台机器麦克风会互相串音,测语音相关功能要至少两台机器。

写完一段新的同步代码,跑这五个场景。每章后续的「踩坑预警」基本都对应矩阵里某一行。

测试 1 · Owner 端 → 远端基本同步

Section titled “测试 1 · Owner 端 → 远端基本同步”

最基础的:Owner 改字段,远端能不能看到。

步骤:

  1. 客户端 A 触发改动(按按钮、调滑块等)。
  2. 客户端 B 看是否在 1 秒内同步到。
  3. 打开 Debug View 8,看对象上是否显示「P=ping、Q=100%」之类的健康指示。

通不过就回头查这几项:

  • UdonBehaviour 的 Sync Method 是不是 None。
  • [UdonSynced] 标记有没有漏。
  • 改字段前是不是 Owner。
  • Manual 模式下有没有忘记 RequestSerialization

第 5 章的核心场景。

步骤:

  1. 客户端 A 进入实例,触发若干状态变更(按几次按钮、推怪几下)。
  2. 不要让 A 退出,让客户端 B 后进来。
  3. 检查 B 看到的状态:UI、灯光、敌人位置、波次数字,是否都和 A 一致。
  4. 检查 B 的 Console(如果是 Editor),有没有「OnDeserialization 触发了多少次」的日志。

通不过就回头查这几项:

  • 这个状态是不是档①但用了网络事件实现。
  • OnDeserialization 里有没有依赖未同步的字段。
  • [UdonSynced] 字段的初值有没有给合理默认值。

第 3 章的场景。

步骤:

  1. A 和 B 一起进入实例,A 是初始 Master 兼默认 Owner。
  2. A 触发若干同步变更,B 端看到正确状态。
  3. A 强制退出(关掉 A 的客户端,正常从 VRChat 主菜单退出即可,避免 ALT-F4 跳过某些清理回调)。
  4. B 这边观察:
    • OnPlayerLeft 是否触发?
    • 之前 Owner 是 A 的对象,现在 Owner 自动转给了谁?(应该是 B,因为 B 此时是新 Master)
    • B 的 OnOwnershipTransferred(player) 是否触发?
    • 同步状态有没有被卡在中间值?

通不过的典型表现:AI 怪不动了,多半是 AI 状态机依赖 Master 而非 Owner;分数倒退,多半是新 Owner 接管时同步字段被默认值覆盖。

Networking.SetOwner(Networking.LocalPlayer, gameObject);

步骤:

  1. A 是当前 Owner。
  2. B 触发抢权(按一个 Pickup、按一个按钮等)。
  3. 两边都应该收到 OnOwnershipTransferred(player == B)
  4. B 立刻改字段,A 端能否看到。
  5. A 立刻再改字段,看会不会被同步出去(不应该,B 才是新 Owner)。

人为拉一下带宽,看代码有没有崩。

步骤:

  1. 在某个同步对象上接一个测试按钮,让它在 1 秒内连续 100 次 RequestSerialization,每次写一个不同字段。
  2. 触发后看:
    • OnPostSerialization 报告 success == false 的次数。
    • Networking.IsClogged 是否进入 true 状态。
    • 远端 B 接收到的最终值是否是最后一次提交的值。
  3. 测试结束,确认正常逻辑(按钮、UI)能恢复。

通过的标准是「拥塞期间逻辑变慢但不崩」。


把这份模板放进每个同步对象的注释里,写完每章功能填一遍。

=== Sync Test Matrix · GameState ===
[ ] Test 1: Owner change → remote sees within 1s
[ ] Test 2: Late joiner gets current state correctly
[ ] Test 3: Owner leaves → ownership transfers, no value drift
[ ] Test 4: SetOwner → both sides receive OnOwnershipTransferred
[ ] Test 5: 100x RequestSerialization in 1s → no crash, logical state intact
Notes:
-

第 1 章拆状态时给状态分类是案头作业。这份测试矩阵是落地后的实际验证,两边对得上才算同步设计走通了。


把第 2 章实现 C 的同步按钮加进项目,按照测试矩阵跑五个测试。每个测试结果写一行:通过 / 不通过 / 不确定。

不通过和不确定的项就是后续章节要回头处理的。

进阶:在按钮上加一个「连续 50 次按下」的测试按钮(自动用 SendCustomEventDelayedFrames 触发),看测试 5 的拥塞表现。比较 Continuous 和 Manual 两种模式下的差异。


到这里第一部七章的具体内容讲完了。第 1 章把状态拆成四象限:先拆状态,再选通道。第 2 章给出四种通道(Local / Event / Variable / ObjectSync),默认按 A → D 顺序选,能停就停。第 3 章说清楚 Owner、Master、Local Player 三个判断回答的不是同一个问题。第 4 章画出手动同步的生命周期:请求 → OnPreSerialization → 发送 → OnPostSerialization,远端 OnVariableChanged / OnDeserialization第 5 章说迟入只复制状态、不重放事件,按四档分类。第 6 章给出三个网络数字(200B / 49KB / 约 11 KB/s),「能塞进去」和「持续发得起」是两件事。第 7 章给出测试矩阵五项,每写完同步代码跑一遍。

第一部理解章会把这七章揉成一个核心心智模型:「同步不是广播,是状态复制」。

第二部开始写真实的对象架构:玩家进入房间时系统给他什么、VRCPlayerObject 的位置、GameState 的拓扑设计。