拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Unity Rigidbody碰撞穿透与抖动问题排查:从原理到实践

项目概述1. 问题全貌为什么Rigidbody碰撞还会穿模和抖动Unity里用Rigidbody做物理交互最让人抓狂的就是两个看似矛盾又经常一起出现的问题碰撞穿透和碰撞抖动。明明加上了刚体设置了碰撞体物体还是像幽灵一样穿过墙壁或者在接触面上抖得跟筛糠似的。这篇文章不是讲物理引擎的那套底层算法而是把我自己项目里踩过的坑、排查过的案例、最后沉淀下来的解决方案一次性说清楚。先说结论碰撞穿透和碰撞抖动本质上都是物理引擎在“处理接触”时的误差累积只是触发场景不同。穿透通常是“来不及检测”或者“根本没检测”抖动则是“检测到了但处理不稳定”。理解了这个底层逻辑后面所有的排查手段就有了方向不会东一榔头西一棒子。文章适合三类人看刚接触Unity物理系统不久、被碰撞体搞到心态爆炸的新手在正式项目里遇到“物体莫名其妙穿墙”、“刚体碰一下就疯狂抖”这种诡异Bug需要快速定位的开发者以及想系统梳理Unity物理参数、避免在性能优化阶段回头补课的进阶玩家。文章里涉及的操作我都标注了适用场景和注意事项物理引擎的很多参数不是越大越好也不是越小越稳关键是找到适合你项目的那组值。主体2. 碰撞穿透的根因分析与解决思路2.1 穿透的第一大元凶运动速度超过了物理检测频率先说一个最容易被忽略的事实Unity的物理引擎不是在每一帧渲染时都做碰撞检测的它有自己的固定频率。这个频率就是Project Settings里的Fixed Timestep默认是0.02秒也就是每秒做50次物理计算。这么设计是为了保证物理模拟的稳定性避免因为不同设备的帧率差异导致物理表现不一致。但代价是如果一个物体在0.02秒内移动的距离超过了它碰撞体的尺寸物理引擎就有概率完全跳过碰撞检测直接就穿过去了。我举个例子你马上就能理解你的子弹碰撞体半径只有0.1米但它的飞行速度是100米/秒。在物理引擎的两次检测之间0.02秒这道子弹已经飞了2米也就是说它在物理引擎的“视野”里直接从墙的一侧跳到了另一侧墙对它来说就像不存在一样。这种情况在射击类游戏、跑酷类游戏里特别常见因为高速运动是标配。解决思路有以下几条按优先级排列开启连续碰撞检测Continuous Collision Detection简称CCD这是最直接的方案。把Rigidbody的Collision Detection设置从Discrete改成Continuous或者Continuous Dynamic物理引擎就会对这类物体做更密集的扫掠检测而不是只检测两个离散的时间点。适当调小Fixed Timestep比如从0.02改成0.01物理检测频率翻倍穿透概率也会骤降。代价是物理计算量翻倍对性能有影响不建议在移动端或者大场景里无脑用。用射线检测Raycast代替物理碰撞。射线检测是瞬时的它只计算“这条线段穿过了什么物体”不涉及持续的速度积分。对于子弹、激光这类纯粹的“飞行物”射线检测反而是最稳定、性能最好的方案。2.2 穿透的第二大原因碰撞体本身就薄得离谱有些时候物体速度不算快但还是会穿模。这时候你得检查一下你的碰撞体是不是太薄了。比如一堵厚度只有0.05米的墙如果物理检测频率是50Hz哪怕物体速度只有1米/秒它也有概率被“跳过”这堵墙。我把这个场景拆开给你看物体朝墙面移动物理引擎检测到的时候物体可能已经在墙的另一侧了。因为墙太薄物体的包围盒在两次检测之间“跃迁”的距离比墙还宽物理引擎就认为它们从未接触过。对于这种问题优先从建模层面解决不要用薄片状的Mesh Collider也别把Box Collider的厚度调成零点几毫米。哪怕是看起来应该是“墙面”的东西也至少保持0.1米以上的物理厚度。如果美术模型不允许修改可以加一个不可见的额外碰撞体作为“物理墙体”不让玩家看到但保证物理交互稳定。还有一个很常见的坑Mesh Collider默认的Convex凸包选项是不勾选的。如果勾选了ConvexMesh Collider会被简化为凸包形状对于凹形物体比如一个U型管道就会造成检测误差如果不勾选Convex它虽然能精确匹配模型形状又能做精确碰撞检测但性能开销大而且非Convex的Mesh Collider不能与另一个非Convex的Mesh Collider发生碰撞。这一点在设置里看不出来只有实际运行才会发现问题。2.3 穿透的第三大元凶碰撞体组合与Trim设置不合理物理引擎在检测碰撞时不是每对碰撞体都做最精细的检测而是有一套“优化策略”。默认情况下Unity会把Fast Moving Body快速运动物体单独标记并对它们做更精确的检测。但如果你把场景里一半的物体都标记成了Continuous Dynamic物理引擎的负担会呈指数级上升。这时候它可能会“偷工减料”反而降低了一些碰撞对的检测精度。我的建议是只有真正高速运动的物体子弹、飞镖、快速投掷物才开启CCD其余物体一律用Discrete。这样的话物理引擎的负担可控也不用担心普通物体之间出现穿透。很多入门项目里开发者图省事把一整个场景的Rigidbody全部设成Continuous结果不仅穿透没解决帧率反而掉得厉害这就是典型的“用错了工具”。还有一点容易忽略Rigidbody默认的Sleeping Threshold休眠阈值。当一个物体长时间处于低速或静止状态物理引擎会把它标记为“休眠”不再对它做完整的物理计算。这本身是性能优化没问题。但如果物体在休眠状态下被外力推动或者与另一个休眠物体发生了位置重叠重新唤醒时可能就会产生穿透或弹跳异常的Bug。这种情况我在做密室逃脱类游戏时遇到过布置好的家具和道具玩家一开始没动它们物理引擎就把它们休眠了。等玩家走过去推动其中一把椅子椅子开始移动但和它重叠的桌子还没来得及唤醒椅子就直接穿过桌子了。解决方法是在生成物体时先调用Rigidbody.WakeUp()或者干脆把所有动态物体默认设置为不睡眠。3. 碰撞抖动的机制拆解与实操调优3.1 抖动是怎么产生的一个“推回去-又被压回来”的死循环碰撞抖动的表现是物体在接触面上高频率地来回移动看起来像是被“电”了一样。它的原理其实不复杂物理引擎在检测到碰撞后会计算穿透深度然后把物体“推开”到安全位置。但如果物体的重力、压力、或者外部推力持续把它“推回”碰撞体内部物理引擎就不得不在每一帧都做一遍“穿透-推出”的操作视觉上就成了抖动。你可以把这个过程想象成一个人反复去推一扇门门被推开一条缝但门外一直有人用恒力把门往回顶结果门就在“打开-关闭-打开-关闭”的循环里不停颤动。物理引擎也在做同样的循环只是循环频率是50Hz所以肉眼看到的是高频抖动。这个问题的根源通常有两个一是穿透发生物理引擎在检测到穿透后强行修正二是约束求解不收敛物理引擎在计算多个接触点、摩擦力、法向力时没有找到一个稳定的解。特别是当一个物体同时与多个碰撞体接触时比如叠在一起的箱子约束方程越多求解器就越可能发生振荡。3.2 抖动最典型的场景刚体堆叠与多层碰撞体嵌套先说一个最常见的抖动场景刚体叠放。你把十个箱子叠在一起它们之间会产生大量的接触点。每一条接触都由物理引擎的求解器处理而求解器是分批迭代的——不是一次算出所有力的平衡而是每轮迭代调整一个局部区域然后下一轮再调整。如果迭代次数不够或者约束数量太多就会出现箱子越叠越抖的情况尤其是在最底层的箱子受力最大的时候。解决这个问题的思路有四个调整Rigidbody的Interpolate插值设置从不插值改成Interpolate或Extrapolate。插值可以让物理位置的更新在渲染帧之间平滑过渡避免因为物理频率50Hz和渲染频率通常60Hz以上不一致造成的视觉抖动。这个方法对大部分“看起来轻微抖动”的情况都有奇效。减少单帧内的同时接触数量把大刚体拆分为多个较小刚体但用关节Joint连接。这是一个偏进阶的做法用少量关节替代大量碰撞接触物理引擎处理起来更稳定。把Collision Detection设为Continuous Dynamic的同时确认底层的Static Collider本身有足够的厚度和稳定的位置。经常有开发者把一面墙的Collider放在移动的物体上或者挂在被动画驱动的子节点下这会让物理引擎无所适从。检查物理材质Physics Material的摩擦力和弹性设置。抖动的最常见原因之一是把Friction摩擦力设置得过高或者把Bounciness弹性设置成了大于0的值但没有配合足够小的弹性阈值Bounce Threshold。这里有个很实用的经验值大多数需要稳定堆叠的场景物理材质的Friction设为0.4-0.6之间Bounciness设为0Bounce Threshold设为1默认值是2。如果你的物体在接触面上总是“蹦蹦跳跳”先怀疑Bounciness再怀疑跟地面接触的碰撞体材质。3.3 抖动的另一大类成因物理位置与渲染位置不同步这一节要说的是我个人认为是Unity物理里最隐蔽、也最让人摸不着头脑的抖动来源物理引擎计算的位置和你在Transform里看到的位置压根就不是同一个东西。Unity的物理模拟在自己独立的世界里运行。Rigidbody会维护一个内部的物理位置这个位置通过物理引擎的固定频率更新。渲染层使用的Transform.position则是每帧被Rigidbody同步一次。如果同步不及时或者你在脚本里直接修改了Transform.positionRigidbody的物理位置和Transform的位置就会发生冲突物理引擎会尝试“纠正”这个错误表现出来就是抖动、跳变或者奇怪的弹射。我在一个项目里就踩过这个坑用脚本控制一扇门打开我直接写了DoorTransform.position targetPosition结果门在动画期间疯狂抖动因为下方的Rigidbody每个物理帧都在把它拉回原来的地方。后来改成用Rigidbody.MovePosition()问题立刻就消失了。经验总结涉及Rigidbody的物体千万不要直接改Transform.position和Transform.rotation想让它移动要么通过施加力AddForce要么用MovePosition/Rotate来实现。这两者的区别在于AddForce是物理力驱动会产生自然的加减速MovePosition是位置驱动物理引擎会把它当成“瞬时位移”并尝试调整速度和碰撞响应。具体选哪个要看你的实际需求。想让角色按照物理规则运动和碰撞用AddForce想让一个物体“滑动”到某个精确位置同时不希望物理引擎干扰太多用MovePosition。但无论用哪种避免直接操作Transform都是一条铁律。3.4 针对“碰撞穿透抖动”的复合型问题排查法很多时候你遇到的现象不是一个单纯的穿透或单纯抖动而是混合出现的物体一开始穿过了一部分然后被物理引擎强行推回去接着因为反复穿透-修正而抖动。这种情况下单独调某个参数往往无效得用组合技。我的排查顺序是这样的按照这个顺序走能解决七八成问题第一步检查Fixed Timestep和物体最大速度算一下物体在单个物理帧内的移动距离是否超过碰撞体最小边长。如果超过了优先用CCD。第二步检查碰撞体的位置是否稳定是不是被挂在了动画驱动或脚本驱动的节点下。如果是改成挂在Rigidbody的根节点上。第三步检查物理材质把Bounciness归零Bounce Threshold调到1Friction调到0.4-0.6。第四步检查Rigidbody是否开启了插值Interpolate选项选上观察抖动是否缓解。第五步检查Rigidbody的mass和drag。mass不是越大越好如果mass值太大碰撞响应会变得极不稳定drag建议默认值0.1除非要做流体效果。第六步排查是否用了不当的碰撞体组合比如两个非凸的Mesh Collider互相碰撞。这套排查流程我用了很长时间实测下来是效率最高的。很多项目里“莫名其妙”的抖动问题最后都落在第三步和第四步而穿透问题则绝大多数是第一步造成的。4. 实操演示从零搭建一个稳定的碰撞原型4.1 场景搭建与关键参数配置这部分我带你重新搭一个项目环境用最小成本验证上面所有结论。你可以把这个原型当成一个“物理配置试验台”之后遇到任何碰撞穿透或抖动问题先把问题场景搬到这里做回归测试能大幅缩短排查时间。新建场景后步骤如下创建一个地面用Plane或者Cube都行把它的Collider保留Rigidbody不需要加。Static物体只需要Collider就能参与碰撞检测。创建一面测试墙同样用Cube厚度建议至少0.2米。把它旋转并放在地面上充当要穿透的目标。创建一个小球作为高速运动的测试物体。给它加Sphere Collider和Rigidbodyradius建议设为0.5方便观察效果。给小球写一个简单的移动脚本给Rigidbody施加一个朝向墙壁方向的力并把速度拉到足够大比如AddForce(Vector3.forward * 20000f)或者直接设置velocity为20米/秒然后观察小球的运动状态。关键参数的建议值我整理成了一个表你直接照着填参数项建议值说明Rigidbody.mass1通用值不需要调太重Rigidbody.drag0.1默认值注意不要调成0Rigidbody.angularDrag0.05旋转阻尼防止旋转抖动Collision DetectionContinuous高速物体用普通物体用DiscreteInterpolateInterpolate防视觉抖动Physics Material Friction0.4稳定接触Physics Material Bounciness0不要有弹性Bounce Threshold1比默认值更敏感按照这个配置跑起来小球应该能够稳定碰撞、不穿透、不抖动。如果依然出现穿透或抖动那问题大概率在代码层或者场景布局层往下看。4.2 一个隐藏很深的坑Fixed Timestep与渲染帧率的配合在这个原型里你可能会遇到一个情况小球运动很流畅但落地瞬间会有轻微抖动。这就涉及到Fixed Timestep和渲染帧率的配合问题。Unity的物理计算以固定频率执行默认0.02秒一次。渲染帧率则是变化的可能是60Hz、75Hz、144Hz甚至更高。物理位置每0.02秒更新一次渲染层则需要在这两个物理帧之间做插值才能在任意渲染帧率下都显示平滑的运动轨迹。Interpolate和Extrapolate这两个选项就是做这件事的。但如果你在代码里往Rigidbody上加了OnTriggerStay或者OnCollisionStay这类“每物理帧调用”的回调并在里面改了Transform的某些值就会破坏插值逻辑引起抖动。所以在物理回调里尽量只做数据记录不要改物理对象的状态。非要改的话加一个标志位等FixedUpdate结束后再统一处理。还有一类情况是协程和物理回调同时操作Rigidbody。协程的调用顺序和物理更新不同步在协程里直接给Rigidbody设速度很容易造成物理状态突变。推荐做法是协程里计算目标值把速度赋值放到FixedUpdate里执行。这个原型搭建完之后你可以用来做几组对照实验分别用Discrete和Continuous对比速度穿透效果分别用Interpolate和None对比落地抖动情况分别用Bounciness0和Bounciness0.5对比弹性反弹效果。三组对照做下来你对Unity物理参数的理解会比看十篇文档还深刻。4.3 代码层如何配合获取碰撞信息与后处理技巧在物理系统稳定之后怎么获取碰撞信息并进行自定义处理是另一个高频需求。这里分享几个我在项目里常用的写法。首先是获取碰撞点信息。在OnCollisionEnter里collision.contacts数组能拿到所有接触点每个ContactPoint包含point、normal和otherCollider。point是发生碰撞的世界坐标normal是碰撞表面法线方向。这个信息可以用来做粒子特效、弹痕或者音效触发点。其次是碰撞状态的判断。OnCollisionEnter、OnCollisionStay、OnCollisionExit分别在接触、持续接触、脱离接触时触发。但如果一个物体在穿透边缘反复进入和退出碰撞状态这三个回调会高频率触发造成不必要的开销。此时可以在回调里加一个最小间隔判断比如只有在距上次回调超过0.1秒时才做实际处理。最后是关于Trigger和Collider的区别。用IsTrigger开启的Trigger碰撞体不会产生物理阻挡效果只能用来检测“进入、停留、退出”这些状态。它适合做区域检测、收集物判断、触发机关这类逻辑不适合做物理墙。很多新手把Trigger当成Collider用结果物体穿过去了但回调依然触发这不是Bug是设计如此。如果你需要在物体穿过某个区域时既能触发逻辑又不阻挡物体比如进入一个水域时播放效果最合理的方式是在对象上挂两个碰撞体一个作为物理碰撞体不勾Trigger另一个静置Trigger区域专门做逻辑判断。5. 常见问题与排查技巧速查5.1 高频问题对照表这里整理了一张速查表可以直接当成排查手册使用。遇到问题先对着表找现象再针对性排查。现象可能原因解决方向高速物体穿透静态墙Fixed Timestep下位移过大开启Continuous或改用Raycast物体穿透另一动态物体两个物体都用Discrete且相对速度高至少其中一个开启Continuous Dynamic物体在接触面上抖动没有开Interpolate或物理材质不匹配开InterpolateFriction设为0.4-0.6Bounciness归零箱体堆叠时整体抖动接触点太多求解器不收敛减少同时参与约束的碰撞体数量或用关节替代直接改Transform导致瞬移/反弹物理位置与渲染位置冲突改用MovePosition或AddForce物体缓慢穿过薄碰撞体碰撞体太薄加厚碰撞体或添加额外的物理碰撞体物体在Trigger区域抖动Trigger不影响物理但可能与碰撞体重叠检查是否两个碰撞体重合物理材质冲突场景初始化时物体重叠后弹开休眠物体被唤醒导致重叠修正生成时避免重叠或在Start里先WakeUp再Sleep5.2 排查思路的实战演示举个例子你在做跑酷游戏玩家控制一个高速冲刺的角色碰到墙壁时偶尔会直接穿过去偶尔又会卡在墙里抖动。按照速查表你需要同时检查“高速穿透”和“接触面抖动”两个问题。首先把这个冲刺角色的Rigidbody的Collision Detection设为Continuous Dynamic然后跑一圈看穿透是否还出现。如果还出现把Fixed Timestep从0.02压到0.015此时物理更新频率提升到约66Hz穿透概率进一步下降。如果这两个操作后穿透解决了但卡墙抖动还存在进入第二步把Interpolate设为Interpolate把物理材质的Bounciness归零Friction适当调低到0.4以下。还有一个很容易被漏掉的细节角色在冲刺结束时如果脚本在OnCollisionEnter里把速度清零物理引擎的接触解算可能已经基于旧速度计算了一部分穿透修正清零后反而造成位置跳变。更好的做法是保留碰撞后的轻微速度衰减而不是直接归零。这个案例想表达的思路是碰撞问题往往不是单点参数的问题把“高速穿透”和“抖动”当成两个独立问题依次解决胜于原地打转。5.3 性能优化与物理稳定性的平衡调优物理参数时一个绕不开的话题是性能。你不可能为了稳定性把Fixed Timestep降得太低也不可能把场景中所有物体都设为Continuous。合理的思路是按物体的重要性和运动特征做分层配置玩家角色、关键NPC、投掷物使用Continuous Dynamic确保交互稳定。普通动态物体可推动的箱子、杂物用Discrete不开启CCD。纯静态场景地面、墙壁、建筑物用Static Collider不挂Rigidbody物理引擎会自动优化。另一个性能点是碰撞体的数量。一个Mesh Collider比一个Box Collider开销大得多如果一个物体能用多个Box Collider拼出接近Mesh的形状比如用4个Box拼成一个环形物理性能会好很多。当然这不是绝对的要根据实际性能测试结果决定。物理材质也可以分层地面和墙壁用一套“刚性-无摩擦”的材质箱子和可推动物体用另一套“较高摩擦-无弹性”的材质。避免创建大量只微调了一个数值的物理材质副本Unity编辑器里材质数量太多会拖慢加载和切换速度。6. 扩展进阶从碰撞到物理交互的高级玩法6.1 动态改变碰撞体与物理参数基础碰撞稳定之后很多玩法需要动态修改碰撞体和物理参数。比如角色蹲伏时碰撞体缩小、跳跃时关闭地面碰撞、气垫船在水面上切换物理材质等。这些操作的核心原则是在修改碰撞体前先停止物理模拟修改后再重新激活。否则物理引擎可能基于旧的碰撞体计算接触然后突然切换到新的碰撞体时产生穿透或抖动。具体实现是GetComponentCollider().enabled false; collider.size newSize; GetComponentCollider().enabled true;如果直接修改Rigidbody的参数比如mass和drag建议在FixedUpdate里统一赋值不要在主线程Update里频繁改避免物理引擎读取到不一致的状态。6.2 用关节模拟复杂机械结构时需要注意什么关节Joint是Unity物理系统的进阶玩法。比如用HingeJoint做门窗、用FixedJoint做拼接、用CharacterJoint做布娃娃。关节同样会遇到穿透和抖动而且比普通碰撞体对参数更敏感。我在做机械结构时踩过最大的坑是两个物体的锚点Anchor设置不准确导致关节在未受力状态下就在内部拉扯。这时候物理引擎会不断尝试修正两个物体之间的距离如果修正幅度过大就会出现高频抖动。解决办法是在生成关节时确保Anchor的位置严格位于两个物体的物理轴心点上或者直接使用Auto Configure Connected Anchor自动配置连接锚点。这个选项可以自动计算关节之间的连接点避免手动设置误差。关节的驱动参数比如HingeJoint的Target Velocity和Force Limit需要给合适的约束范围过大的Force Limit会导致关节在极限位置反复弹跳。我的经验是把Force Limit设为目标速度所需的1.5倍即可不需要盲目拉满。6.3 物理性能分析工具与优化面板排查物理问题光靠感觉是不够的要善用Unity的Profiler和Frame Debugger。Win数字键打开Window下的Profiler切入Physics模块能看到每次物理更新的耗时分布。如果发现Physics.Simulate耗时过高大概率是场景内动态碰撞体数量太多或者有非Convex的Mesh Collider在参与计算。Frame Debugger可以查看每一帧的渲染和物理状态对于“为什么这个物体在渲染层看到了但物理引擎没检测到”这类问题特别有用。它会显示物理世界的同步状态和渲染层的差异。还有一个被很多人忽略的工具Physics DebuggerProject Settings里的Physics底部有“打开物理调试器”按钮。打开后可以可视化显示所有碰撞体、接触点、关节受力。如果你怀疑某个接触点的法线方向不对可以在这里直接查看。7. 项目实战经验总结做了这么多年Unity项目我个人的体会是物理系统的问题七成是参数配置问题三成是设计模式问题。参数配置好查照着经验和速查表就能解决大半设计模式问题需要你在写代码之前就想清楚到底要不要用物理引擎。比如一个纯粹的展示性场景你只是想让几个物体放在那里好看那完全不需要给它们加Rigidbody直接用静态盒体碰撞体就够了。如果你是在做一款基于物理交互的核心玩法那就要在项目早期做好物理参数调优和性能分层不要等到上线前才来调整。还有一个很多人容易忽略的点物理材质PhysicMaterial是项目中很容易被多人协作搞得一团糟的资源。每个人改了自己的版本场景里到处引用不同的材质最后表现完全不可控。建议在项目初始化时就把物理材质做成标准化的几个预设团队所有人都在这些预设里选不允许新建自定义材质除非经过专门评审。最后再分享一个小技巧如果你在项目里频繁遇到穿透问题可以考虑给关键物体添加一个自定义的“安全网”逻辑——在Update里做一个简单的距离判断如果物体的位移量超过了物理阈值的两倍就发出警告日志。这个调试输出可以帮你快速锁定是哪些物体出了问题而不是靠肉眼在Game窗口里盯着看。物理系统是Unity里最“玄学”的部分之一但也是可控的。理解它的工作频率、接触解算机制、以及你能调整的每个参数的实际意义你会发现很多问题都是可以提前避开的。希望这篇文章能帮你省下一部分熬夜排查的时间。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门