Unity物理引擎入门:刚体、碰撞器与物理材质详解

Unity物理引擎入门:刚体、碰撞器与物理材质详解
1. 项目概述从零开始理解Unity物理引擎如果你刚接触Unity可能会被它琳琅满目的组件和功能搞得眼花缭乱。其中物理引擎Physics Engine绝对是构建任何带有交互性、真实感内容的核心基石。它不是你手动去写代码计算一个球如何下落、两个箱子如何碰撞而是由引擎内置的一套复杂系统来模拟现实世界的物理规律。简单来说你只需要告诉Unity“这里有个刚体Rigidbody它受重力影响”然后物理引擎就会自动接管让它下落、碰撞、反弹。对于想入门Unity开发尤其是涉及角色移动、物体交互、机关解谜等功能的开发者来说掌握物理引擎基础是绕不开的第一步。这不仅仅是让物体“动起来”更是赋予游戏世界真实感和可预测性的关键。很多人会把物理引擎和动画系统搞混。动画Animation是预先设计好的、确定性的运动序列比如角色的待机、行走、攻击动作。而物理模拟是实时计算、非确定性的其结果取决于质量、速度、摩擦力、碰撞形状等多种因素的相互作用。一个角色被爆炸冲击波掀飞——这个过程由物理引擎计算而角色落地后爬起来做的那个动作——这通常由动画系统播放。理解这个区别是正确使用物理引擎的前提。2. 物理引擎核心组件深度解析Unity的物理引擎基于NVIDIA的PhysX在构建移动平台等项目时可能使用其他后端它通过一系列组件协同工作。你不能只知其然更要知其所以然明白每个组件背后的设计意图和适用场景。2.1 刚体Rigidbody物理世界的入场券刚体组件是让一个游戏对象GameObject受物理引擎控制的“开关”。一旦挂载该对象就拥有了质量、阻力、重力等物理属性。它的核心参数直接决定了物体的“性格”质量Mass单位是千克kg。这是最容易被误解的参数。它不仅仅是重量更是物体惯性大小的度量。一个质量大的物体更难被推动被碰撞时运动状态改变也更小。在游戏中一辆坦克的质量应该远大于一个木箱。但要注意质量是相对的你需要为场景中的所有物理对象设定一个合理的比例关系而不是纠结于绝对的真实数值。比如你可以设定角色质量为1那么一个箱子可以是5一辆车可以是50。阻力Drag与角阻力Angular DragDrag影响物体直线运动时的减速想象在空气中或水中的运动Angular Drag影响物体旋转时的减速。如果你想做一个飘在空中的、缓慢旋转的幽灵或者太空中的物体可以把这两个值设得很低如0.1。而对于一个实心的、快速停下的金属块则可以设得高一些如5。使用重力Use Gravity这个复选框决定物体是否受全局重力设置的影响。关闭它物体就会像在太空中一样漂浮。这在制作飞行道具、UI物理效果或者特定谜题时非常有用。是否为运动学Is Kinematic这是高级功能的分水岭。勾选后物理引擎将不再计算力对该物体的影响但它可以通过Transform位置、旋转直接移动并且能影响其他非运动学的物理对象。常用于由动画或代码精确控制的平台、电梯。玩家角色有时会用Character Controller替代但物理角色也可用此模式。需要被“抓取”并移动的物体。注意运动学刚体和非运动学刚体碰撞时非运动学刚体会被弹开而运动学刚体岿然不动。这就像一堵移动的墙推开了箱子。2.2 碰撞器Collider定义物体的“形状”物理引擎需要知道物体的边界才能计算碰撞。这就是碰撞器的职责。它定义了物体的物理轮廓这个轮廓通常比视觉上的网格Mesh更简单以提升计算效率。基本碰撞器包括盒型Box、球型Sphere、胶囊型Capsule。它们计算效率极高应作为首选。例如一个角色通常使用胶囊碰撞器因为它能很好地模拟人体并且在斜坡上移动时不易卡住。网格碰撞器Mesh Collider使用物体实际的网格模型作为碰撞形状。这能提供最精确的碰撞但性能开销巨大尤其是对于复杂网格。务必谨慎使用。通常只用于场景中静止的、形状复杂且互动关键的静态物体如一个复杂的岩石雕塑并且要勾选“凸起Convex”选项如果物体是凹形的则无法用于动态物体间的碰撞。复合碰撞器一个游戏对象下可以有多个子对象每个子对象挂载不同的基本碰撞器共同组成一个复杂形状。这是平衡精度和性能的常用手段。比如一张桌子可以由一个盒型碰撞器做桌面四个细长的盒型碰撞器做桌腿。碰撞器与触发器Is Trigger勾选“Is Trigger”后碰撞器不再产生物理碰撞效果即物体不会弹开但会发送OnTriggerEnter、OnTriggerStay、OnTriggerExit消息。这用于检测区域进入、拾取物品、通过检查点等逻辑判断场景。2.3 物理材质Physic Material定义表面的“触感”两个物体碰撞时是像橡皮球一样弹跳还是像抹了油一样滑开亦或是像陷入泥沼一样停下这由物理材质决定。动态摩擦力Dynamic Friction 静态摩擦力Static Friction物体运动时和静止时与表面摩擦的系数。值越高越难滑动。冰面的摩擦力接近0橡胶面的摩擦力则很高。弹力Bounciness碰撞后速度保留的比率。0表示完全无弹性像泥巴1表示完全弹性碰撞理想状态动能无损失。实际应用中0.6-0.8能模拟出不错的皮球效果。摩擦力合并模式 弹力合并模式当两个拥有不同物理材质的物体碰撞时如何计算最终的摩擦力和弹力通常使用“平均Average”或“最大Max”即可满足大部分需求。3. 物理交互的脚本控制实战物理引擎是自动的但游戏逻辑是需要控制的。我们通过C#脚本来监听物理事件、施加力从而驱动游戏玩法。3.1 施加力与运动控制让一个物体动起来最符合物理直觉的方式是施加力Force。Unity提供了几种方式public class BallController : MonoBehaviour { public float kickForce 10f; private Rigidbody rb; void Start() { rb GetComponentRigidbody(); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { // 方式1AddForce - 添加一个持续的力符合Fma rb.AddForce(Vector3.up * kickForce, ForceMode.Impulse); // Impulse模式瞬间冲量 // 方式2AddForceAtPosition - 在特定点施加力可能产生旋转 // rb.AddForceAtPosition(Vector3.forward * kickForce, transform.position Vector3.right, ForceMode.Impulse); // 方式3直接修改速度 - 非物理方式但有时更直接可控如平台跳跃 // rb.velocity new Vector3(rb.velocity.x, 10f, rb.velocity.z); } } }ForceMode参数详解ForceMode.Force添加一个持续的力单位牛顿考虑质量。最符合真实物理。ForceMode.Impulse添加一个瞬间冲量单位牛顿秒考虑质量。适合爆炸、跳跃、被击中。ForceMode.Acceleration添加一个持续的加速度单位米/秒²忽略质量。所有物体加速效果一样。ForceMode.VelocityChange添加一个瞬间的速度变化单位米/秒忽略质量。对于大多数游戏互动如角色跳跃、被子弹击中Impulse是最常用且效果直观的模式。3.2 碰撞与触发检测物理引擎在每帧计算后会通过消息调用Message的方式通知我们发生了什么。我们需要在脚本中编写对应的方法来响应。public class PlayerCollisionHandler : MonoBehaviour { // 1. 普通碰撞检测Collider的Is Trigger为false void OnCollisionEnter(Collision collision) { // collision对象包含了丰富的碰撞信息 Debug.Log(撞到了: collision.gameObject.name); // 接触点 ContactPoint contact collision.contacts[0]; Debug.Log(接触点位置: contact.point); // 碰撞相对速度 float hitStrength collision.relativeVelocity.magnitude; if (hitStrength 5f) { Debug.Log(撞击力度很大); } } void OnCollisionStay(Collision collision) { /* 碰撞持续中 */ } void OnCollisionExit(Collision collision) { /* 碰撞离开 */ } // 2. 触发检测Collider的Is Trigger为true void OnTriggerEnter(Collider other) { if (other.CompareTag(Coin)) { Debug.Log(捡到金币); Destroy(other.gameObject); // 销毁金币物体 // 这里可以触发加分逻辑 } else if (other.CompareTag(DeathZone)) { Debug.Log(落入死亡区域); // 触发角色死亡、重生逻辑 } } void OnTriggerStay(Collider other) { /* 停留在触发区域内 */ } void OnTriggerExit(Collider other) { /* 离开触发区域 */ } }关键技巧使用CompareTag来识别物体比直接比较名字gameObject.name效率高得多也更容易管理。务必在Unity编辑器中为你的游戏对象设置好Tag。4. 刚体运动模式与性能优化物理计算是性能消耗大户不当的使用会导致帧率下降。理解不同的刚体运动模式及其开销至关重要。4.1 运动模式详解动态刚体DynamicIs Kinematic为false。这是最常用、也是计算最复杂的模式。物理引擎会计算所有力重力、碰撞力、手动添加的力等对其的影响。适合大多数可互动的物体如箱子、球、敌人非精确控制时。运动学刚体KinematicIs Kinematic为true。物理引擎不计算力对其影响其运动完全由Transform驱动通过代码修改position/rotation。但它可以与动态刚体发生碰撞并影响后者。性能开销远低于动态刚体。适用于玩家角色配合Character Controller或自己编写移动逻辑。移动平台、电梯。需要被拾取并移动的物体在拾取时设置为Kinematic放下时再改回Dynamic。静态碰撞器Static没有Rigidbody组件但挂载了Collider。它被假定为永远静止。性能最优。用于场景中永远不会移动的部分如地面、墙壁、建筑物。物理引擎会对它们进行特殊优化如烘焙到静态碰撞网格。4.2 性能优化黄金法则法则一静态物体绝不加刚体。地面、墙壁等只加碰撞器不加Rigidbody。这是最重要的优化。法则二慎用Mesh Collider。尽量用基本碰撞器或复合碰撞器近似。如果必须用Mesh Collider勾选“Convex”并尽量简化网格。法则三合理设置碰撞层Layer。在Edit - Project Settings - Physics中可以管理不同层之间是否进行碰撞检测。例如“UI”层和“Enemy”层完全不需要碰撞检测就取消它们之间的勾选。这能大幅减少物理引擎需要检测的碰撞对。法则四控制动态刚体的数量。屏幕上同时活动的物理物体最好控制在数十个以内。对于大量小物体如碎片可以考虑使用粒子系统模拟或者在它们静止后将其设置为Kinematic或直接销毁。法则五使用“Sleep”机制。当刚体速度低于某个阈值一段时间后物理引擎会使其“休眠”停止计算直到它再次被外力扰动。确保你的刚体允许休眠Rigidbody组件默认开启。避免通过每帧施加微小力来保持物体“活跃”。5. 常见物理问题排查与实战技巧即使理解了原理在实际开发中还是会踩坑。下面是一些高频问题和解决思路。5.1 物体抖动或穿透这是最常见的问题俗称“抖动”或“穿模”。原因一碰撞器形状不匹配或相交。在编辑模式下两个物体的碰撞器尤其是Mesh Collider如果初始位置就相交物理引擎会尝试将它们推开可能导致剧烈抖动。确保场景初始化时所有碰撞器都不应相互嵌入。原因二移动速度过快。如果物体在一帧内移动的距离超过了其碰撞器的尺寸就可能从另一个碰撞器的一端“穿越”到另一端检测不到碰撞。解决方案使用Rigidbody.MovePosition和MoveRotation来移动运动学刚体而非直接修改Transform。这些方法会在移动过程中进行连续碰撞检测。对于动态刚体避免在一帧内施加过大的力使其获得超高速。可以钳制速度rb.velocity Vector3.ClampMagnitude(rb.velocity, maxSpeed)。在Project Settings - Physics中可以增加Default Contact Offset默认接触偏移值但这会轻微影响精度。原因三缩放Scale非均匀。对物体进行非均匀缩放如Scale的X、Y、Z值不同可能导致碰撞器形状计算异常引发不可预测的碰撞行为。尽量保持缩放值为(1,1,1)或在建模阶段就处理好比例。5.2 力与运动效果不符预期现象给物体施加了一个向上的力但它斜着飞出去了。排查检查力的施加模式ForceMode。Force和Impulse效果差别很大。检查力的施加方向。确保你使用的方向向量如Vector3.up是在正确的坐标系下世界坐标Vector3.up还是本地坐标transform.up。检查碰撞器中心Center和刚体的质心Center of Mass。有时模型原点不在几何中心导致受力旋转。可以在代码中通过rb.centerOfMass进行调整。检查是否有其他力在干扰如阻力Drag设置过大或者有其他脚本在同一帧施加了相反的力。5.3 触发检测不灵敏或重复触发现象物体穿过触发器区域但OnTriggerEnter没被调用或者被调用了多次。排查确保双方都有碰撞器且至少一方有刚体。触发器也是碰撞器必须满足碰撞检测的基本条件。检查碰撞层Layer设置确保双方所在的层在物理设置中允许碰撞。检查碰撞器尺寸和位置在Scene视图中勾选Gizmos - Colliders可视化查看是否如预期。重复触发问题通常发生在两个物体边界持续摩擦时OnTriggerStay会每帧调用这是正常的。如果OnTriggerEnter被多次调用可能是物体在单次进入过程中由于物理微调导致“进出-再进入”。可以在脚本中使用状态标志位来避免重复逻辑。private bool hasCollected false; void OnTriggerEnter(Collider other) { if (!hasCollected other.CompareTag(PowerUp)) { hasCollected true; // 执行一次性的拾取逻辑 Debug.Log(获得能量); Destroy(other.gameObject); // 一段时间后重置标志位如果需要的话 // Invoke(nameof(ResetCollection), 5f); } } void ResetCollection() { hasCollected false; }5.4 物理模拟与帧率相关物理引擎的模拟是固定时间步长Fixed Timestep默认0.02秒即50Hz进行的与图形渲染的帧率Update是解耦的。这保证了物理世界的稳定性。但这也带来一个问题在FixedUpdate中处理物理相关操作如施加力是最正确的因为这与物理模拟同步。在Update中处理如果帧率波动可能导致施加力的频率不稳定。最佳实践所有直接调用Rigidbody方法AddForce,MovePosition等的代码都应放在FixedUpdate中。而检测输入Input.GetKeyDown通常在Update中你可以将输入结果存储在变量中然后在FixedUpdate里使用。private bool shouldJump; void Update() { // 在Update中检测瞬间输入 if (Input.GetKeyDown(KeyCode.Space)) shouldJump true; } void FixedUpdate() { // 在FixedUpdate中执行物理操作 if (shouldJump) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); shouldJump false; // 重置标志 } // 持续的移动力也可以在这里施加 float moveHorizontal Input.GetAxis(Horizontal); rb.AddForce(new Vector3(moveHorizontal * 10f, 0, 0)); }掌握这些基础你就能让Unity世界中的物体按照你期望的物理规则互动起来。物理引擎是一把强大的工具初期可能会觉得它有些“不受控制”但一旦理解了它的脾气你就能创造出丰富、可信且充满趣味的交互体验。记住多实验多观察Scene视图中的碰撞器善用Debug.Log来输出信息是快速掌握物理引擎的不二法门。