第 31 章 · Lag 补偿的可行边界
这一章解决:命中、抓取、区域判定遇到延迟时,哪些可以近似补偿,哪些不能写成公平承诺。
先看一下
轻实时玩法里,玩家 A 开枪时在自己屏幕上瞄准了敌人。请求到达 GameState Owner 时,敌人已经移动了一段距离。如果 Owner 用「收到请求那一刻」的位置判定,A 会觉得明明瞄到了却没命中;如果完全信 A 上报的命中,又会给作弊和不同步留下空间。
这一章会拿到什么
- Lag 补偿原模型需要的两个前提:权威服务器 + 历史状态缓冲
- VRChat 里四类判定的可行边界:hitscan、投射物、抓取、区域触发
- 一份「接受客户端命中报告前先检查什么」的片段
- 一张玩法取舍表:合作 PvE、派对、PvP 分别怎么放宽
依赖前面
- 第 26 章的轻实时范式和子弹本地预演
- 第 29 章的本地预演边界
- 第 30 章的 Owner 确认回包
- 第 20 章的版本号与请求幂等
原模型需要什么
Section titled “原模型需要什么”传统 Lag compensation(延迟补偿)的核心是服务器历史,而不是开枪客户端的口头报告。服务器保存一段历史状态;收到开火命令后,服务器估算开枪者当时看到的是哪一个历史时间点,把目标碰撞体临时回退到那个时刻做命中检测,再恢复到当前状态。
这个模型至少需要两件事。
第一,服务器是最终权威。客户端只能上报输入,不能决定自己命中了谁。第二,服务器有统一的历史状态缓冲,能回答「100 ms 前目标在哪里」。
VRChat 世界缺这两个前提。GameState Owner 是玩家客户端;每个对象的 Owner 分散在不同玩家手上;Udon 没有自动保存全局历史状态的机制。因此本章只讨论近似:在哪些玩法里可以接受客户端报告,报告前做哪些合理性检查,哪些玩法不应该承诺公平命中。
四类判定的边界
Section titled “四类判定的边界”| 判定类型 | VRChat 近似 | 风险 |
|---|---|---|
| hitscan | 发起者本地射线命中,向 Owner 上报目标和命中点,Owner 做距离 / 角度 / 冷却检查 | 客户端可伪造命中,需要限制用途 |
| 投射物 | 本地预演飞行,Owner 只裁决伤害或结果 | 不同客户端看到的弹道可能不同 |
| 抓取 / 拾取 | 依赖对象 Owner 和 VRC Object Sync,必要时请求转移 Owner | 物理不同步时会出现抢夺错位 |
| 区域判定 | Owner 或本地客户端检测进入区域,再由 GameState 验证阶段和位置范围 | 高频进入 / 离开会撞事件速率和边界抖动 |
这张表的用法是先定玩法承诺。合作 PvE 可以接受「客户端报告命中 + Owner 合理性检查」,因为目标是顺滑和可玩。竞技 PvP 不适合这样写成公平命中,因为被命中的一方没有可信服务器保护。
hitscan:可用但要降承诺
Section titled “hitscan:可用但要降承诺”hitscan(瞬时射线命中)最像传统 Lag 补偿的场景。VRChat 近似版通常让发起者本地先射线检测,然后把命中报告发给 GameState Owner。
public void FireLocalHitscan(){ if (pendingShotRequestId >= 0) return;
int reqId = NextShotRequestId(); RaycastHit hit; bool hitSomething = Physics.Raycast(muzzle.position, muzzle.forward, out hit, maxRange);
PlayLocalShotFx(hitSomething, hit.point); SendShotReport(reqId, hitTargetId, hit.point, muzzle.position, muzzle.forward);}Owner 端不能直接接受报告。至少做四个检查:请求是否重复、冷却是否满足、距离是否离谱、角度是否大致合理。
private bool AcceptShotReport(ShotReport r, VRCPlayerApi shooter){ if (r.requestId <= lastProcessedShotId[shooter.playerId]) return false; if (TooSoonToShoot(shooter.playerId)) return false; if (Vector3.Distance(r.origin, r.hitPoint) > maxRange + rangeSlack) return false; if (Vector3.Dot(r.direction.normalized, (r.hitPoint - r.origin).normalized) < minAimDot) return false; return TargetStillAllowed(r.targetId);}它的定位是合理性检查:把明显不可能的报告拒绝掉,把正常延迟下的报告放行。反作弊能力要等第 33 章单独讨论。
投射物:同步结果,不同步每一帧
Section titled “投射物:同步结果,不同步每一帧”投射物更适合「本地预演 + 结果确认」。所有客户端都同步一颗子弹每帧的位置,成本高且容易错位。第 26 章已经把子弹轨迹列为本地预演,不同步。
一颗火球可以这样写:发起者本地生成视觉弹道;同一时间向 Owner 发送 requestId、起点、方向、技能 ID;Owner 检查冷却和资源后接受;命中发生时发起者或目标对象报告命中;Owner 最终写 HP / 分数同步字段,并播确认事件。
这条链路不会让每个客户端看到完全相同的火球轨迹。它只保证最终共识字段一致:谁掉血、分数加多少、敌人是否死亡。
如果火球本身是玩法核心(例如需要躲弹幕),就要降低弹幕数量、降低速度、扩大碰撞体、允许「擦边不算」的宽容区间,别把它设计成高精度竞技弹道。
抓取和区域判定
Section titled “抓取和区域判定”抓取 / 拾取首先是 Ownership 问题。对象当前 Owner 决定它的同步空间状态;抢夺时先处理 Owner 转移,再处理表现。VRC Object Sync 会同步位置 / 旋转和少量基础物理状态,但不等于同步完整自定义物理规则。第七部会单独展开对象同步和物理。
区域判定通常更稳。合作防守里的「进入治疗圈」「站在占点区域」不需要回退 100 ms。用 Owner 或本地检测进入区域,再让 GameState 检查阶段、队伍、区域 ID 和节流即可。判定形状可以做大一点,边界上加 0.2 到 0.5 秒滞后退出,减少网络和物理抖动造成的来回跳。
区域玩法比精确射击更适合 VRChat 的多人约束。这属于玩法承诺与平台能力匹配。
挑一条试。
- 把 hitscan 的
minAimDot调宽。命中更顺后,误判会增加到什么程度? - 设计一个区域技能:站在圈内 1 秒后回血。它需要 Lag 补偿吗?哪些字段要同步,哪些只做本地表现?
| 场景 | 预期 | 实际 |
|---|---|---|
| 1. 低延迟命中敌人 | 本地先播命中特效,Owner 确认后 HP 下降 | … |
| 2. 超出距离上报命中 | Owner 拒绝,HP 不变,本地预演撤销或自然结束 | … |
| 3. 冷却中连续开火 | 后续报告被拒绝,lastProcessedShotId 推进 | … |
| 4. 迟入玩家加入 | 不补播历史子弹,只看到敌人当前 HP / 死亡状态 | … |
| 5. 两人同时命中同一目标 | Owner 按请求序号 / 当前 HP 裁决,不让 HP 跌破下限 | … |
- 能说出传统 Lag 补偿需要权威服务器和历史状态缓冲
- 能解释为什么 VRChat 里只能做近似补偿
- 能给 hitscan 命中报告列出至少 4 个合理性检查
- 能区分投射物的视觉轨迹和最终伤害状态
- 能判断一个玩法是否应该避开高精度 PvP 命中承诺
- Valve Developer Community · Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization(访问日期:2026-06-17)— 延迟补偿、历史状态缓冲、命中回退的原模型。
- Gabriel Gambetta · Client-Side Prediction and Server Reconciliation(访问日期:2026-06-17)— 客户端预测和服务器确认的基础模型。
- VRChat Creator Docs · Networking and Synchronization — Owner 和同步变量的官方说明。
- 第 26 章 · 轻实时范式 — 子弹本地预演和状态共识的前置章节。
- 第 30 章 · Owner 伪权威与确认回包 — 请求确认链路。