Unity物理系统原理


Unity 3D物理底层是 NVIDIA PhysX(C++编写的专业物理引擎);2D物理使用Box2D。

Unity本身不实现物理计算,C#层的Rigidbody、Collider只是C#代理组件;真正全部物理模拟都跑在底层PhysX C++内部,C#通过桥接调用PhysX接口。

核心组件

  1. Collider(碰撞体) 定义物体物理形状边界,和模型Mesh是两套东西。模型管渲染显示,Collider管碰撞检测。
  • 基础碰撞体:Box、Sphere、Capsule,计算速度很快。
  • MeshCollider:用模型网格做碰撞,性能开销高;动态物体尽量不要用。
  • 勾选Is Trigger → 变为触发器,只检测重叠,无物理阻挡效果。
  1. Rigidbody(刚体) 把物体交给PhysX物理引擎控制;拥有质量、速度、重力、惯性。
  • 没有Rigidbody的物体:属于静态物体,物理引擎认为不会运动,只做碰撞阻挡,不受重力、力影响。
  • IsKinematic:运动刚体,不受力、重力驱动,只能通过代码修改position,但是依然可以参与碰撞交互,适合移动平台、动画物体。

✅碰撞生效必要条件:双方都有Collider,至少一方拥有Rigidbody,否则物理引擎不会产生碰撞回调。

物理系统在游戏主循环的时机

物理是固定时间步模拟,默认FixedTimestep = 0.02s,每秒50次物理步进,和渲染帧率完全解耦。

一帧完整顺序

处理输入
循环执行(如果需要,一帧可多次执行物理步进):
    调用所有脚本 FixedUpdate()      //C#写施加力、修改刚体速度
    PhysX执行完整物理模拟(碰撞检测、求解、更新刚体位置旋转)
    触发 OnCollision / OnTrigger 回调
执行 Update()
执行 LateUpdate()
渲染画面

关键点:物理计算发生在FixedUpdate之后。所有刚体受力操作应该写在FixedUpdate,不要写在普通Update;否则帧率波动会造成物理行为不稳定。

如果设备帧率很低,1帧时间超过0.02s,Unity会连续跑多次FixedUpdate+物理模拟,追赶物理时间;帧率很高时,某一帧可能一次FixedUpdate都不跑。

PhysX物理模拟流水线(单次物理Step)

每一次FixedTimestep,PhysX内部完整跑一遍这套流程:

  1. 收集刚体状态:读取C#层施加的力、冲量、速度、重力。
  2. Broad Phase(粗检测) 用AABB包围盒快速筛选,剔除完全不可能相交的物体对;只留下有可能碰撞的候选对象。快速,粗略,减少后续计算量。
  3. Narrow Phase(精细碰撞检测) 对上一步候选对,做精确几何体相交计算;算出接触点、法线、穿透深度。

高速物体容易发生“穿透”,可开启CCD连续碰撞检测解决,但性能开销上升。

  1. 约束求解器 Solver 根据质量、物理材质(摩擦力、弹性)、关节Joint约束,计算碰撞冲量,处理反弹、摩擦、关节约束。
  2. 积分更新:根据受力,更新刚体速度、位置、旋转。
  3. 同步回Unity:把PhysX内部计算出来的位置写回GameObject的Transform;触发C#脚本OnCollisionXXX / OnTriggerXXX回调。

⚠️Transform是被PhysX改写,不是C#脚本自己修改。如果你直接改transform.position去移动带Rigidbody的物体,会绕过物理引擎,引发抖动、穿透bug;物理物体移动优先用rb.MovePosition() / AddForce()。

Collision vs Trigger

项目 OnCollision(实体碰撞) OnTrigger(触发器)
Is Trigger 双方都不勾选 至少一方勾选IsTrigger
物理效果 产生阻挡、反弹、摩擦,物体不能穿透 无物理阻挡,物体直接穿过
回调参数 Collision,包含碰撞点、法线、碰撞速度 Collider,只拿到对方碰撞体引用
Rigidbody条件 双方都有Collider,至少一方有Rigidbody 双方都有Collider,至少一方有Rigidbody
典型场景 撞墙、弹跳、物理打击 拾取道具、传送门、区域检测

回调时机:

  • Enter:刚接触的那一次物理步
  • Stay:每一次物理步持续接触都会调用
  • Exit:分离那一刻调用

Rigidbody三种模式

  1. Dynamic动态刚体(默认) PhysX完全控制位置旋转;受重力、力、碰撞影响。适合玩家、抛射物、箱子。
  2. Kinematic运动刚体(IsKinematic=true) 不受重力和外力;只能通过Transform/MovePosition移动;依然可以碰撞推开动态物体。适合移动平台、机关门。
  3. Static静态刚体 没有Rigidbody组件,只有Collider。物理引擎认为永远不动,适合地面、墙体。

❗不要移动静态物体的transform,会触发物理世界重建,严重卡顿。

重要坑点与原理说明

  1. 不要直接修改transform.position操作动态Rigidbody 动态刚体位置由PhysX内部维护。直接写transform会强行覆盖物理计算结果,产生抖动、穿透。 正确方式:rb.velocity / rb.AddForce() / rb.MovePosition()。

  2. 物理回调(OnCollision、OnTrigger)跑在物理步进,不是Update 它的触发频率是50Hz,不是渲染帧率;不要在里面写依赖deltaTime的逻辑。

  3. 物理材质PhysicMaterial 作用于Collider,控制摩擦系数、弹性(Bounciness),决定物体滑动、弹跳效果。

  4. Joint关节 PhysX提供铰链、弹簧、固定关节;在物理求解阶段施加约束,限制两个刚体之间相对运动。

  5. Layer Collision Matrix(层碰撞矩阵) 可以配置哪些层之间允许发生碰撞,直接在物理引擎层过滤,减少碰撞计算量。

  6. CCD连续碰撞检测 普通Discrete离散碰撞是每一步结束检测位置;高速物体在两个物理步之间直接穿过障碍物,发生穿透。开启CCD可以追踪物体运动轨迹检测碰撞,CPU开销明显上升,只给高速物体开启。

性能关键原理

  1. 基础碰撞体Box/Sphere/Capsule计算极快,MeshCollider开销大。
  2. BroadPhase会维护AABB树,大量移动物体,树更新开销上涨。
  3. Solver求解器,物体互相堆叠(大量物体堆一起)开销会急剧上涨。
  4. 触发器也消耗物理计算,不要大规模滥用Trigger。

和C#层的关系总结

  • C#脚本的Rigidbody、Collider只是薄代理对象,真实数据全部存PhysX的C++内存。
  • C#代码只能设置参数、施加力;真正碰撞、运动全部由PhysX完成。
  • FixedUpdate由Unity调用C#,C#修改刚体状态,之后PhysX运行模拟,再把结果同步回来,触发碰撞回调。

如果你需要,我可以对比CharacterController和Rigidbody角色控制器底层原理差异。


21 8 月, 2026Garfield God学习笔记阅读 1 次

0 条回复

还没有留言,来说点什么吧。

留言