Unity物理系统原理
Unity 3D物理底层是 NVIDIA PhysX(C++编写的专业物理引擎);2D物理使用Box2D。
Unity本身不实现物理计算,C#层的
Rigidbody、Collider只是C#代理组件;真正全部物理模拟都跑在底层PhysX C++内部,C#通过桥接调用PhysX接口。
核心组件
- Collider(碰撞体) 定义物体物理形状边界,和模型Mesh是两套东西。模型管渲染显示,Collider管碰撞检测。
- 基础碰撞体:Box、Sphere、Capsule,计算速度很快。
- MeshCollider:用模型网格做碰撞,性能开销高;动态物体尽量不要用。
- 勾选
Is Trigger→ 变为触发器,只检测重叠,无物理阻挡效果。
- 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内部完整跑一遍这套流程:
- 收集刚体状态:读取C#层施加的力、冲量、速度、重力。
- Broad Phase(粗检测) 用AABB包围盒快速筛选,剔除完全不可能相交的物体对;只留下有可能碰撞的候选对象。快速,粗略,减少后续计算量。
- Narrow Phase(精细碰撞检测) 对上一步候选对,做精确几何体相交计算;算出接触点、法线、穿透深度。
高速物体容易发生“穿透”,可开启CCD连续碰撞检测解决,但性能开销上升。
- 约束求解器 Solver 根据质量、物理材质(摩擦力、弹性)、关节Joint约束,计算碰撞冲量,处理反弹、摩擦、关节约束。
- 积分更新:根据受力,更新刚体速度、位置、旋转。
- 同步回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三种模式
- Dynamic动态刚体(默认) PhysX完全控制位置旋转;受重力、力、碰撞影响。适合玩家、抛射物、箱子。
- Kinematic运动刚体(IsKinematic=true) 不受重力和外力;只能通过Transform/MovePosition移动;依然可以碰撞推开动态物体。适合移动平台、机关门。
- Static静态刚体 没有Rigidbody组件,只有Collider。物理引擎认为永远不动,适合地面、墙体。
❗不要移动静态物体的transform,会触发物理世界重建,严重卡顿。
重要坑点与原理说明
不要直接修改transform.position操作动态Rigidbody 动态刚体位置由PhysX内部维护。直接写transform会强行覆盖物理计算结果,产生抖动、穿透。 正确方式:
rb.velocity/rb.AddForce()/rb.MovePosition()。物理回调(OnCollision、OnTrigger)跑在物理步进,不是Update 它的触发频率是50Hz,不是渲染帧率;不要在里面写依赖deltaTime的逻辑。
物理材质PhysicMaterial 作用于Collider,控制摩擦系数、弹性(Bounciness),决定物体滑动、弹跳效果。
Joint关节 PhysX提供铰链、弹簧、固定关节;在物理求解阶段施加约束,限制两个刚体之间相对运动。
Layer Collision Matrix(层碰撞矩阵) 可以配置哪些层之间允许发生碰撞,直接在物理引擎层过滤,减少碰撞计算量。
CCD连续碰撞检测 普通Discrete离散碰撞是每一步结束检测位置;高速物体在两个物理步之间直接穿过障碍物,发生穿透。开启CCD可以追踪物体运动轨迹检测碰撞,CPU开销明显上升,只给高速物体开启。
性能关键原理
- 基础碰撞体Box/Sphere/Capsule计算极快,MeshCollider开销大。
- BroadPhase会维护AABB树,大量移动物体,树更新开销上涨。
- Solver求解器,物体互相堆叠(大量物体堆一起)开销会急剧上涨。
- 触发器也消耗物理计算,不要大规模滥用Trigger。
和C#层的关系总结
- C#脚本的Rigidbody、Collider只是薄代理对象,真实数据全部存PhysX的C++内存。
- C#代码只能设置参数、施加力;真正碰撞、运动全部由PhysX完成。
- FixedUpdate由Unity调用C#,C#修改刚体状态,之后PhysX运行模拟,再把结果同步回来,触发碰撞回调。
如果你需要,我可以对比CharacterController和Rigidbody角色控制器底层原理差异。


0 条回复
还没有留言,来说点什么吧。