1. 从一次“角色穿墙”事故说起Rigidbody到底管什么刚接触Unity那会儿我做过一个特别典型的蠢事。场景里放了个带Rigidbody的方块想让它受重力往下掉结果运行起来它纹丝不动我以为是重力没开折腾了半天才发现——我把Rigidbody的Use Gravity勾掉了同时Is Kinematic还开着。两个选项一冲突物理引擎直接把它当成一个“不参与模拟的静态物体”自然什么都不发生。这件事让我意识到很多人对Rigidbody的理解停留在“加上它物体就会动”这个层面但真正决定它怎么动、动不动、和谁碰撞的是组件面板上那一堆参数之间的配合关系。Rigidbody是Unity物理系统的入口它把物体从“渲染层的一个网格”变成“物理世界里的一个刚体”赋予它质量、速度、受力响应和碰撞参与能力。没有它Collider只是一个用来做射线检测的几何体有了它Collider才真正参与碰撞解算。这篇内容适合三类人刚学Unity、对物理组件一知半解的入门者做过一些Demo但角色控制总是“飘”或者“卡墙”的进阶者以及想搞清楚物理模拟和代码控制边界在哪的老手。我会从参数原理讲到实际配置再到踩坑排查尽量把每个选择背后的“为什么”讲透而不是只丢一堆API让你自己猜。2. 面板参数逐个拆解每个勾选项背后都是一次物理决策2.1 Mass与Drag为什么你的物体“轻飘飘”或者“像在糖浆里”Mass质量默认是1单位在Unity里是千克的抽象化表示。很多人觉得质量只影响碰撞时的“谁推得动谁”其实它还直接影响动量计算。两个物体碰撞质量大的那个速度变化小这是动量守恒的直接体现。但要注意Unity的物理求解器在处理质量差异极大的物体时比如1和10000容易出现穿透或者抖动因为求解迭代次数有限。我的经验是场景内参与碰撞的物体质量尽量控制在同一个数量级比如都在0.5到50之间避免极端值。Drag阻力分两种Drag是线性阻力Angular Drag是角阻力。它们的作用是让物体在没有外力时逐渐停下来。这里有个反直觉的点Drag不是摩擦力。摩擦力是接触面之间的Drag是空气阻力式的、与速度相关的衰减。如果你把Drag设成10物体几乎瞬间停住看起来像“粘”在空中设成0物体会永远滑下去在没有摩擦的情况下。我一般给角色控制器设0到0.5给掉落的箱子设0.5到2给需要“漂浮感”的物体设2到5。Angular Drag同理但很多人忘了它结果物体落地后一直转个不停就是因为角阻力太小。提示如果你发现物体下落速度“不对劲”先检查Drag是不是被误设成了非零值。重力加速度是9.81但Drag会持续削减速度导致终端速度远低于预期。2.2 Use Gravity与Is Kinematic一对最容易打架的开关Use Gravity勾上物体就受全局重力影响。但它的效果会被Is Kinematic完全覆盖。Is Kinematic的意思是“这个刚体不受物理引擎的力和碰撞驱动只受代码控制”。一旦勾上重力、碰撞反弹、受力全部失效你只能通过transform.position或者Rigidbody.MovePosition来移动它。那什么时候用Kinematic典型场景是平台移动、电梯、被动画驱动的物体。比如一个移动平台你希望角色站上去能跟着走但平台本身不需要受重力掉下去就把它设成Kinematic然后用代码或动画移动。但这里有个大坑Kinematic刚体撞到普通刚体时普通刚体会被推开但Kinematic刚体自己不受影响。如果你用Kinematic做角色撞墙时角色会穿过去因为物理引擎不处理它的碰撞响应。所以角色控制通常用非Kinematic刚体加约束而不是Kinematic。2.3 Interpolate与Collision Detection解决抖动和穿透的关键Interpolate插值有两个选项None、Interpolate、Extrapolate。物理模拟是按固定时间步长跑的默认0.02秒但渲染帧率是变化的。如果不插值物体在视觉上会抖动因为渲染帧可能落在两个物理步之间。Interpolate会根据上一物理步和当前物理步的位置做平滑Extrapolate则预测下一帧位置。绝大多数情况用Interpolate就够了Extrapolate在物体突然停止时会有“过冲”感。Collision Detection分Discrete和Continuous。Discrete是每物理步检测一次碰撞快速移动的物体可能在一帧内穿过薄墙这就是“隧穿”。Continuous连续检测会做扫掠检测防止穿透但性能开销更大。我的建议玩家角色、子弹、快速移动的物体用Continuous静态环境和慢速物体用Discrete。另外Continuous检测只对刚体之间的碰撞有效对静态碰撞体没有刚体的Collider也有效但需要刚体本身开启。2.4 Constraints冻结轴线的正确用法Constraints可以冻结位置或旋转的某个轴。常见用法是冻结角色的旋转X和Z防止它摔倒冻结Y轴位置让物体只在平面上移动。但要注意冻结的是物理模拟的自由度不是Transform。如果你用代码直接改Transform冻结是拦不住的。另外冻结旋转后物体仍然会受到扭矩只是不表现出来这可能导致物理求解器内部状态异常。所以更好的做法是如果不需要旋转直接用Rigidbody.freezeRotation true或者在Constraints里勾选而不是靠代码每帧重置旋转。3. 代码控制与物理模拟的边界什么时候该用AddForce什么时候该改速度3.1 AddForce、velocity和MovePosition的本质区别这三个方法经常被混用但它们的物理含义完全不同。AddForce是施加一个力力会经过质量换算成加速度再积分成速度。它受Drag影响也受物理步长影响。适合模拟推力、爆炸、跳跃初速度。但AddForce有个问题如果你每帧都加同一个力速度会线性增长因为力是持续作用的。所以跳跃通常用AddForce配合ForceMode.VelocityChange或者直接设velocity。velocity是直接设置速度向量。它绕过力的累积直接改速度。适合做精确控制比如角色移动。但直接改velocity会覆盖物理引擎计算出的速度可能和碰撞响应冲突。比如你设了velocity向前撞墙后物理引擎想把速度反弹但你下一帧又设回向前就会“贴墙滑”或者抖动。MovePosition是Kinematic刚体专用的移动方式它会计算一个位移并在物理步内平滑移动同时触发碰撞检测。它不受重力影响适合平台、电梯。但注意MovePosition是“瞬移”到目标位置只是物理引擎会在路径上做扫掠检测所以不会穿透。我的经验法则需要物理真实感受重力、碰撞反弹、受力用AddForce或velocity需要精确控制且不关心物理反馈用MovePositionKinematic角色移动通常用velocity加约束跳跃用AddForce。3.2 FixedUpdate里做物理Update里做输入这是Unity物理的黄金法则。物理模拟在FixedUpdate里按固定步长执行Update是按帧率执行的。如果你在Update里改velocity物理步可能还没跑导致速度被覆盖或者累积异常。正确做法是在Update里读取输入在FixedUpdate里应用物理。比如void Update() { inputX Input.GetAxis(Horizontal); inputZ Input.GetAxis(Vertical); } void FixedUpdate() { Vector3 move new Vector3(inputX, 0, inputZ) * speed; rb.velocity new Vector3(move.x, rb.velocity.y, move.z); }这样输入采样和物理应用分离既保证了响应性又保证了物理稳定性。3.3 重力缩放与自定义重力让跳跃手感“对味”Unity默认重力是-9.81但很多游戏用更大的重力来让跳跃更“干脆”。你可以通过Physics.gravity全局改也可以给单个刚体设rb.useGravity false然后手动加力。更精细的做法是用Physics.gravity配合跳跃初速度计算。跳跃高度公式是h v² / (2g)所以如果你想要2米高、重力-20初速度就是sqrt(2 * 20 * 2) ≈ 8.94。这个计算在调手感时非常有用比瞎试快得多。另外Rigidbody有个mass和drag但重力加速度和它们无关伽利略早就证明了。所以别指望改质量能让物体掉得更快那是Drag的活。4. 碰撞与触发器Rigidbody和Collider的配合逻辑4.1 为什么两个物体撞在一起却没反应这是新手最常见的问题。碰撞发生的条件是至少一个物体有Rigidbody两个物体都有Collider且Collider不是Trigger。如果两个物体都只有Collider没有Rigidbody它们就是静态碰撞体不会互相碰撞但可以被射线检测到。如果一个是Rigidbody一个是静态Collider碰撞正常。如果两个都是Rigidbody碰撞也正常。但还有几个隐藏条件Layer碰撞矩阵。如果两个物体所在的Layer在Edit Project Settings Physics Layer Collision Matrix里被取消勾选它们就不会碰撞。这个坑很隐蔽因为场景里看起来一切正常但就是不撞。我遇到过好几次最后发现是Layer设错了。另一个坑是Collider的Is Trigger。勾上之后碰撞事件变成OnTriggerEnter/Stay/Exit物理引擎不再做碰撞解算物体会穿过去。如果你想要碰撞响应又要检测进入用OnCollisionEnter别勾Trigger。4.2 触发器里的Rigidbody为什么Trigger没反应触发器事件要触发也需要至少一个物体有Rigidbody。如果两个都是静态Collider加TriggerOnTriggerEnter不会调用。所以做触发区域时要么给触发区域加个Kinematic Rigidbody要么确保进入的物体有Rigidbody。我一般给触发区域加Kinematic Rigidbody这样它不动物理上也不受影响但能正常触发事件。4.3 碰撞回调里的物理操作禁忌在OnCollisionEnter里直接改velocity或者AddForce是可以的但要注意时机。碰撞回调是在物理步内调用的此时物理求解器正在处理接触。如果你在回调里改速度可能会被求解器覆盖或者导致下一帧的接触点异常。更安全的做法是设一个标志位在FixedUpdate里处理。比如bool shouldBounce false; void OnCollisionEnter(Collision col) { if (col.gameObject.CompareTag(Bouncy)) shouldBounce true; } void FixedUpdate() { if (shouldBounce) { rb.AddForce(Vector3.up * bounceForce, ForceMode.Impulse); shouldBounce false; } }这样物理操作发生在物理步的合适位置避免冲突。5. 性能与稳定性Rigidbody用多了会怎样5.1 刚体数量与物理步长的权衡每个Rigidbody都会参与物理求解求解器每步要处理所有刚体的受力、碰撞、约束。刚体数量多了CPU开销线性增长。我做过测试场景里200个活跃刚体时物理耗时开始明显500个时帧率掉得厉害。优化手段包括远处物体设成Kinematic或休眠用Rigidbody.Sleep()主动休眠减少不必要的碰撞层。物理步长Time.fixedDeltaTime默认0.02秒对应50Hz。调小步长比如0.01会让物理更精确但开销翻倍。调大比如0.04会降低精度快速物体更容易穿透。一般不建议动除非有特殊需求。5.2 休眠机制为什么物体“睡着了”叫不醒Unity的刚体有自动休眠机制当速度低于阈值一段时间后刚体进入休眠不再参与物理计算直到受到外力或碰撞。这本来是优化但有时候你通过代码改位置或者加力物体没反应因为它还在休眠。解决办法是rb.WakeUp()或者直接设rb.isKinematic false再设回来。另外rb.sleepThreshold可以调但一般不用动。5.3 父子物体与刚体别在刚体下面挂刚体刚体之间的父子关系是物理上的大忌。父刚体移动时子刚体的Transform会被强制改变但物理引擎不知道导致子刚体的碰撞和速度计算错乱。正确做法是刚体物体不要有刚体子物体如果需要组合用关节Joint连接或者用代码同步位置。我见过有人给角色模型加刚体模型下面又挂了个武器刚体结果武器乱飞就是因为父子关系干扰了物理模拟。6. 排查实录角色卡墙、抖动、穿透的完整排查链路6.1 卡墙从Collider形状到物理材质的逐层排查角色卡墙通常有几个原因。第一Collider形状和视觉模型不匹配比如Capsule Collider的半径太大卡在墙角。第二物理材质Physic Material的摩擦力太大角色贴着墙就动不了。第三代码里每帧设velocity和碰撞响应打架。我的排查顺序是先看Collider是不是比模型大再看物理材质的Dynamic Friction和Static Friction最后看代码是不是在FixedUpdate里硬设速度。如果是硬设速度改成用AddForce或者只在没有碰撞时设速度。6.2 抖动插值、约束和物理步长的三角关系抖动一般来自三个地方。第一没开Interpolate渲染帧和物理步不同步。第二约束冻结了不该冻结的轴导致求解器内部冲突。第三物理步长和渲染帧率不匹配比如高刷新率显示器上60Hz物理步会显得抖。解决办法开Interpolate检查Constraints必要时把物理步长调到0.016660Hz匹配显示器。6.3 穿透Continuous检测也不是万能的开了Continuous还会穿透通常是因为碰撞体太薄。Continuous检测是扫掠检测但如果碰撞体厚度小于一个物理步内的位移还是可能穿。解决办法是加厚碰撞体或者用射线检测提前预判。另外Continuous只对刚体之间的碰撞有效对静态碰撞体也有效但需要刚体本身开启。如果穿透发生在两个静态碰撞体之间那本来就不会碰撞不是穿透问题。7. 一些让我少走弯路的实操习惯我现在做任何涉及物理的项目都会先做三件事第一把场景里所有刚体的质量、Drag、Constraints列个表确保没有极端值和冲突设置。第二给玩家角色单独设Layer和环境的碰撞矩阵检查一遍。第三在FixedUpdate里加个调试输出打印速度和接触点数量方便排查异常。另外物理材质Physic Material经常被忽略。默认材质的摩擦力和弹性都是0.6和0但不同物体需要不同手感。比如冰面摩擦力设0.05弹力球弹性设0.9。这些微调比改代码有效得多。最后别怕用Kinematic。很多人觉得Kinematic“不物理”但平台、电梯、移动障碍物用Kinematic加MovePosition是最稳的方案比用非Kinematic刚体加约束简单得多。物理引擎是工具不是信仰该用代码控制的时候就果断用。