Unity物理系统核心:刚体、碰撞器与物理材质详解

📅 2026/8/7 6:13:11
Unity物理系统核心:刚体、碰撞器与物理材质详解
1. 项目概述从“抖动的方块”到可控的物理世界如果你刚开始接触Unity可能已经迫不及待地想让游戏里的角色跳起来、让箱子被推走、让子弹击中目标。当你兴冲冲地给一个方块加上刚体Rigidbody再扔一个方块砸向它期待看到“砰”一声的碰撞和反弹时现实却可能给你泼了一盆冷水两个方块粘在一起疯狂抖动或者穿模而过又或者反弹得软弱无力。这恰恰是物理系统入门时最经典的“劝退”场景。今天这第四天的学习我们就来彻底解决这个问题目标不是简单地让物体“能撞”而是实现一个符合直觉、稳定可控的“碰撞反弹”效果。这背后涉及到的正是Unity物理引擎PhysX最核心的几个基础概念刚体、碰撞器、物理材质以及一系列关键的参数配置。理解它们你才算真正推开了物理世界的大门而不是在门外被一堆莫名其妙的抖动和穿透搞得晕头转向。2. 物理系统核心组件拆解刚体、碰撞器与物理材质在Unity中构建一个可交互的物理对象就像组装一个机器人需要骨骼刚体、皮肤碰撞器和关节润滑剂物理材质协同工作。2.1 刚体Rigidbody物体的“物理灵魂”刚体组件是物理系统的入场券。它为GameObject赋予了物理属性使其受到重力、外力如AddForce的影响并能参与碰撞计算。你可以把它理解为一个物体的“物理灵魂”或“质量中心”。核心参数解析Mass质量决定物体惯性的大小。质量越大改变其运动状态加速或减速所需的外力就越大。一个常见的误区是认为质量影响下落速度在Unity中重力加速度是恒定的但质量会影响碰撞时的动量交换。Drag阻力与 Angular Drag角阻力分别模拟线性移动和旋转时所受的空气阻力。值越大物体停下来越快。对于需要快速停止或模拟水中运动的物体可以适当调高。Use Gravity使用重力是否受全局重力影响。对于飞行道具或悬浮物可以关闭。Is Kinematic是否为运动学刚体这是一个极其重要的开关。勾选后该物体不受物理力重力、碰撞力的影响其运动完全由你的代码如Transform.position控制但它可以触发其他物理刚体的碰撞。常用于移动平台、由动画驱动的角色或需要精确控制的物体。Interpolate插值与 Collision Detection碰撞检测这是解决文章开头提到的“抖动”和“穿透”问题的关键。注意刚体的Transform组件位置和旋转是由物理引擎每帧计算后驱动的。直接通过代码修改带有刚体的物体的Transform.position是“不物理”的行为可能会引发不可预知的问题如剧烈抖动。正确的做法是通过Rigidbody.MovePosition或施加力AddForce来移动它。2.2 碰撞器Collider物体的“物理外形”碰撞器定义了物体的物理形状用于检测与其他碰撞器的接触。它就像物体的“碰撞皮肤”或“命中框”。Unity提供了多种基本碰撞器Box, Sphere, Capsule和复杂的网格碰撞器Mesh Collider。类型与选择基本碰撞器Primitive Colliders性能最优形状规则。绝大多数情况应优先使用它们来近似物体的外形。例如一个人物可以用胶囊体Capsule一个箱子用盒子Box。网格碰撞器Mesh Collider完全贴合3D模型的网格形状精度最高但性能开销巨大。务必勾选“Convex”凸面选项非凸的Mesh Collider只能用于静态环境Is Static且不能与另一个非凸Mesh Collider发生碰撞。对于复杂的角色或道具通常采用“基本碰撞器组合”或使用简化的低模网格作为碰撞体。一个重要属性Is Trigger是否为触发器勾选后该碰撞器不再产生物理碰撞效果即不会阻挡物体但会触发OnTriggerEnter/Stay/Exit事件。用于检测区域、拾取物品、通过关卡等逻辑判断场景。2.3 物理材质Physics Material定义碰撞表面的“性格”物理材质决定了两个碰撞器接触时的表面特性是控制“反弹”效果的核心。你可以把它想象成物体表面的材料是橡胶、钢铁还是冰面。核心参数解析Dynamic Friction Static Friction动摩擦与静摩擦0到1的值。0像冰面一样光滑1像砂纸一样粗糙。静摩擦是物体从静止到运动所需克服的摩擦力。Bounciness弹性0到1的值。0表示完全无弹性碰撞碰撞后粘在一起或没有速度反弹1表示完全弹性碰撞理想情况动能无损失。我们实现反弹主要靠调节它。Friction Combine Bounce Combine摩擦/弹性组合模式当两个拥有不同物理材质的物体碰撞时如何计算最终的摩擦力和弹性。常用模式Average平均取两者平均值。Minimum最小取两者中较小的值。如果你想确保某个表面特别滑或特别不弹可以用这个。Maximum最大取两者中较大的值。Multiply相乘两者相乘。通常用于弹性可以快速衰减反弹效果。3. 实现物体碰撞反弹的详细步骤理论清晰后我们动手实现一个经典的场景一个球体从空中落下撞击到一个平面上并产生清晰的反弹。3.1 场景搭建与基础配置创建地面在场景中创建一个Cube重命名为“Ground”。缩放其Scale如X:10, Y:0.5, Z:10使其成为一个厚实的地板。创建小球创建一个Sphere重命名为“Ball”将其放置在Ground上方一定高度如Y5。添加刚体为Ball对象添加Rigidbody组件。此时运行游戏小球会受重力下落但会直接“穿”过地面因为还没有碰撞器。添加碰撞器Unity在创建基本3D物体时会自动附加对应的碰撞器Sphere Collider 和 Box Collider。检查确认Ball有Sphere ColliderGround有Box Collider。现在运行小球会落在平面上并静止但还没有反弹效果。3.2 创建并配置物理材质在Project视图中右键 - Create - Physics Material命名为“BouncyBall”。选中新建的材质在Inspector中调整其属性Bounciness弹性设置为0.8。这是一个较高的值会产生明显的反弹。Bounce Combine弹性组合设置为Maximum。这意味着当球与任何表面碰撞时都采用两者中弹性更高的那个值。因为我们只给球设置了高弹性材质所以能确保反弹效果。Friction摩擦力可以将Dynamic Friction和Static Friction都设为0.1左右让球在碰撞后还能滚动而不是立刻停下。应用物理材质将“BouncyBall”材质拖拽到Ball对象的Sphere Collider组件的“Material”属性槽中。运行游戏现在小球落下后应该会弹起几次高度逐渐衰减最后停在平面上。这就是通过物理材质实现的基础反弹。3.3 通过脚本动态控制反弹有时我们需要更动态的控制比如根据碰撞速度改变反弹系数或者只在满足特定条件时反弹。using UnityEngine; public class DynamicBounceController : MonoBehaviour { private Rigidbody rb; public float baseBounciness 0.7f; // 基础弹性系数 public float velocityBounceFactor 0.1f; // 速度对弹性的影响因子 void Start() { rb GetComponentRigidbody(); if (rb null) { Debug.LogError(DynamicBounceController 需要挂在带有 Rigidbody 组件的物体上); } } void OnCollisionEnter(Collision collision) { // 1. 获取碰撞点的相对速度法线方向上的速度分量 float relativeSpeed collision.relativeVelocity.magnitude; // 2. 动态计算本次碰撞的弹性例如速度越快反弹越强 float dynamicBounciness baseBounciness (relativeSpeed * velocityBounceFactor); dynamicBounciness Mathf.Clamp(dynamicBounciness, 0f, 1f); // 限制在0-1之间 // 3. 获取或创建物理材质实例避免修改共享的Asset PhysicsMaterial bounceMat collision.collider.material; if (bounceMat null) { bounceMat new PhysicsMaterial(); collision.collider.material bounceMat; } // 重要确保我们修改的是实例而不是Project中的资源 if (!bounceMat.name.Contains((Instance))) { bounceMat new PhysicsMaterial(bounceMat); collision.collider.material bounceMat; } bounceMat.bounciness dynamicBounciness; bounceMat.bounceCombine PhysicsMaterialCombine.Maximum; // 4. 可选根据碰撞对象标签进行不同处理 if (collision.gameObject.CompareTag(SuperBouncy)) { bounceMat.bounciness 1.0f; // 遇到超级弹力面完全弹性碰撞 } Debug.Log($与 {collision.gameObject.name} 碰撞速度 {relativeSpeed:F2}, 设置弹性为 {dynamicBounciness:F2}); } }脚本解析这段脚本通过OnCollisionEnter在每次碰撞发生时动态修改碰撞器的物理材质。collision.relativeVelocity给出了碰撞瞬间的相对速度向量其大小可用于增强反弹效果。关键点在于动态创建物理材质实例。直接修改collision.collider.material可能会意外地修改Project视图中的原始材质资源影响所有使用该材质的物体。通过检查名称或直接new PhysicsMaterial()可以确保修改只作用于当前这次碰撞。通过标签Tag判断可以实现对不同表面采用不同反弹策略的逻辑。4. 高级议题解决穿透、抖动与性能优化回到我们开头提到的社区问题“带有刚体的物体碰撞后没有位移但是发生了抖动”。这通常不是反弹问题而是物理稳定性的问题。4.1 穿透Tunneling问题与碰撞检测模式当物体速度非常快时比如子弹可能会在一帧内完全穿过一个薄的碰撞器导致检测不到碰撞。这就是“穿透”。解决方案调整Collision Detection模式在Rigidbody组件中Discrete离散默认模式。性能最好但可能错过高速碰撞。适用于大部分低速移动物体。Continuous连续对特定对象如动态刚体进行连续碰撞检测防止穿透性能开销大。Continuous Dynamic连续动态对动态刚体进行连续检测并对标记为Continuous或Continuous Dynamic的物体进行连续响应。这是解决高速物体穿透问题最有效的设置。Continuous Speculative连续推测另一种连续检测算法有时在复杂场景下更稳定。实操建议对于子弹、高速移动的玩家或敌人将其Rigidbody的Collision Detection设置为Continuous Dynamic。同时被撞击的静态或动态物体至少设置为Continuous。这就是社区回答中提到的关键设置。4.2 抖动Jitter问题与插值抖动通常发生在两个质量、速度相近的物体持续接触时比如堆叠的箱子物理引擎在每帧计算微小的相互嵌入和修正力导致视觉上的高频震动。解决方案一启用Interpolate插值在Rigidbody组件中None无默认。Interpolate插值根据上一帧的物理状态平滑当前帧的渲染位置能有效平滑运动是消除抖动和运动卡顿的首选方案。适用于由物理驱动的主要运动物体如玩家角色、主要道具。Extrapolate外推根据当前速度预测下一帧的位置可能在速度突变时产生“幽灵”感一般较少使用。解决方案二调整物理材质和刚体属性增加摩擦力适当增加碰撞双方的摩擦力可以减少接触时的微小滑动。降低弹性过高的弹性Bounciness在持续接触时容易引发振荡。增加角阻力Angular Drag可以减少物体旋转带来的不稳定感。调整质量比避免两个质量完全相同的刚体频繁碰撞。可以稍微调整其中一个的质量。解决方案三关键的Solver Iterations求解器迭代次数在Edit - Project Settings - Physics中Default Solver Iterations默认求解器迭代提高此值例如从6提高到10-15可以让物理引擎花更多计算力去解决复杂的接触约束从而减少抖动和物体嵌入。这是解决复杂堆叠、布料、关节抖动非常有效的全局设置但会增加CPU开销。4.3 性能优化要点物理计算是性能消耗大户尤其是移动平台。分层碰撞管理Layer Collision Matrix在Edit - Project Settings - Physics中通过图层碰撞矩阵禁用绝不会发生交互的图层之间的碰撞检测例如背景装饰物图层和子弹图层。这是最有效的优化手段之一。慎用Mesh Collider如前所述尽量用基本碰撞器组合替代。对于复杂静态地形如果必须用Mesh Collider勾选Convex并开启Cooking Options中的优化选项并标记为Static。合理设置刚体睡眠Sleeping静止的刚体会自动进入睡眠状态停止物理计算。确保你的物体在静止时能正常睡眠避免不必要的唤醒如持续施加极小的力。控制物理更新频率在Project Settings - Time中Fixed Timestep决定了物理更新的频率。默认0.02秒50Hz。在保证物理稳定的前提下对于节奏较慢的游戏可以尝试略微调高如0.033秒30Hz以减少每帧的物理计算次数。但调得过高会导致物理不精确。5. 常见问题排查与实战心得5.1 问题速查表问题现象可能原因解决方案物体直接穿透无碰撞1. 未添加碰撞器Collider2. 其中一方是触发器Is Trigger3. 物体移动速度过快1. 检查并添加碰撞器2. 取消勾选Is Trigger或使用OnTrigger事件3. 将Rigidbody的Collision Detection改为Continuous Dynamic碰撞后剧烈抖动或抽搐1. 两个刚体质量、形状相似且持续接触2. 物理材质弹性过高3. 求解器迭代次数不足1. 启用Rigidbody的Interpolate2. 适当降低Bounciness增加Friction3. 增加Project Settings中Default Solver Iterations的值反弹力过弱或没有反弹1. 物理材质的Bounciness为0或过低2. 碰撞双方的Bounce Combine模式为Minimum3. 刚体质量过大1. 调高物理材质的Bounciness值0-12. 将Bounce Combine改为Average或Maximum3. 检查质量比确保不是“鸡蛋撞石头”物体运动不平滑有卡顿感1. 在Update中直接修改Transform.position来移动物理物体2. FixedUpdate频率与帧率不匹配1. 改用Rigidbody.MovePosition或AddForce2. 为Rigidbody启用Interpolate物理表现不一致时快时慢Fixed Timestep不稳定或帧率波动影响Time.fixedDeltaTime确保代码中与物理相关的操作如AddForce放在FixedUpdate中而非Update中。5.2 实战心得与技巧“静态”不等于“不动”一个物体如果不移动最好将其标记为Static在Inspector右上角勾选。这不仅对光照烘焙重要物理引擎也会将其视为不可移动的优化对象。即使这个物体后来通过脚本移动了物理引擎也会将其当作动态物体处理但初始的静态标记仍有优化意义。调试利器Physics Debugger。在Game视图下拉菜单中可以开启Physics Debugger以线框形式可视化所有碰撞体并查看刚体的速度、受力等信息。对于排查碰撞体错位、大小问题非常直观。理解“力”的模式。AddForce有四种模式Force持续力依赖质量、Impulse瞬间冲量依赖质量、Acceleration持续加速度忽略质量、VelocityChange瞬间速度变化忽略质量。想让一个物体无视质量立刻获得一个速度用VelocityChange最直接。复杂形状用Compound Colliders复合碰撞器。不要试图用一个Mesh Collider去匹配复杂角色。创建一个空物体作为父节点挂上Rigidbody然后为其添加多个子物体每个子物体使用简单的Box/Sphere/Capsule碰撞器来组合成近似形状。这样在性能和稳定性上远胜于单个Mesh Collider。关于“完全弹性碰撞”网络热词中提到了“unity 实现完全弹性碰撞”。在理想物理中完全弹性碰撞Bounciness1意味着动能无损失。但在Unity的物理引擎中即使Bounciness设为1由于数值精度和求解器限制也很难实现永无止境的完美反弹通常会有一个极其微小的衰减。如果追求视觉上的“无限弹跳”可能需要在反弹高度低于某个阈值时手动通过脚本补充一个微小的速度。