跳转到内容

第 37 章 · VRCObjectSync 与物理对象

约 6 分钟 难度:4 动手章

这一章解决:什么时候用 VRCObjectSync,什么时候写自定义状态,什么时候把物理效果降级成本地表现。

先看一下

一个球被玩家踢出去,所有人都看到它滚到同一个角落,这是 VRCObjectSync 擅长的场景。另一个场景里,玩家用高速投射物打弱点,命中公平决定胜负,这就不是同一类问题。物理同步不是「让物理变公平」,它只是把对象的 Transform / Rigidbody 状态复制给其他客户端。

这一章会拿到什么

  • VRCObjectSync 适合与不适合的对象类型
  • 物理对象的 Owner 写权和转移边界
  • 瞬移、重置、归还对象池时的处理顺序
  • 一份「精确物理别做」的判断清单

依赖前面

  • 第 3 章的 Owner 与 Master 区分
  • 第 5 章的空间兜底
  • 第 32 章的位置修正和插值边界
  • 第 36 章的对象池生命周期

这是 difficulty 4 章节。只想先完成最小主项目时,可以先跳到第八部的闭环,再回来看本章的高风险系统。


官方 VRCObjectSync 的定位很窄:同步一个 GameObject 的 Transform 位置和旋转,并配合 Rigidbody 做简单物理对象同步。它不负责 Udon 字段,不负责 HP,不负责分数,也不负责命中裁决。

对象是否适合原因
可拾取箱子适合位置就是主要状态
被推倒的障碍物适合迟入玩家看到当前位置即可
低频移动平台可用,但要小心 Owner 和插值轨迹最好可预测
高速子弹通常不适合高频位置同步成本高,命中仍不公平
竞技球类核心判定风险高物理分歧会影响胜负
装饰性碎片不建议同步本地特效即可

把它放进第 5 章的恢复分类:空间兜底。迟入玩家需要看到「这个箱子现在在哪里」,VRCObjectSync 是标准工具。迟入玩家需要看到「这个敌人还剩多少 HP」,那是对象自己的同步字段,不是 VRCObjectSync


物理对象也有 Owner。Owner 客户端负责把它的空间状态同步出去。其他客户端看到的是远端状态,不应该同时写同一个 Rigidbody。

public void TryKickBall(Vector3 impulse)
{
if (!Networking.IsOwner(gameObject))
Networking.SetOwner(Networking.LocalPlayer, gameObject);
if (!Networking.IsOwner(gameObject)) return;
rb.AddForce(impulse, ForceMode.Impulse);
}

这段保守地处理 SetOwner:发出请求后不立刻假定成功。更完整的写法是在 OnOwnershipTransferred 后执行待处理动作。物理对象尤其要避免「刚请求 Owner 就马上 AddForce」的乐观写法,因为旧 Owner 和新 Owner 同时写,表现会抖。

拾取物可以允许持有者成为 Owner;共享机关和球类要写清 Owner 规则:谁最后交互、谁在区域内、是否回到 GameState Owner。Owner 规则越频繁变,越容易产生 ownership churn。


物理对象经常需要重置:回合开始时球回到中心,掉出地图后回出生点,对象池归还时藏回池中。重置不是普通移动。普通移动可以插值,重置应该让远端知道「这里有一次不连续跳变」。

public void ResetBall()
{
if (!Networking.IsOwner(gameObject)) return;
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
transform.SetPositionAndRotation(spawnPoint.position, spawnPoint.rotation);
objectSync.FlagDiscontinuity();
}

官方组件提供了处理不连续移动的接口。实际项目里可以按组件 API 使用 TeleportToFlagDiscontinuity,目标是避免远端把重置位置当成一段长距离插值,出现物体从地图一端拖着尾巴滑回出生点的效果。


VRCObjectSync 可以让球在大家眼里大致同位置,但不保证每个客户端同一帧碰撞同一个目标。物理回调本地触发,网络延迟和模拟差异都会影响结果。

合作防守里可以这样处理:子弹轨迹本地播放,命中请求交给 GameState Owner 检查范围和冷却,最终 HP 走同步字段。物理只负责表现,规则由 Owner 裁决。

对于简单机关,例如箱子压住按钮,可以把「按钮是否被压住」降成低频状态:Owner 每 0.2 秒检查一次箱子是否在区域内,然后写 [UdonSynced] bool isPressed。远端不需要每帧精确知道碰撞细节,只需要知道按钮现在是否成立。


VRCObjectSync 的对象也可以放进对象池,但归还时要清理物理状态。

清理项原因
线速度 / 角速度避免下一次取出继续飞
Owner避免旧玩家离开后对象卡住
Transform回到池内初始点或新生成点
kinematic / gravity回到该对象类型的默认物理状态
Udon 状态HP、阶段、目标等不能残留
插值不连续标记避免远端长距离拖尾

物理对象池比普通对象池更容易出残留。第 36 章的 OnSpawned 清单在这里要加上 Rigidbody 和 VRCObjectSync


情况更稳的做法
命中公平决定胜负改成 Owner 裁决的低频请求
物体高速且数量很多本地预演 + 命中事件,不同步每个投射物
只是爆炸碎片或装饰本地特效,不同步
Quest 需要稳定帧率减少活跃 Rigidbody 和同步对象
物体必须严格确定性避开 VRChat 物理同步,改成状态机

物理同步适合「大致一致」的共享物体,不适合承诺严格一致。把这条边界写进玩法设计,比后面补一堆修正代码更稳。


挑一条试。

  • 给一个球加 Reset 按钮。重置时如果不清 velocityangularVelocity,下一次会发生什么?
  • 把一个高速投射物从 VRCObjectSync 改成本地预演 + 命中请求。哪些状态还需要同步?
场景预期实际
1. 玩家拾取并移动物体Owner 写空间状态,远端看到位置更新
2. 物体掉出地图后重置速度清零,位置瞬移,不出现长距离插值拖尾
3. Owner 离开对象能被新 Owner 接管或回到重置点
4. 迟入玩家进入看到物体当前 Transform,而不是初始位置
5. 多个物理对象同时活跃记录帧率、拥塞和远端抖动,决定是否降级
  • 能说明 VRCObjectSync 只同步空间状态
  • 能区分物理表现和规则判定
  • 能解释 SetOwner 不是瞬间成功
  • 能写出重置物理对象的清理顺序
  • 能指出一种该放弃网络物理的场景