“Unity物理引擎”这几个字几乎每个做过 Unity 项目的人都会在某个阶段跟它较劲。我最早接触它是在做一个推箱子小游戏当时以为给方块挂上 Rigidbody 和 BoxCollider重力一开就完事了结果方块堆在一起像果冻一样抖推快了直接穿墙斜面上一松手就自己往下溜。后来做数字孪生项目、做机械臂交互演示、做 VR 里的抓手和门轴才发现物理引擎远不只是“加个刚体”那么简单它是一整套关于时间步长、碰撞检测、约束求解、质量比例和性能预算的系统工程。这篇文章想把我这些年跟 Unity 物理引擎打交道的经验摊开讲清楚它底层是什么、各个参数到底在管什么、什么场景该用哪种碰撞体、什么时候该放弃物理做假物理、以及遇到抖动穿透卡帧时应该从哪一环开始查。无论你是刚学 Unity 想搞明白 Rigidbody 和 Collider 关系的新手还是已经在项目里被物理性能拖过后腿的老手都应该能在这里找到能直接抄走的东西。1. 先把 Unity 物理引擎的底层盘子摸清楚1.1 PhysX 到底在 Unity 里扮演什么角色Unity 的 3D 物理并不是自研的它集成的是 NVIDIA 的 PhysX 求解器2D 部分则是另一套 Box2D 思路的实现。这个事实很重要因为它解释了很多“为什么 Unity 的参数长这样”的问题。PhysX 的工作方式是每一帧物理更新开始时把所有刚体的位置、速度、质量、碰撞形状以及约束关系打包成一份场景描述接着做宽相检测Broadphase用空间划分快速筛掉明显不可能碰撞的对象对再做窄相检测Narrowphase对可能碰撞的形状做精确的相交测试然后进入约束求解阶段迭代计算冲量和位置修正最后把结果写回 Transform。很多新手会误以为 Unity 自己算物理于是看到Physics.simulationMode这种参数时会觉得莫名其妙。实际上把 PhysX 理解成一个“外部承包商的施工队”就顺了Unity 负责把图纸交给它场景数据负责告诉它这次要施工多久时间步长负责把施工结果搬到你家墙上Transform 同步。所以当你调整Time.fixedDeltaTime、Physics.defaultSolverIterations这些参数时你影响的其实是施工队的作业节奏而不是 Unity 本身。理解这一层还有个实际好处你会知道为什么某些参数改了没反应。比如你在运行时改Rigidbody.mass它确实会立刻生效因为质量是每次求解都会读的但你在运行时改 Collider 的形状PhysX 需要重建内部形状缓存开销比想象中大得多频繁改就会掉帧。这类“参数响应特征”的差异全都源于引擎与求解器之间的数据交换方式。顺带一提Unity 后来推出的 DOTS 体系里有一套Unity.Physics它是基于自研的 DOTS 友好物理实现走的是 ECS 数据流。两套东西不要混用也不要在同一个项目里指望它们共享碰撞体。我在一个数字孪生的项目里见过同事试图让 DOTS 的 PhysicsShape 跟 MonoBehaviour 的 BoxCollider 相互碰撞折腾了两天没成功。结论很干脆选一套一路走到底。1.2 固定时间步长物理世界的“心跳”物理引擎和渲染最大的区别在于渲染可以“随缘”——这一帧 16ms下一帧 25ms人眼勉强能忍但物理不行。如果物理也随帧率变化那同一个跳跃动作在 60 帧设备上跳 3 米在 30 帧设备上就跳 1.5 米游戏直接崩坏。所以 PhysX 采用的是固定时间步长Fixed Timestep驱动默认是 0.02 秒也就是 50Hz。这个 0.02 是怎么来的简单算一下50Hz 意味着高速物体每秒只被采样 50 次。一个初速 20 米/秒的子弹每次采样间隔移动 0.4 米。如果目标是一堵 0.2 米厚的墙那就一定会穿过去——这就是“隧穿”的数学根源。这也是为什么增加采样率把 fixedDeltaTime 降到 0.01能缓解穿透因为每步位移缩短到 0.2 米。但代价是物理计算量翻倍。Maximum Allowed Timestep这个参数很多人没注意过默认 0.3333 秒。它的作用是给物理“补帧”设上限。假如某一帧因为加载资源卡了 1 秒Unity 不会老老实实跑 50 次物理更新那只会让卡顿雪上加霜而是最多补 0.3333 秒的量剩下的时间直接放弃。所以你在低端机上偶尔会看到物理对象“瞬移”或者突然穿透往往就是这个上限触发了。我的经验是绝大多数项目保持 0.02 不动只有两类情况需要调。第一类是高速小物体密集的场景比如弹幕、球类竞技可以降到 0.01第二类是纯展示型、交互很轻的数字孪生大屏可以放宽到 0.0333省一点 CPU。改这个值之后一定要回头检查所有跟物理速度、力、跳跃高度相关的数值——因为它们都是以秒为单位积分的步长一变表现就会漂。注意Time.fixedDeltaTime和Time.deltaTime不能混用。所有跟物理相关的计算施力、速度积分都应该放在FixedUpdate里并用Time.fixedDeltaTime而摄像机跟随、UI 动画这类应该放在Update或LateUpdate。1.3 物理更新与渲染更新为什么不同步理解了固定步长就能明白为什么物理对象看起来会抖。典型场景是这样物理每 0.02 秒更新一次位置而渲染可能每 0.011 秒出一帧。当渲染帧落在两次物理更新之间时它拿到的还是上一次物理算出来的旧位置于是屏幕上就是一顿一顿的。解决方案是开启 Rigidbody 的Interpolate。它的原理是让引擎在两次物理结果之间做插值渲染时用“上上次位置”和“上次位置”按时间比例算出一个平滑的中间值。开了之后视觉上会平滑很多但会引入大约一个物理步长的延迟——50Hz 下就是 20ms。对于手感敏感的操作比如平台跳跃这 20ms 是可感知的所以有些团队选择关闭插值、提高物理频率来换取更即时的响应。Extrapolate是另一种插值模式它不做插值而做外推延迟更低但偶尔会出现“预测过头”导致的回弹一般用在高响应要求的运动物体上。另外一个容易忽略的点是Auto Sync Transforms。这个选项默认是关闭的老版本是开启的关闭状态下你直接修改 Transform 不会立刻同步到物理系统需要调用Physics.SyncTransforms()。很多人在关闭它之后发现“我明明改了位置射线却打不中”原因就在这里。判断标准很简单如果你的逻辑依赖“改完 Transform 马上做物理查询”要么开这个开关有性能开销要么手动同步。2. 碰撞体与刚体的组合拳怎么搭出稳定的物理对象2.1 Collider 选型盒、球、胶囊、网格各有各的脾气选碰撞体是物理搭建里最容易埋坑的一步。PhysX 对不同的碰撞体形状有完全不同的处理路径图元类Box、Sphere、Capsule是解析式求交速度极快网格类MeshCollider是三角面求交慢且有限制地形TerrainCollider是高度场专门优化过。我的选型顺序基本是能用图元就用图元实在不行才上凸包最后才考虑非凸网格。角色用 Capsule 是行业惯例不是因为好看而是因为胶囊不会卡在台阶接缝上、不会在旋转时抖动、还自带圆形底部能自然滑过小障碍。方块类道具用 Box 最稳堆叠起来也比 Mesh 稳定得多。球体用于球类、滚动物体唯一注意是球体和地面的接触点极小静止时容易微微滚动可以用Rigidbody.Sleep()或者调高角阻尼压制。MeshCollider 的门道就多了。默认勾选Convex后PhysX 会为网格生成一个凸包近似这个凸包可以用来跟其他凸包碰撞但形状可能会比你想象的“胖”一点——一个 L 形模型勾上 Convex 之后凹进去的部分会被填满导致视觉上没碰到却撞上了。而取消 Convex 的非凸网格碰撞体只能作为静态碰撞体使用挂了 Rigidbody 也不会参与动力学计算这是硬性限制。提示MeshCollider 的Cooking Options里有Enable Mesh Cleaning和Weld Colocated Vertices这类选项打开后能显著减少因为模型接缝导致的“卡住”现象。模型导出前在建模软件里做一次焊接更彻底。热词里提到的“renderer 的包围盒”在这里就有用了。Renderer.bounds和Collider.bounds是两套东西前者是渲染网格的轴对齐包围盒后者是物理形状的轴对齐包围盒。做物理粗筛或者自制剔除逻辑时用Renderer.bounds做一次快速判断能省下不少射线开销但千万别拿它当碰撞判断依据因为包围盒只保证“包含”不保证“精确”。2.2 Rigidbody 关键参数逐个拆解Rigidbody 上那几个参数说明书只有一句话但每个都值得展开讲。Mass质量。单位是千克但更多的是相对意义。PhysX 在求解冲量时看的是质量比两个质量 1 和 1000 的物体碰撞轻的会被弹飞得很离谱重的几乎不动这就是质量比失衡。经验法则是场景内参与碰撞的物体质量比尽量控制在 10:1 以内超出的部分靠约束或手动处理。我做过一个起重机吊臂的演示最初吊臂质量 5000、货物质量 5结果货物在接触瞬间像被弹弓打出去一样飞了。后来把吊臂降到 200、货物调到 20物理立刻正常了。Drag和Angular Drag是阻尼注意它跟摩擦力不是一回事。阻尼更像是空气阻力跟速度成正比速度越快减得越快摩擦力是接触面之间的需要两个物体接触才生效。很多新手想“让物体滑一会儿就停”结果去调 Drag其实应该调物理材质的摩擦系数。Drag 合理的用途是抑制高频抖动比如给静止的箱子一点角阻尼能防止它在斜坡上无限微滚。Interpolate前面讲过主要解决视觉抖动。Collision Detection是穿透问题的关键。Discrete是默认值性能最好但高速物体容易穿Continuous用于自身高速的物体Continuous Dynamic用于自身高速且要撞其他高速物体Continuous Speculative是最新加入的模式用预测式检测性能比前两者好对静态物体效果尤佳我现在的项目基本默认给它。Constraints冻结轴做 2.5D 游戏或者限制旋转时非常实用。冻结Freeze Rotation X/Z是让 3D 物体只在水平面转动的标准做法比用代码每帧清零欧拉角稳定得多。Sleep Threshold和自动休眠机制值得单独提。PhysX 会把长时间速度低于阈值的刚体标记为“睡眠”跳过它们的求解。这本是优化但会引发两个问题一是物体被“隔空”影响时不醒比如你移动它脚下的平台二是你手动改 Transform 后它仍然在睡。解决办法是对刚体调用WakeUp()或者改速度让它自然唤醒。2.3 物理材质摩擦和弹性的博弈PhysicMaterial新版叫 Physics Material里有两个组合维度摩擦力和弹性。摩擦力分静摩擦和动摩擦弹性只有一个Bounciness值。还有一个非常关键但容易被忽略的概念是Friction Combine和Bounce Combine。当两个物体接触时它们的摩擦系数怎么合成模式有 Average平均、Minimum取小、Maximum取大、Multiply相乘。默认是 Average。问题在于默认值带来的“反直觉”现象如果你给地面材质设了摩擦 1给箱子材质设摩擦 0.6平均下来是 0.8箱子其实挺难滑动的。想做出冰面效果要么两边都调到接近 0要么把地面的 Combine 设成 Minimum这样只要地面是 0无论箱子多大摩擦都会被拉低。弹性也是同理而且弹性叠加很容易失控。两个 Bounciness 都是 1 的物体会无限弹跳能量不衰减。实际做球类运动时我的做法是球体 Bounciness 设 0.6、地面设 0.3Combine 用 Maximum 或者 Multiply这样既能保证手感又不会太夸张。注意物理材质是按“两个接触面”计算的所以同一个物体跟不同表面对碰手感会不一样这是正常的。如果你希望某物体在任何表面手感一致需要给所有交互对象都配置对应材质。Unity 6 之后摩擦类型引入了Minimum和Patch模式Patch模式允许更大的静摩擦支持做斜坡上静止的箱子会更稳。这个新特性在旧版本里没有如果你的项目需要“坡上停住不滑”的效果升级引擎可能是最省事的方案。3. 触发、射线与查询物理引擎不只能“撞”3.1 Trigger 和碰撞事件的本质区别Trigger 就是勾了Is Trigger的碰撞体它不再产生物理推力只发事件。听起来简单但有几个硬性规则必须记住。第一Trigger 事件需要至少一方有 Rigidbody。两个静态碰撞体即使重叠也不会触发任何回调——这是最常见的新手坑很多人问“为什么我的触发器没反应”答案基本都是两边都没刚体。第二静态触发器没有刚体的 Trigger在被带刚体的物体进入时才会触发反之如果是一个带刚体的 Trigger 去碰静态碰撞体同样能触发因为刚体在动。OnTriggerEnter、OnTriggerStay、OnTriggerExit三个回调中Stay是每次物理帧都调用的如果有 50 个物体待在里面就是每帧 50 次调用性能相当可观。我现在的习惯是把需要持续检测的逻辑挪到Update里用一个 List 自己维护只在 Enter 和 Exit 时增删列表能省下一大笔开销。碰撞事件则有更多细节。OnCollisionEnter的参数里带着ContactPoint数组里面包含接触点、法线、冲量大小。冲量可以用来做撞击音效的强弱分级或者判断“这一下算不算重击”。法线可以用来做撞击火花的方向。这些数据白给但很多人只用了collision.gameObject。另外要注意Unity 6 中引入了ICollisionEventsJob一类的 DOTS 方案Monobehaviour 侧也有Physics.ContactModifyEvent可以在求解前修改接触点的摩擦和弹性。这个 API 做“局部冰面”或者“临时打滑”特别方便不用去改材质资源。3.2 射线、球形投射与重叠查询怎么选射线检测是物理系统里最常用的查询工具但很多人只会用Physics.Raycast其实一整套 API 各有适用场景。查询方法形状典型用途相对开销Raycast无限细线瞄准、地面检测低SphereCast球体扫掠角色移动预判、子弹有体积中CapsuleCast胶囊扫掠角色位移检测中高BoxCast盒子扫掠箱子推动预判、载具中OverlapSphere球体范围爆炸影响范围、范围拾取中CheckSphere球体占位出生点是否被占、地面贴合低Linecast两点连线视线检测低我用得最多的是SphereCast而不是Raycast。原因是射线没有宽度正好擦边的物体检测不到而角色、子弹都有体积用射线很容易出现“明明贴着墙但检测不到”的问题。做法是把射线起点稍微抬高、用一个小半径的 SphereCast命中率立刻不一样。范围查询比如OverlapSphere会分配数组频繁调用会产生 GC。正确做法是用OverlapSphereNonAlloc并传入一个预分配的缓冲区。这类NonAlloc版本在新版 API 里标注为过时取而代之的是Physics.OverlapSphere配合PhysicsScene或RaycastCommand批量处理。如果查询量非常大比如上千个单位的邻近搜索RaycastCommand配合 Job System 能把开销压到极低。还有一个隐藏细节所有物理查询都受Physics.queriesHitTriggers影响默认是 true也就是说射线会打到触发器上。做地面检测时如果不小心打到自己角色的触发器就会得到莫名其妙的结果。项目的物理设置里关掉这个开关或者在Raycast里传入QueryTriggerInteraction.Ignore都能解决。3.3 层碰撞矩阵别让物理做无用功Layer Collision Matrix在 Project Settings Physics 里很多人一年都不会打开它一次但它是性价比最高的物理优化手段。默认情况下所有层两两之间都会做碰撞检测。一个场景里有角色、敌人、子弹、地形、道具、特效碰撞盒如果不做裁剪每帧的宽相检测要处理的对象对数量是 O(n²) 级别的。把“子弹层不跟子弹层碰撞”、“特效层不跟道具层碰撞”这些关系在矩阵里勾掉之后PhysX 在宽相阶段就直接跳过这些配对开销立刻下降。我做过一个实测一个约 300 个动态刚体的场景默认矩阵下物理耗时约 4.2ms把矩阵里七八组明显无意义的配对关掉后降到 2.6ms 左右。这个优化不需要改一行代码纯粹是配置层面的事。同样的思路还可以用在Layer Overrides上这个组件能覆盖单个刚体的碰撞层设置做“某物体临时穿过某层”的需求时比在代码里改全局层方便得多。比如角色释放穿墙技能时给角色加一个 Layer Override 让它忽略墙体层技能结束后移除即可。提示层数的扩展开销也要注意。PhysX 对层数有限制32 个每多一个层意味着矩阵维度增加。规划层名时提前想清楚分类维度别等做到一半发现层用完了。4. 关节与角色控制从门轴到人物移动4.1 Joint 家族的适用边界Unity 提供了一组关节组件做机械结构、门、链条、绳索、布娃娃时会用到。它们的本质是约束求解器比手动写代码限制位置性能好得多但也更容易失控。FixedJoint是最简单的刚性连接两个物体像被焊住一样。它有个Break Force和Break Torque超过阈值就断开做“打碎”效果很方便。要注意固定关节不自带父子关系两个物体仍然是独立刚体只是约束在一起所以质量比失衡时会抖。HingeJoint是铰链做门、摇臂、跷跷板标准之选。关键参数是Limits角度限制和Motor驱动电机。电机做自动门或者转盘很好用但要注意电机是施加力矩而不是直接设速度所以需要配合阻尼和最大力参数调否则会出现高频振荡。我调自动门的经验是Motor Force 不要超过物体质量的两倍Target Velocity 控制在 90 度/秒以内配合一定阻尼门开合就会很自然。SpringJoint是弹簧做蹦床、橡皮筋、悬挂绳都会用到。Spring是刚度Damper是阻尼这两个参数的平衡点很难找。一个偷懒的办法是用引擎自带的SpringJoint预设值起步然后按“先调 Spring 到差不多再调 Damper 消抖”的顺序微调。ConfigurableJoint是最全能的几乎所有关节都是它的特例它的参数多到令人头疼线性限制、角限制、驱动、连接锚点、坐标轴方向但做机器人关节、精确机械动画时非它不可。我的建议是除非确实需要复杂约束否则优先用现成的简化关节。CharacterJoint主要用于布娃娃做死亡倒地效果时用。它本质是带限制的球关节配合碰撞体稍小的骨骼排布能做出还不错的人体软倒。但布娃娃系统最容易出的问题是关节爆炸原因通常是骨骼碰撞体互相穿模挤压解法是把骨骼碰撞体缩小一点、关节角度限制收窄。4.2 CharacterController 和 Rigidbody 移动该怎么选这是被问得最多的问题之一。答案不是“哪个更好”而是“哪个更适合你的项目类型”。CharacterController是物理引擎之上的一个封装它内部用胶囊做扫掠检测提供Move和SimpleMove接口自带 slopeLimit、stepOffset、skinWidth 等参数。优点是移动稳定、不会因为撞到小凹凸而弹起、爬台阶和斜坡处理很省心。缺点也很明显它不响应外力也就是说你没法直接给它加力、没法被爆炸推开、没法自然地被斜坡滑动影响一切都要手写。同时它跟 Rigidbody 的碰撞是单向的——它能推动别人但别人的力对它几乎无效。Rigidbody 移动则完全交给物理用MovePosition或者AddForce驱动。优点是所有物理交互都自然被撞击、被推、被压在下面都能自动处理。缺点是容易出现各种物理异常低速时卡在地形接缝、斜坡上往下滑、跳跃落地时弹跳。这些都要额外调参处理。我的实际选择标准是这样的如果是单人剧情向、平台跳跃、第一人称射击用 CharacterController手感可控性更好如果是物理沙盒、多人对战、需要大量物体互动、载具用 Rigidbody。混用是可行的但必须清楚谁是主导。比如角色用 CharacterController 走位但身体上挂一个 Kinematic 的 Rigidbody 用来接收碰撞事件。一个具体的 Rigidbody 角色移动写法可以参考void FixedUpdate() { Vector3 move Vector3.ProjectOnPlane(inputDir, Vector3.up).normalized; Vector3 targetVel move * moveSpeed; targetVel.y rb.velocity.y; rb.velocity Vector3.Lerp(rb.velocity, targetVel, 1f - Mathf.Exp(-accel * Time.fixedDeltaTime)); if (jumpRequested isGrounded) { rb.AddForce(Vector3.up * jumpImpulse, ForceMode.Impulse); } }这里用指数插值而不是直接赋值是为了让加速和减速有过渡避免速度突变导致的抖动。Vector3.ProjectOnPlane是为了保证在斜面上移动方向始终水平不然角色会往坡里钻。一个常见的坑是用 Rigidbody 做角色后如果不设置Freeze Rotation角色会在碰撞时翻倒。加上 X 和 Z 轴冻结后再用代码控制 Y 轴朝向即可。5. 性能与稳定性调优实战5.1 物理掉帧的排查路径物理掉帧跟渲染掉帧的表现不同渲染掉帧是帧率整体下降物理掉帧常常是帧率正常但物体行为异常比如位置滞后、抖动、堆叠爆炸。排查我一般按这个顺序走。第一步看 Profiler 的 Physics 模块。它会显示Physics.Processing、Physics.Simulate、Physics.ProcessReports等条目。如果Physics.Simulate占大头说明刚体和碰撞体太多如果Physics.ProcessReports高说明事件回调太频繁多半是OnCollisionStay或者OnTriggerStay用太多。第二步数刚体数量。经验值移动端同屏动态刚体控制在 50 个以内比较稳PC 端可以到两三百。静态碰撞体数量本身不太影响求解但会影响宽相数据结构的内存和构建时间所以超大场景比如几平方公里的数字孪生地图建议做分区激活。第三步检查defaultSolverIterations。这是求解器迭代次数默认 6。堆叠场景下如果箱子叠得高加迭代次数能明显改善稳定性但开销是线性的。我的做法是全局保持 6只在必要场景用Rigidbody.solverIterations给关键物体单独提高。第四步看物理材质的数量。每个不同材质的接触对都需要单独求解过多的物理材质会导致缓存失效。如果一个场景有 20 种不同材质的箱子实际碰撞组合多达上百种性能会明显下降。合并材质能省不少。5.2 抖动、穿透、滑落这三类问题的对症下药抖动最常见的原因是物体没有休眠或者插值没开。检查顺序物体静止时是否进入 Sleep在 Physics Debugger 里能看到刚体状态是 Awake 还是 Asleep如果没睡检查它的速度是否一直在阈值以上可能是参数振动导致的。其次是碰撞体尺寸和视觉模型不匹配比如视觉模型有 1.2 米宽但碰撞体只有 1 米会导致视觉上重叠而物理上分离看起来像抖。第三是质量比过大重的物体压着轻的物体时求解器需要更多迭代才能收敛没收敛就会抖。穿透的根源基本就是速度和步长的矛盾。解决方案按代价从低到高排列把Collision Detection改成Continuous Speculative把墙体加厚对关键物体降低fixedDeltaTime对高速物体用射线预判自己做处理比如子弹飞行前先做一次SphereCast判断路径上有没有障碍。我在做抛掷类玩法时用的方法是混合方案物理抛掷只负责视觉效果命中判定用射线。这样既不会穿透也不会有物理延迟投掷手感和判定精度都能保证。斜坡滑落这个问题的本质是静摩擦不足或者来自重力的切向分量没被抵消。常见处理方式有三种把物理材质的静态摩擦调高到 0.8 以上用Friction Combine Maximum保证两边取最大值或者干脆在检测到角色站在斜坡上且没有输入时给刚体加一个反向的速度抵消。第三种方式最可靠但不是纯物理做产品时用起来最省心。注意解决物理问题的优先级应该是“先改设计再改参数最后改代码”。很多穿透和抖动问题改一下关卡布局比如把墙加厚、把坡变缓就能消失比调参省事十倍。6. 常见问题速查表与实操心法6.1 一张表覆盖八成常见异常现象最可能的原因优先尝试的解法物体静止时轻微抖动未休眠、质量比大、插值未开开 Interpolate、调质量比、检查休眠高速物体穿墙单步位移大于墙厚换 Continuous Speculative、加厚墙触发器不响应双方都无刚体给其中一方加 Rigidbody射线打不到目标Trigger 被命中、图层被忽略设置 QueryTriggerInteraction斜坡上停不住静摩擦不足提高摩擦、用 Maximum 合成堆叠结构抖动松散求解器迭代不够提高 solverIterations、减少层数物体被推时像弹珠质量差异过大压缩质量比到 10:1 以内场景卡顿刚体过多、事件过多减少动态刚体、去掉 Stay 事件修改 Transform 后检测失效Auto Sync 关闭调 Physics.SyncTransforms关节物体乱飞约束冲突、Break 阈值低收窄角度限制、提高 Break Force这张表里我踩过最多次的是“触发器不响应”。有次做拾取系统道具是静态触发器、玩家是 CharacterController没有 Rigidbody死活触发不了。后来给道具加了 Kinematic 的 Rigidbody 就好了。这个坑值得每个新手提前知道。6.2 几个我实践中总结的零碎心得第一个心得是关于 Kinematic 刚体的用法。很多人以为勾上Is Kinematic就等于“不参与物理”其实它仍然参与碰撞检测和触发只是不受力和重力影响速度要靠你手动设置或者MovePosition。做移动平台、电梯、传送带上物品时用一个 Kinematic 刚体的平台是最好的方案因为平台移动时上面的物品会被带着走前提是物理材质摩擦足够。第二个心得是关于调试工具的。Window Analysis Physics Debugger是个宝藏它能把场景里所有碰撞体、接触点、休眠状态可视化。物体为什么会卡住、为什么浮空、为什么下沉打开这个窗口立刻一目了然。我现在的习惯是每搭一个新物理场景就先开 Physics Debugger 看一遍能提前发现百分之八十的布局问题。第三个心得跟数字孪生项目有关。数字孪生场景通常模型巨大、物体成千上万全用真实物理是不可能的。我的经验是做分层视觉层用渲染、展示层用简化物理只对关键设备做刚体、交互层用假物理射线加手写运动。真正参与 PhysX 求解的物体可能只占整体的百分之五。全栈数字孪生项目里那个gameassembly.dll的体积和加载时间都能看出来如果把所有模型都挂上刚体加载时间会直接翻倍。第四个心得是关于程序化地形的物理。用Mathf.PerlinNoise生成高度图做地形是很常见的玩法但直接把生成的网格塞进 MeshCollider 会非常慢而且非凸网格不能跟动态刚体碰撞。正确做法是用TerrainCollider配合TerrainData的高度图或者把地形按区域拆成多个凸包。我做过一个用 Perlin Noise 生成随机岛屿的小项目用 TerrainCollider 方案的物理耗时只有 MeshCollider 方案的六分之一。第五个心得是关于摄像机跟随的物理对象。摄像机跟物理对象时一定放在LateUpdate里做并且跟的位置最好用插值后的视觉位置而不是物理位置。如果直接用物理位置低帧率下摄像机会跟着抖因为它在读一个 50Hz 更新的值。Rigidbody.position是物理位置transform.position在开启插值后是视觉位置两者会差一个插值量但视觉上更顺。最后提一个思路上的转变。物理引擎是个“锦上添花”的工具不是“地基”。但凡涉及手感、精度、确定性、网络同步的需求物理都应该退居二线让代码逻辑接管主导权。我见过太多团队一开始什么都交给物理做到后期发现手感调不动、同步对不齐、性能下不来又回头重写。早点想清楚“哪些是真的需要物理哪些只是看起来像需要物理”能省掉大量返工。