ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从动量守恒到恢复系数:射击游戏碰撞分类与物理引擎调参实战

2026/9/23 4:11:02 拓冰建站 浏览量
从动量守恒到恢复系数:射击游戏碰撞分类与物理引擎调参实战 作为一个既教物理又折腾独立游戏的人我经常在FPS游戏里被朋友问这游戏的枪为什么打人会有击退效果为什么我跳起来撞到墙角会被卡住其实这些看似细枝末节的游戏手感背后全是物理里碰撞分类的基本功。你平时在教科书上看到的是弹性碰撞、非弹性碰撞几个干巴巴的名词但它在射击游戏里却直接决定了子弹打中敌人时是噗一下嵌入、还是砰一声弹开、还是整个人被掀飞出去。这篇文章我不打算讲枯燥的公式推导而是用尽量生活化的类比把这三种碰撞类型弹性碰撞、完全非弹性碰撞、一般非弹性碰撞彻底讲透再带着你把它们套到射击游戏的枪械反馈、物理子弹、爆炸冲击、人物碰撞体这些真实场景里看看游戏引擎是怎么用这些物理规则营造手感的。不管你是中学物理还没忘完的普通玩家还是正在做自己的射击游戏Demo的开发新手这篇文章都应该能让你收获一点原来如此的感觉。1. 先搞懂碰撞的分类底层逻辑1.1 碰撞到底是什么——从动量守恒说起要理解碰撞分类第一个绕不开的概念是动量。物理课本里说的动量是物体质量和速度的乘积公式写成 (p mv)它描述的是一个物体运动的强度。两个物体碰到一起如果不考虑地面摩擦力、空气阻力这些外部干扰那么碰撞前后它们的总动量是守恒的这就是动量守恒定律。这个公式本身很简单但它是个很可怕的约束——不管两个物体撞成什么样、碎成几块只要没有外力参与碰撞前后的总动量必须一模一样。这就是碰撞分类的根基动量守恒是所有碰撞都必须遵守的底线而碰撞分类则取决于在满足这个底线之外还有没有额外多守住一条动能守恒。很多人把动能和动量搞混这里用一个生活化类比说明动量像一个奔跑中的大胖子动能像他那股撞上去能把人怼飞的冲击力。两个物体相撞动量必然守恒这是物理世界不允许讨论的规则但动能可不一定——如果碰撞过程中有声音、有热量、有形变那动能就有一部分被偷走变成了内能或声能。碰撞分类本质上就是看动能有没有被偷走、偷走了多少。1.2 为什么碰撞分类应该看成一条连续谱教科书里的分类是弹性碰撞、非弹性碰撞、完全非弹性碰撞。但真正做游戏物理、或者做物理题的时候我更喜欢把它理解成一条连续谱而不是三个互不相干的抽屉。这条谱的两端是理想状态。一端是弹性碰撞动能完全守恒碰撞前后总动能一样现实里几乎不存在只是某些情况下非常接近——比如两颗台球对撞、牛顿摆里的钢球互撞。另一端是完全非弹性碰撞两个物体碰完直接粘在一起动能损失最大比如一颗子弹射进沙袋里卡住不动。而现实世界里绝大多数碰撞都落在两端之间叫一般非弹性碰撞动能有损失但没损失到粘在一起的程度。比如你玩碰碰车两车撞完会弹开一些但不会有台球那种干脆利落的弹性——一部分动能被车的形变、轮胎摩擦和碰撞时砰的噪音吃掉了。物理学里有个概念叫恢复系数公式是 (e \frac{v_2 - v_1}{v_1 - v_2})。看不懂没关系你可以粗暴理解成碰完之后分开的猛烈程度。(e1) 是理想弹性碰撞(e0) 是完全非弹性碰撞游戏物理引擎里的Bounciness参数本质就是干这个的。2. 弹性碰撞与非弹性碰撞的物理细节拆解2.1 弹性碰撞能量守恒的完美世界弹性碰撞最神的性质不是动量守恒而是它额外守住了动能守恒。以一个运动小球撞另一个静止小球这个最简单场景为例设运动小球质量为 (m_1)速度为 (v_1)静止小球质量为 (m_2)碰后速度分别为 (v_1) 和 (v_2)。动量守恒给出一个方程[ m_1 v_1 m_1 v_1 m_2 v_2 ]动能守恒再给出一个方程[ \frac{1}{2} m_1 v_1^2 \frac{1}{2} m_1 v_1^2 \frac{1}{2} m_2 v_2^2 ]两个方程联立求解就能得到碰后的速度[ v_1 \frac{(m_1 - m_2)}{m_1 m_2} v_1 ][ v_2 \frac{2 m_1}{m_1 m_2} v_1 ]这两个公式看起来吓人但背后藏着特别反直觉的结论如果 (m_1 m_2)也就是两个球质量相等你会发现碰后运动球的速度完全变成0原来静止的球则完全获得了碰撞前的速度——这就是台球桌上经常看到的定球效果也是牛顿摆最让人着迷的地方。质量差距越大轻球越容易被弹飞重球几乎纹丝不动。2.2 非弹性碰撞现实世界的主旋律如果是非弹性碰撞那动力学方程里就得损失掉一部分动能。现实里这种损失主要是三块形变消耗物体凹进去再弹回来但回不到原样、热能消耗摩擦生热和声能消耗碰撞发出的响声。因为动能不守恒我们不能再写动能守恒方程只能靠恢复系数 (e) 来做补充。碰后相对速度等于原相对速度乘以 (e)所以 (e0.6) 就意味着碰完以后互相分开的速度只有撞之前靠近速度的60%。一般非弹性碰撞算起来很麻烦但搞懂完全非弹性碰撞就够了。完全非弹性碰撞的定义极端简单碰完两个物体以同一个速度一起运动。既然粘在一起了恢复系数就是0。拿子弹射沙包举例设子弹质量 (m)、速度 (v)沙包质量 (M)、初始静止碰撞后黏在一起的速度 (V) 可以直接算[ m v (m M) V ][ V \frac{m}{m M} v ]子弹质量越小、沙包质量越大最后共同速度就越小。这一个公式其实就是射击游戏里击退效果的物理原型。2.3 三个碰撞类型快速对比表碰撞类型恢复系数e动量守恒动能守恒现实/游戏典型例子弹性碰撞e 1守恒完全守恒台球对撞、牛顿摆、理想化粒子一般非弹性碰撞0 e 1守恒部分损失篮球落地弹起、汽车追尾、Ragdoll撞墙完全非弹性碰撞e 0守恒损失最大子弹射入沙包、两个泥团相撞、黏弹粘合这张表看起来简单但它背后有个特别值得新手记住的点在物理引擎里头所有碰撞类型的代码底子都是一样的——先保证动量守恒再用恢复系数 (e) 去调整分离速度。你肉眼看到的弹开还是黏住其实只是同一个公式在不同参数下的表现。3. 射击游戏引擎里碰撞是怎么跑的3.1 从物理公式到游戏引擎碰撞体的思想游戏引擎当然不会像物理课本那样解方程但原理是一脉相承的。在Unity、Unreal这些引擎里一个可碰撞的对象通常由三件套组成碰撞体Collider定义物体的形状范围、刚体Rigidbody决定物体是否受物理影响和物理材质Physics Material提供摩擦力与弹力系数。这里面物理材质的Bounciness参数就是物理世界恢复系数 (e) 在主流的游戏引擎里的直译。调成1物体撞啥都像打了鸡血一样弹个没完调成0碰撞后基本就卸力了。我们在FPS里踩墙跳、手雷弹墙、敌人中弹后飞出去背后全靠这个数值。一个常见的误区是很多人觉得游戏里的碰撞就应该100%还原现实物理。真那样做FPS根本没法玩——真实子弹动能一枪能掀翻成年人吗游戏需要的是看起来像那么回事、打起来很爽的伪物理。所以引擎里几乎所有碰撞都是经过艺术化调参后的结果。3.2 子弹命中目标典型的完全非弹性碰撞案例先说最核心的应用子弹击中人体。现实里子弹射进肉体绝大多数能量被组织吸收、子弹留在体内或穿出这是典型的完全非弹性碰撞。在游戏里这种非弹性体现在两个地方一是伤害结算二是受击反馈。伤害结算用物理公式其实是杀鸡用牛刀游戏一般直接按命中部位和距离查表。但受击反馈就纯粹靠物理了。当子弹命中角色时客户端会计算一个冲击力施加到角色的Rigidbody上让角色产生轻微后仰或位移。这个冲击力可以直接从动量定理推出来子弹动量的变化量乘以一个修正系数再除以受击角色质量就能估算出角色被推倒的速度。我在自己的FPS Demo里写过这样一段子弹命中后的受击逻辑Vector3 bulletVelocity bulletRigidbody.velocity; float bulletMass bulletRigidbody.mass; // 子弹命中瞬间的动量变化模拟完全非弹性碰撞 Vector3 impactImpulse bulletVelocity * bulletMass * hitReactionScale; // 把冲击力加到目标刚体上 targetRigidbody.AddForceAtPosition(impactImpulse, hitPoint, ForceMode.Impulse); // 同时给一个极短时间的HitStop让枪感更清脆 StartCoroutine(FreezeFrames(hitStopDuration));这里有个细节很多人不知道为什么要用AddForceAtPosition而不是直接在Rigidbody上AddForce因为冲击力施加位置不同角色受力后的运动状态完全不同——打在胸口角色是整体后退打在头部角色会向后仰甚至旋转倒地。AddForceAtPosition能精确控制撞击点实现出更自然的受击动画。3.3 人物移动和跳跃弹性碰撞的偷懒版本人物与场景的碰撞看起来和弹性不太沾边因为我们不希望角色被墙壁弹得乱飞。但跳床和弹跳板的设计就要反过来用它。正常角色控制器CharacterController里的碰撞其实是挡住就停的逻辑几乎没有弹性恢复这相当于把地面和墙壁的恢复系数强行压到很低的水平。但如果你做一张跳床或者想让玩家从高处落地时有一个缓冲弹起的手感就要单独给那块碰撞面设置一个高Bounciness的物理材质再配合速度衰减公式[ v_{bounce} e \cdot v_{impact} ]假如你从高处落地撞击瞬间速度为20米/秒地面物理材质的Bounciness设为0.5那弹起速度就是10米/秒。这个公式和你在物理课上学过的恢复系数完全是一回事但游戏里的地面往往还会给弹起方向加一个操控系数让玩家能微调落点。这里有一个无数人踩过的坑如果你把角色整个当成普通刚体放到跳床上碰上Bounciness1的材质它会无限反弹、越弹越高玩家完全没法操控撞两次摄像头都能晃晕。正确的做法是给角色加一个最高弹跳速度限制或者在角色落地弹起瞬间覆盖恢复系数。3.4 爆炸冲击波与Ragdoll真实物理在游戏中的艺术化爆炸伤害是射击游戏里最有画面感的物理时刻。手雷爆开时会对爆炸半径内的所有刚体施加径向冲量这个冲量的大小遵循类似平方反比的衰减公式[ F \frac{k}{r^2} \cdot m ]其中 (r) 是物体到爆炸中心的距离(k) 是爆炸强度系数。距离越远受力越小这是从点爆炸能量扩散规律简化来的。而物理引擎里实际做的时候会用球形范围检测加Falloff曲线来逼近不会真的做一次平方反比——因为在每秒几十帧的游戏里平方反比意味着近距离冲量无穷大很容易把物体炸出物理边界。Ragdoll布娃娃物理系统则是另一个经典应用。角色死亡后骨骼切换成一个由多个刚体关节组成的布娃娃模型这些刚体之间的碰撞就是典型的非弹性碰撞——身体部位碰到地面会反弹一点点但因为肌肉张力消失几乎不会产生真正的回弹。这个刚刚好的效果就是靠把每个关节的物理材质Bounciness压到0.1到0.2之间实现的。4. 游戏物理中的碰撞参数调优与实战避坑4.1 物理材质Bounciness设置的实战经验在FPS项目里给不同表面设置Bounciness是门手艺活。我试过几套参数直接贴出来供参考表面类型BouncinessFriction典型用途混凝土/地面0.11.0角色落地、手雷触地不弹起金属墙壁0.30.2手雷反弹、子弹弹射跳床/弹性板0.8-1.00弹跳玩法尸体/布娃娃0.10.5死亡动画自然倒地冰块0.30滑行、打滑这里有个很微妙的问题手雷的Bounciness设计。真实手雷落地基本不弹但游戏里如果Bounciness设成0手雷落地就像泥巴一样瘫在地上视觉效果非常死板。我一般设0.25到0.35让它有明显但克制的两三次弹跳给玩家留出反应时间同时手感和真实感都能兼顾。4.2 碰撞体和刚体常见问题物理做多了你会发现真正折磨人的不是公式而是引擎里那些物理发疯的瞬间。我遇到的三个典型问题第一个是抖动Jitter。刚体物体紧贴地面时会因为重力不断被压进碰撞体再弹出来产生高频震动。解决办法一个是把物体的Solver Iterations调高一点另一个是干脆不要让它当纯刚体用运动学刚体插值来处理。第二个是隧道效应Tunneling。子弹飞太快一帧之内穿透了一面薄墙这在子弹射击游戏里特别致命。引擎的解决办法是Continuous Collision Detection连续碰撞检测原理是把物体这一帧的运动轨迹当成一条扫掠体来做检测而不是只检测终点位置。代价是性能开销大所以一般只给子弹、高速导弹启用。问题现象常见原因推荐排查方案物体抖动/抖动漂移物理材质摩擦设置过高、刚体睡眠判定异常降低摩擦调整SleepThreshold开启刚体插值高速子弹穿透薄墙默认离散碰撞检测只查末位置使用Continuous或ContinuousSpeculative模式人物被碰撞体卡住碰撞体边缘过薄、多个碰撞体叠加给人物加Capsule碰撞体避免使用复杂Mesh Collider网络延迟导致位置跳变客户端预测误差服务器回滚不一致用快照插值加延迟补偿或在服务器端做HitValidation手雷弹飞方向随机地面存在网格接缝碰撞法线突变在爆炸点做射线检测手工修正初速度方向第三个是网络同步。这是射击游戏物理里最坑的地方。本地玩家丢一个手雷手雷在客户端A有物理模拟但在客户端B要看到同样的弹跳轨迹就需要服务器协调。现在市面上主流的做法是服务器权威客户端预测——物理计算在服务器跑客户端本地预测路径出现偏差时做平滑修正。物理参数稍微偏一点两个端的手雷轨迹就会天差地别所以打包时物理材质和初始速度必须做成可同步的参数。4.3 性能优化思路物理碰撞看着普通实际是射击游戏性能的大坑。一个手雷爆炸要检测周围30个刚体一发霰弹枪7颗子弹、每颗都要做碰撞检测几十人混战的时候物理计算量能直接把CPU拖垮。我的优化思路有几条碰撞分层是必须的。把子弹、角色、场景做成不同的Layer只让该碰撞的层做检测比如子弹层只检测角色层和场景层绝不检测其他子弹。引擎里每个物理每帧都在广播自己的位置分层之后广播数量呈指数级下降。另一个技巧是动态调整物理更新频率。远处的敌人和近处的敌人没有必要每帧都跑物理可以分成两个或多个物理调度等级近距离每秒60次物理更新中距离30次远距离10次。这个方案在《绝地求生》这类大地图射击游戏里很常见因为远处中弹的敌人根本没有必要做高精度的物理反馈——反正玩家也看不清。还有一个容易被忽视的点是碰撞体的形状选择。很多初学者喜欢用Mesh Collider去精准拟合场景模型但Mesh Collider的计算量是Box和Sphere的十几倍。射击游戏里的墙壁其实不需要那么精确的碰撞形状用几个Box Collider拼出来玩家完全感觉不到区别但性能提升是肉眼可见的。5. 我个人在处理碰撞时的几个心得聊到这儿基本的分类和游戏应用都过了一遍。最后分享几个我实际做项目中总结出来的经验希望对你有直接的参考价值。第一个心得是做游戏物理前先把物理公式写清楚哪怕最后引擎一个公式都没用上。我在做碰撞系统时把动量守恒、恢复系数、完全非弹性碰撞公式全部推导了一遍结果写代码时发现所有逻辑都能对号入座——子弹击退用的是动量定理手雷爆炸用的是类平方反比衰减跳床弹跳用的就是恢复系数公式。公式是物理世界的地图引擎只是交通工具没有地图你连该调哪个参数都不知道。第二个心得是精心调过的Bounciness参数一定要全项目统一保存。我见过太多项目里同一个地面在这个场景Bounciness是0.1换个场景又变成0.3结果手雷弹跳手感完全不一致玩家会觉得这游戏物理有问题。做一个物理参数总表把所有表面材质统一管理是一个成熟项目的基本功。第三个心得是物理手感不是越真实越好而是越符合预期越好。射击游戏玩家不会去管真实子弹击中人体能不能把人击飞他只关心这一枪打中了、反馈清晰、玩起来爽。所以我做受击反馈时会刻意放大击退效果把真实的冲击力乘以1.2到1.5的系数让中弹后的位移更明显。这不算欺骗——这是游戏设计与物理的合理结合。如果你想试着做自己的FPS物理玩法建议从最简单的手雷弹跳开始给一个球体设置刚体调整Bounciness为0.3丢出去观察两次弹跳衰减然后把恢复系数改成0.8观察越弹越欢的失控感。亲手把玩过这两个极端你就能深刻理解恢复系数这条连续谱两端到底意味着什么也就能明白射击游戏里的每一个砰背后其实都是物理规则在替你讲故事。