ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Collider与Rigidbody的协作机制:Unity物理系统核心要点与避坑指南

Collider与Rigidbody的协作机制:Unity物理系统核心要点与避坑指南 1. Collider与Rigidbody一对经常被误解的搭档在Unity开发里Collider组件和Rigidbody组件堪称物理系统的两根支柱几乎每个涉及交互、移动、碰撞判断的游戏都离不开它们。但很多初学者甚至一些工作两年的开发者对这两个组件的理解还停留在加上就能用的层面结果遇到物体穿透抖动不停性能莫名卡顿之类的问题时排查半天也找不到根源。这篇文章我想从实际开发的角度把这两个组件的工作机制、配置要点以及常见的坑一次讲清楚。内容包括它们各自负责什么、为什么必须配合使用、动态碰撞体和静态碰撞体有什么区别、刚体插值和连续碰撞检测到底该怎么选以及移动平台和物理引擎交互时经常踩的雷。无论你是刚入门Unity的新手还是想系统性捋一遍物理系统知识的中级开发者这篇文章都应该对你有帮助。先给一个最核心的结论Collider负责形状Rigidbody负责行为。Collider定义了物体在物理世界中的轮廓边界用来判断碰没碰到Rigidbody赋予物体被物理引擎驱动的身份用来决定受力后怎么动。两者配合才构成完整的物理交互缺一个就会出各种匪夷所思的问题。2. Collider组件详解物理世界的轮廓2.1 常用Collider类型与适用场景Unity内置了多种Collider组件每种本质上是一个数学几何体或网格边界。选对类型直接影响物理精度和计算开销我的建议是能简单就不复杂能用几何体就不用Mesh。Box Collider最常用的包围盒适合箱子、墙体、平台、门等规则物体。它在内部生成一个AABB轴对齐包围盒或OBB定向包围盒碰撞检测开销极小。Sphere Collider球体碰撞体适合球形物体比如滚动的球、角色头顶的受击判定。注意它只要是球体哪怕Scale不等比拉伸也不会变成椭球体这点很容易踩坑。Capsule Collider胶囊体由一个圆柱和上下两个半球构成。绝大多数人形角色的碰撞体都用它因为形状贴合人体轮廓又比Mesh Collider便宜得多。Box/Sphere/Capsule2D2D物理系统对应的版本原理一致只是在XY平面内计算。Mesh Collider用模型网格直接生成碰撞边界精度最高代价也最大。非必要不用用的话尽量配合低模或者勾选Convex凸包选项。这里要重点说一下Convex选项。一个非凸的Mesh Collider只能和另一个非凸Mesh Collider发生碰撞性能极差。勾选Convex后引擎会用凸包近似你的网格这样就能和普通几何Collider正常交互物理计算也快很多。缺点是不能表示凹陷物体比如一个L形的房间。实际项目里L形房间通常拆成两个Box Collider来拼而不是用一个凹Mesh。2.2 Collider的触发模式与物理材质Collider上有两个核心开关Is Trigger和Material。把Is Trigger勾上之后这个碰撞体就不再参与物理碰撞的阻挡逻辑而是变成一个纯检测区域。当其他碰撞体进入这个区域时引擎会回调OnTriggerEnter、OnTriggerStay、OnTriggerExit。这个设计非常实用比如里程碑检测、玩家进入某个区域触发剧情、攻击范围判定都该用触发器而不是碰撞体。物理材质Physic Material则控制物体表面摩擦力和弹力。它有三个关键参数Dynamic Friction运动时的摩擦力0表示完全光滑1表示摩擦力很大。Static Friction静止时的摩擦力决定物体从静止到运动需要多大推力。Bounciness弹性系数0表示完全不弹1表示几乎完美反弹。调材质时要记住物理材质是加在Collider上的不是加在Rigidbody上的。很多人新建了一个Physic Material却忘记拖到Collider的Material槽里白白折腾半天。2.3 Collider在父子层级中的位置Collider是Transform的附属组件位置、旋转、缩放都会跟随父物体。但要注意Scale对Collider的影响Box Collider的Size是局部坐标下的尺寸如果父物体Scale是(2,2,2)碰撞体实际大小也是两倍。代码里改Size时最好考虑到这点否则可能出现明明设置的碰撞体大小和模型一致运行时却大了一圈的怪象。还有一点容易忽略Collider可以被多个组件叠加。比如主碰撞体用Capsule Collider做物理阻挡子物体再挂一个Box Collider并勾上Is Trigger用来做脚底的落地检测。这在角色控制器里是很常见的组合方式。3. Rigidbody组件详解让物体活起来的引擎3.1 Rigidbody的核心属性和含义Rigidbody是Unity物理系统的动力来源。只有在挂上Rigidbody之后物体才会受到重力、摩擦力、碰撞反作用力等物理效果。它的核心参数如下Mass质量质量越大惯性越大。碰撞时的质量比决定物体被推动的程度。举个例子一列火车和一个皮球撞在一起火车几乎不动皮球飞出去因为质量差异巨大。Drag线性阻尼模拟空气阻力值越大物体减速越快。默认0.1想模仿冰面滑行就调成0或者很小想模拟水中运动就调到5甚至更高。Angular Drag角阻尼旋转时的空气阻力用于抑制旋转。默认0.05调大会让物体很快停止旋转。Use Gravity是否受重力影响。不勾选时物体不会下落但碰撞和推力仍然生效常用于漂浮物体或者被动画驱动的角色。Is Kinematic运动学刚体。勾选后物体不再受物理引擎驱使但仍能推动其他物体。配合Transform.position或者动画来控制它移动可以实现运动但不被物理干扰的效果。Interpolate插值None、Interpolate插值、Extrapolate外推三档。如果物体移动过快物理引擎的更新频率默认每帧固定时间步长和渲染帧率不同步会出现抖动。用Interpolate可以平滑补间。Collision Detection碰撞检测模式包含Discrete离散、Continuous连续、Continuous Dynamic动态连续、Continuous Speculative推测连续几种。这个后面详细展开。3.2 刚体的三种工作形态根据是否勾选Is Kinematic以及如何移动物体Rigidbody能分成三种工作形态理解这一点对后续开发至关重要完全物理驱动不勾Kinematic直接给刚体加力AddForce或者设置速度velocity。物体的移动完全由物理引擎计算和碰撞体之间的交互最真实。缺点是难以精确控制位置适合投掷物、爆炸飞散的碎片、被击飞的敌人这类不用管落点的物体。动画/脚本驱动勾选Is Kinematic用Transform.position或Animator控制位置。物体移动时会推开路径上的动态物体但它自己不受重力、碰撞力影响。适合移动平台、电梯、NPC骨骼动画控制的角色。混合模式运行时动态切换Is Kinematic的值。这是很多开发者不知道的技巧。比如一个箱子没被玩家推时设成Kinematic节省物理计算玩家碰到它时切换成非Kinematic让它能被推动。反复切换会有微小的性能开销但收益往往更大。3.3 刚体加力方式的选择逻辑通过Rigidbody控制物体移动有几种常见方式选错会带来截然不同的手感AddForce施加瞬时力效果受质量影响。它适合模拟冲击力、爆炸力、跳跃起跳瞬间。AddTorque施加旋转力矩让物体沿某个轴旋转。适合翻倒、轮胎滚动。velocity直接赋值绕过力的累积直接设置速度。效果不受质量影响手感更直接。但要注意在FixedUpdate里赋值才是安全的在Update里赋值会因为物理步进不同步而产生抖动。MovePosition / MoveRotation以刚体为对象平滑移动适合类似Kinematic但又想保留一定物理响应的场景。配合Interpolate效果更好。我觉得很多项目把AddForce和velocity混用导致手感怪异却迟迟找不到原因其实根源就在这里。简单说想模拟推力用AddForce想模拟恒定速度用velocity想把物体推到某个点用MovePosition。4. 二者协作机制为什么单独挂一个组件会出问题4.1 只有Collider没有Rigidbody静态碰撞体一个物体只挂Collider、不挂Rigidbody在Unity中称为静态碰撞体Static Collider。静态碰撞体没毛病它可以阻挡所有动态物体场景里的地面、墙壁大多如此。但它有两点限制它自己不会动。一旦运行时通过Transform移动了静态碰撞体物理引擎会重建相关的碰撞数据带来性能峰值而且移动穿过另一个静态碰撞体时不会触发任何碰撞事件。它不能推动别的物体。静态碰撞体撞到动态物体时动态物体会被弹开或阻挡但静态碰撞体本身纹丝不动——这是符合直觉的因为引擎默认它质量无穷大。4.2 只有Rigidbody没有Collider什么都不会发生反过来只挂Rigidbody没挂Collider的物体也经常被误加。这种情况通常是因为开发者把box collider忘了或者从Prefab里误删了。结果是物体会受重力影响无限下落但它和任何东西都不产生碰撞直接穿透整个世界。因为物理碰撞判定完全依赖Collider没有Collider就相当于没有物理身体。我见过有人在主角身上挂了Rigidbody但Collider加在了子物体上然后Father和Child的层级关系导致碰撞体位置偏移角色莫名其妙被卡住。排查半天发现Collider和模型的中心点对不上。所以挂Collider时一定要确认它和Rigidbody在同一个GameObject还是不同层级层级不同会有完全不同的表现。4.3 一个物体上的多个Collider与刚体的组合一个GameObject可以挂多个Collider物理引擎会把它们的并集作为碰撞形状。举例来说一个人形角色可能有胶囊体躯干、球体头、盒体脚它们合在一起构成角色的完整物理轮廓。所有Collider和同一个Rigidbody关联受力时统一计算。这里我要提醒一个常见坑给子物体加Collider时如果父物体有Rigidbody子物体上的Collider也会被当作父刚体的一部分参与碰撞。但如果子物体也有自己的Rigidbody那它就成了独立的物理体父物体的刚体不会连带它。这个父子结构决定物理层级的规律很多人一开始想不明白。5. 碰撞器与刚体协作的三大常见坑与排查链路5.1 物体穿透明明有碰撞体怎么还是穿过去了这是我在社区里看到最高频的问题之一。症状主角快速冲向一面墙或者高速投掷物体物体直接穿过墙体有时候穿一半又弹回来看起来像卡进墙里。排查链路检查碰撞体尺寸先用Scene视图的隐藏线框模式Wireframe看碰撞体是否覆盖了模型的物理轮廓。如果Collider只有模型的一部分穿透其实是因为没碰到。检查Collision Detection模式默认是Discrete离散检测物理引擎在每个固定时间步里只做一次碰撞判定。如果物体移动速度很快一个步进内它可能从墙的一侧直接跳到另一侧引擎根本没检测到碰撞。把模式改成Continuous或Continuous Dynamic。检查Fixed TimestepProject Settings里Physics的Fixed Timestep默认是0.02秒每秒50次物理更新。如果物体一帧移动超过自身尺寸穿透就很难避免。调小Fixed Timestep能缓解但会增加物理计算开销。更推荐的方式是限制物体最大速度或者用Raycast做辅助检测。检查刚体是否被Transform直接位移如果代码在Update里直接改Transform.position物理引擎的碰撞响应根本来不及生效。正确做法是给Rigidbody设velocity或使用MovePosition。5.2 抖动与抖动物体静止时像在呼吸移动时像在抽搐症状物体放在地上理论上应该纹丝不动却不停地在原地颤抖角色在斜坡上移动时上下颠簸。这个问题的根源通常是物理更新和渲染帧率不一致。物理引擎默认以固定步长更新FixedUpdate渲染帧率是浮动的。当一个用Transform控制移动的物体不挂刚体时渲染每次重新计算位置如果和物理步进错位就会出现肉眼可见的抖动。解决方式物理引擎驱动的物体把代码写在FixedUpdate而非Update里。给刚体的Interpolate设为Interpolate或Extrapolate让渲染线程补间物理状态。如果是Kinematic刚体通过Transform移动也可以在LateUpdate里做插值。还有一个冷门的点两个静态碰撞体重叠也会让动态物体抖动。比如一块地板上面又放了一个碰撞体动态物体滚过时会产生微小的连续碰撞反馈看起来像是在震。用Wireframe模式看一下重叠就知道了。5.3 瓷砖拼接处的碰撞卡顿角色走过地面接缝被绊住地面由多块同样大小的Tile拼成角色行走时在接缝处有明显卡顿。这个问题常见于2D游戏的地图。原因每块地面瓷砖有自己的Collider两块碰撞体之间有一条微小的缝隙或重叠区域角色脚下的Capsule Collider在跨越接缝时和两侧碰撞体的接触面不断变化摩擦力和法线方向改变导致速度损失。我的做法是把多个相邻的地面碰撞体合成为一个或者用Tilemap Collider加上Composite Collider 2D把多个Tile拼成一个复合碰撞体消除接缝。1-2D里同名组件是Tilemap Collider 2D和Composite Collider 2D。3D项目则可以用一个大的Box Collider代替多个小碰撞体。6. 刚体运动技术选型什么时候用物理什么时候别用6.1 物理驱动的优势与代价物理驱动的最大优势是模拟真实。爆炸把石头掀飞石块撞到墙上反弹、落地滚动、彼此挤压——这些效果如果要手写需要大量碰撞检测和动力学代码。用RigidbodyCollider天然就能得到。代价是物理计算的不可控性和性能开销。物理引擎一个著名的局限是睡眠系统当一个刚体长时间不动引擎会把它标记为睡眠状态停止更新它的物理数据以节省性能。被外力撞到时会自动唤醒。睡眠系统本身是个优化但偶尔会带来诡异现象物体明明悬在半空却不动可能因为碰撞体刚好差0.001米接触地面摩擦力大于重力。遇到这种情况试着给它一个极小的初始速度或者用WakeUp唤醒。在手机平台上大量物理刚体的性能开销很可观。通常的优化思路远处、不可见的物体直接禁用Rigidbody或整个GameObject。不参与实时碰撞的物体物理计算结束后手动设为睡眠。用对象池管理投射物避免频繁创建销毁。场景里的静态装饰物只用ColliderIs Trigger别上刚体。6.2 非物理驱动什么时候该用Character Controller很多动作游戏的主角不使用RigidbodyCollider而是用Character Controller组件。原因很简单真实的物理控制角色很难精确控制轻轻一推就滑上坡打滑、下坡加速手感极不稳定。Character Controller本质是一个独立的碰撞体它用Move()方法移动不受物理引擎的力影响自带斜坡检测和步进能力。它不需要Rigidbody。适用场景包括第三人称动作游戏的主角、需要精确跳跃手感的人物、俯视角RPG的操作角色。我在项目里的经验是凡是对操作精度有要求的角色控制优先用Character Controller或者自研的射线/胶囊体检测移动凡是物理效果占主导投掷物、碎片、被击退的怪物才用Rigidbody。混合使用时比如怪物的攻击让主角被击退可以在主角的控制器逻辑里手动加一段位移而不是把主角整个交给物理引擎。6.3 刚体与动画的协同骨骼动画角色身上的碰撞体怎么挂角色身上有Animator播放动画同时又挂了Rigidbody和Capsule Collider。最常见的问题是角色动画播放时下半身穿过墙壁或者被门挤扁。原因不在于碰撞体不够大而在于动画驱动Transform和物理驱动Rigidbody是两套并行的移动系统。动画系统会把角色骨骼设置为特定坐标物理系统计算出的碰撞位置和动画骨骼位置冲突。解决办法有三个方向角色的根运动Root Motion开启让动画位移和刚体位置统一。用Kinematic Rigidbody 动画驱动Transform物理只负责被其他物体推开时做响应。将角色拆成两部分上半身动画驱动、下半身物理驱动用Joint连接模拟四肢的铰链、弹簧等物理约束。第三种最复杂但效果最真实。7. 实际项目中的配置建议与性能调优7.1 一套推荐的刚体默认参数模板不同项目对物理手感要求不同但有几个推荐的出发点。我在新项目中通常这样配地面/静态墙体只有Collider无Rigidbody。可推动箱子Box Collider RigidbodyMass设为10Use Gravity开启Interpolate设为InterpolateCollision Detection在低优先级物体上用Discrete。高速投射物Sphere Collider RigidbodyMass很小0.1Collision Detection设为Continuous。玩家角色Character Controller优先如果用Rigidbody方案把Mass设为70接近真人Interpolate用InterpolateCollision Detection用Continuous Speculative。顺便说一句Continuous Dynamic和Continuous的区别前者用于移动速度非常快且需要防止穿透的物体后者只用来防止物体穿透静态碰撞体。Continuous Dynamic的开销更大别滥用。提示Interpolate的Interpolate模式假设物理步进落后于渲染帧率Extrapolate模式假设物理步进领先于渲染帧率。多数情况下Interpolate手感更稳因为Extrapolate遇到物理卡顿会出现预测过头的现象。7.2 碰撞检测Layer与物理矩阵Unity的Physics面板里有一张Layer Collision Matrix它决定哪些层的物体之间会发生碰撞。默认全部勾选意味着任何物体都可能和任何物体碰撞。在项目里应该主动管理物理碰撞层把玩家、敌人、场景、投射物、道具分别放到不同的Layer。在Collision Matrix里关掉不需要互相碰撞的层比如玩家和敌人不直接碰撞伤害靠触发器投射物不碰撞玩家避免同一阵营互相卡位。这不仅是性能优化也是逻辑隔离能大幅减少OnCollisionEnter回调的误触发。举个例子我做一个俯视角射击游戏时把子弹放在Bullet层只让它和World层碰撞其他层全部关闭。这样子弹永远穿不过玩家和敌人之间的碰撞事件省掉了大量无效检测。7.3 物理查询优化避免每帧Alloc和Overlap很多开发者习惯在Update里用Physics.OverlapSphere检测敌人每次都new一个Collider数组存放结果触发GC压力。更好的做法是复用同一个数组在Update里清空不是重新创建。用非分配版本比如Physics.OverlapSphereNonAlloc。降低检测频率比如在FixedUpdate里检测而不是每个渲染帧都检测。用LayerMask参数过滤掉无关对象减少检测数量。这些调整单独看微乎其微但一个场景里有几十个带Overlap的脚本时GC开销和物理开销叠加起来就成了卡顿的主要来源。8. 用调试可视化验证你的物理配置是否正确8.1 用Wireframe和Gizmos检查碰撞体Scene视图右上角有个Wireframe按钮打开后能看到所有Collider的边界框。这是排查碰撞问题的第一道工具。我习惯在每个场景布局完成后把游戏切到Wireframe模式转一圈检查地面碰撞体有没有和模型边缘对齐。角色胶囊体是否包裹住了视觉模型但又不过分大。触发器区域是否在预期的位置大小是否合理。另外可以用代码画Gizmos在OnDrawGizmos里把重要碰撞体画出来特别是那些运行时动态创建的碰撞体编辑器里看不到只能靠Gizmos验证。8.2 观察物理引擎的状态用Debug.Log验证回调顺序当你怀疑物理交互异常但肉眼看不出来时用日志验证事件顺序。比如在OnCollisionEnter、OnCollisionStay、OnCollisionExit里各打一行日志观察连续碰撞时的触发节奏。注意OnCollisionStay只在物理步进内检测到持续接触时触发不是每渲染帧触发。8.3 利用Physic Debugger面板Unity自带的Physics DebuggerWindow Analysis Physics Debugger能实时显示所有刚体和碰撞体的状态、睡眠/唤醒状态、碰撞点和接触信息。看到刚体被标成红色就说明它处于唤醒状态如果场景里大量远距离物体一直处于唤醒状态说明它们没有正确睡眠这是个性能警报。9. 进阶刚体插值与碰撞检测模式的实际测试9.1 什么时候用Continuous SpeculativeContinuous Speculative是介于Discrete和Continuous之间的方案。它不会像Continuous那样在每个子步进里做扫描检测而是预测刚体未来一小段时间的碰撞情况提前处理。适用场景高速移动且不希望额外性能开销过大的物体例如快速挥舞的武器、跑步的角色。它的缺点是有时候预测结果不准特别是物体突然改变方向时。我的建议是角色和常规物体用Continuous Speculative高速子弹和投射物用Continuous Dynamic逃跑和弹射物体用Discrete也可以但别超过某个速度阈值。9.2 实测同一个物体用不同模式下穿墙的概率我做过一个简单测试一颗5米/秒速度的球体撞击0.5米厚的墙。在Discrete模式下球经常穿过墙体或者卡进去一点弹出来Continuous模式下穿越率明显下降Continuous Dynamic下几乎不穿。但Continuous模式下物理计算耗时大约是Discrete的1.5倍如果场景里有50颗球同时在飞性能差异立刻体现出来。结论是碰撞检测模式不是越高越好而是按物体速度划分等级提高即可。设置一个事件系统把不同的物体归类到不同等级比全局调高可靠得多。9.3 刚体睡眠和唤醒的细节处理刚体睡眠状态可以在Inspector里看到代码可以用Rigidbody.IsSleeping()检测。手动让你知道一个物体即将进入睡眠时可以用Rigidbody.Sleep()强制睡眠反之用Rigidbody.WakeUp()强制唤醒。这里有个实战技巧物体长期静止时把Rigidbody.Sleep()和Collider的IsTrigger组合使用能让触发器区域不再触发。用这个方式实现一次性触发陷阱非常省性能。10. 总结一些我踩过坑后的感悟做Unity物理相关开发这几年我最大的体会是Collider和Rigidbody不是挂上就完事的组件而是一个需要整体思考和调试的系统。很多时候看似是碰撞问题根源在移动方式看似是移动问题根源在碰撞体形状。比如我一遇到角色穿墙第一反应是调Collision Detection但后来发现多半是因为代码里直接改了Transform.position。学会把移动方式和碰撞响应当作一个整体来看排查效率提升了好几倍。如果你正在做一个需要大量物理交互的项目我强烈建议一开始就把物理层级、碰撞矩阵、刚体模板定好。项目后期再调整这些全局配置代价极高。因为你可能得在几十个Prefab里改组件参数还可能引发连锁的物理效果变化。最后分享一个小技巧在调试物理相关的Bug时把Time.timeScale调成0.2或0.1看清楚每帧的碰撞变化比读一百遍文档都有效。物理系统的很多诡异行为慢放之后一眼就能看出本质。
返回列表