如何用Deterministic Physics复现碰撞

解读

国内大厂/中厂面试问“Deterministic Physics”时,真正想确认的是:你能否在Unity的非确定性物理系统里,做出一套可在帧同步框架下逐帧一致、可复盘、可防外挂的碰撞结果
考点集中在三点:

  1. 为什么Unity原生物理(PhysX)无法直接复现;
  2. 怎样把“浮点不确定性”降到“整数可接受误差”;
  3. 如何与帧同步、录像回放、服务器校验打通。
    回答必须给出可落地的工程方案,而不是纯理论。

知识点

  1. Unity物理非确定性的根因:浮点指令顺序、多线程解算顺序、不同CPU的FPU精度、PhysX版本差异。
  2. 确定性三要素:定点数(FixedPoint)、确定顺序(Deterministic Order)、确定输入(Deterministic Input)。
  3. 定点数物理引擎
    • 商业:DOTS-Physics-FixedPoint(Unity官方实验包,基于FixedPoint Math);
    • 开源:BEPUphysics v2 FixedPoint分支、TrueSync2(已停更但思路可用)。
  4. 碰撞复现链路:定点数输入 → 定点数物理步进 → 序列化关键帧(位置+旋转+速度+事件ID) → 哈希校验 → 录像/回放。
  5. 性能与精度权衡:定点数32bit(Q16.16)误差≈0.000015,满足移动MOBA;Q24.8误差≈0.003,跑1000刚体仍稳60帧。
  6. Unity集成细节
    • 关闭PhysX,完全使用定点数引擎;
    • 自己写BroadPhase(Sweep&Prune或Spatial Hash),保证排序一致;
    • 定点数射线、定点数八叉树、定点数NavMesh;
    • 渲染层用Unity GameObject做“傀儡”,每帧把定点数坐标乘以scale→float,只做显示,不参与逻辑。
  7. 防外挂:服务器每8帧跑同一份定点数物理,算哈希,与客户端上报哈希不一致则回滚+惩罚。
  8. 国内项目踩坑
    • 安卓ARM64的NEON指令会优化浮点,必须关掉或改用定点;
    • iOS A系列芯片的FMA指令导致交叉平台结果不同,需统一编译宏;
    • 热更新框架(HybridCLR/ILRuntime)里跑定点数数学,必须预先生成AOT delegate,防止JIT回退。

答案

“要在Unity里确定性复现碰撞,我会把整条链路拆成五步:
第一步,彻底抛弃PhysX,引入定点数物理引擎。项目经验里我集成过BEPU v2 FixedPoint分支,把三维定点数(Q16.16)封装成fp类型,重写所有数学运算符,保证跨平台指令级一致。
第二步,锁定更新顺序。把逻辑帧与渲染帧分离,逻辑帧固定60 Hz,用DeterministicScheduler按EntityID升序遍历,消除多线程时序差。
第三步,输入确定性。所有操作(摇杆、技能、爆炸力)在收集后立即量化成定点数,乘以1000取整,再序列化成网络包,确保服务器与客户端输入字节级相同。
第四步,碰撞复现。每逻辑帧调用PhysicsWorld.Step(fp timestep),把刚体状态(位置、旋转、线速度、角速度)存入环形缓冲区;同时计算整局哈希(XXHash64),每8帧上报服务器。回放时直接重放缓冲区,哈希一致即证明碰撞结果可复现。
第五步,性能与精度平衡。移动MOBA场景下,1000个胶囊体、2000次碰撞对,Q16.16定点数在iPhone 12上跑60帧耗时8.3 ms,满足预算;若精度要求更高,可局部切换Q32.32,但把BroadPhase拆成区域,LOD降级。
通过以上五步,我们上线项目实现了99.97%的帧级哈希一致率,剩余0.03%由安卓后台线程浮点泄漏引起,已通过关核+定点数化解决,最终达到可复盘、可反外挂、可跨平台的确定性碰撞复现。”

拓展思考

  1. 混用方案:若项目已重度依赖PhysX,可划定“确定性岛”——只有玩家、技能、关键道具进定点数世界;场景装饰、粒子、布料仍用PhysX,节省迁移成本。
  2. GPU Deterministic:Unity SRP Batcher+ComputeShader里跑定点数,利用uint4模拟Q16.16,已在公司内部Demo跑通10000单位,未来可落地到大战场。
  3. 服务器3D回放:把定点数状态流直接转成protobuf,存ClickHouse,配合Grafana做实时异常监测,运营可秒级定位“闪现穿墙”外挂。