1. 碰撞与触发:物理交互的两套底层逻辑
做Unity项目的人,绕不开物理交互这块。不管是做角色控制、子弹命中判定、道具拾取,还是做关卡里的机关触发,Collider和Trigger这两套机制几乎贯穿了所有交互场景。我见过不少刚入行的朋友,角色撞墙能停住,但走到传送门前面死活触发不了;也见过子弹打出去穿透敌人,明明加了碰撞体却毫无反应。这些问题九成以上都出在对Collider和Trigger的理解偏差上。
先把概念摆清楚。Collider(碰撞器)是挂在GameObject上的物理形状组件,它定义了物体的物理边界,让物理引擎知道"这个东西占多大空间、长什么形状"。而Trigger(触发器)本质上不是独立组件,它是Collider上的一个开关属性——勾选Is Trigger之后,这个碰撞体就不再产生物理阻挡,转而只负责检测"有没有东西进来、出去、停留"。一句话概括:Collider负责"挡",Trigger负责"感知"。
这个区别听起来简单,但实际项目里出问题的往往不是概念本身,而是配套条件没配齐。比如两个物体要发生碰撞或触发,至少一方必须挂Rigidbody(刚体),这是物理引擎参与计算的前提。很多人只给碰撞体不给刚体,然后纳闷为什么OnTriggerEnter不执行——因为物理系统压根没把这两个物体纳入模拟。再比如Layer碰撞矩阵、Collider的尺寸精度、刚体的isKinematic设置,每一个都会直接影响最终行为。
这篇内容适合谁看?如果你是刚接触Unity物理系统、被各种"撞不到""触发不了"折磨过的开发者,或者你已经有项目经验但想系统梳理一遍碰撞检测的完整链路,那接下来的内容应该能帮你把这块知识补扎实。我会从设计思路、核心参数、实操流程到排查技巧,把Collider和Trigger这套机制拆开讲透,每个环节都配上能直接抄的配置和踩坑经验。
2. 整体设计思路:为什么物理交互要分两套机制
2.1 碰撞与触发的职责边界
Unity把物理交互拆成"碰撞"和"触发"两条路径,背后是有明确工程考量的。碰撞(Collision)走的是物理求解流程:物理引擎检测到两个碰撞体相交后,会计算穿透深度、法线方向、相对速度,然后施加冲量把物体推开,同时回调OnCollisionEnter/Stay/Exit。这套流程是有计算成本的,涉及连续碰撞检测、摩擦力和弹性系数的求解。
触发(Trigger)走的是重叠检测流程:物理引擎只判断两个碰撞体的包围体是否相交,相交就回调OnTriggerEnter/Stay/Exit,但不做任何物理响应。计算量明显更小,适合"只需要知道发生了接触、不需要物理反应"的场景。
这个拆分带来的直接好处是性能可控。你想想,一个场景里可能有几百个道具、几十发子弹、一堆检测区域,如果全部走物理求解,帧率直接崩掉。而把这些"感知型"交互全部改成Trigger,物理引擎只需要做重叠判断,开销小得多。我在一个塔防项目里做过对比:200个敌人和防御塔的接触检测,用Collision方案在低端机上掉到40帧,改成Trigger后稳定60帧。这不是玄学,是物理求解和重叠检测的本质差异。
2.2 什么时候用Collider,什么时候用Trigger
判断标准其实很朴素:需要物理阻挡或力的反馈,用Collider;只需要知道"碰到了"这个事件,用Trigger。
需要Collider的典型场景:角色撞墙停住、箱子被推动、球体弹跳、物体堆叠、载具碰撞。这些都需要物理引擎介入,产生位置修正或力的作用。
需要Trigger的典型场景:进入区域触发剧情、拾取道具、子弹命中判定、检测玩家是否在攻击范围内、关卡边界检测、UI交互区域。这些场景里,物体之间不应该有物理阻挡——你总不希望玩家走过去捡金币的时候被金币弹开吧。
有个容易混淆的点:子弹命中判定。很多教程会让子弹用Collider加Rigidbody,靠OnCollisionEnter检测命中。这在慢速子弹上没问题,但高速子弹会出现"穿透"——因为物理引擎是按帧离散检测的,子弹一帧移动距离超过目标厚度,就直接穿过去了。正确做法是子弹用Trigger加Rigidbody,开启Continuous Dynamic连续碰撞检测,或者干脆用射线检测(Raycast)做命中判定。这个取舍后面会详细讲。
2.3 物理引擎的参与条件:Rigidbody是入场券
这是新手最容易忽略的一点。Unity的物理系统(不管是PhysX还是Box2D)只处理至少一方带Rigidbody的碰撞体对。两个都只有Collider没有Rigidbody的物体,物理引擎根本不会计算它们之间的交互,回调也不会触发。
具体规则是这样的:
| 物体A | 物体B | 碰撞回调 | 触发回调 |
|---|---|---|---|
| Rigidbody + Collider | Collider(静态) | 触发 | 触发 |
| Rigidbody + Collider | Rigidbody + Collider | 触发 | 触发 |
| Collider(静态) | Collider(静态) | 不触发 | 不触发 |
| Rigidbody + Trigger | Collider | 不触发 | 触发 |
| Rigidbody + Trigger | Rigidbody + Trigger | 不触发 | 触发 |
注意最后两行:Trigger之间的交互只走触发回调,永远不会有碰撞回调。哪怕两个都勾了Is Trigger,它们之间只会触发OnTrigger系列事件。这个表格建议截图存手机里,排查问题时对着看,能省掉大量试错时间。
还有一个隐藏条件:Rigidbody的isKinematic。如果刚体勾了isKinematic,它不受物理力影响,但依然能参与触发检测。这个特性在做"跟随移动的检测区域"时特别有用——比如一个挂在角色身上的攻击范围Trigger,你希望它跟着角色走但不被物理推来推去,就给它挂个isKinematic的Rigidbody。
3. 核心参数与组件配置详解
3.1 Collider类型选型:形状决定性能与精度
Unity提供了多种Collider,选型的核心原则是:用最简单的形状覆盖需求,精度不够再升级。
- BoxCollider:盒形,计算最快,适合箱子、墙壁、地板、角色胶囊的替代。绝大多数静态场景物体用它就够了。
- SphereCollider:球形,计算快且各向同性,适合球体、检测范围、爆炸波及范围。
- CapsuleCollider:胶囊形,角色控制器的标配,因为它在斜坡和台阶上的滑动表现比Box平滑。
- MeshCollider:网格形,精度最高但计算最贵,而且默认不支持非凸(non-convex)网格参与物理碰撞。静态地形可以用,动态物体慎用。
- TerrainCollider:地形专用,配合Terrain组件使用。
MeshCollider有个大坑:Convex(凸体)选项。勾了Convex,MeshCollider才能参与动态碰撞和触发;不勾的话只能作为静态碰撞体,而且不能和另一个MeshCollider发生碰撞。我见过有人给角色挂了个不勾Convex的MeshCollider,结果角色直接穿地——因为物理引擎把它当静态物体处理了。另外,Convex MeshCollider最多支持255个三角面,超过会被简化,精度反而下降。所以角色和动态物体,老老实实用Capsule或Box,别上Mesh。
3.2 Is Trigger开关:一个勾选框改变整个行为
Is Trigger这个勾选框的作用,是把Collider从"物理实体"切换成"感知区域"。勾上之后:
- 不再产生物理阻挡,其他物体可以直接穿过
- 不再触发OnCollision系列回调
- 改为触发OnTriggerEnter/Stay/Exit
- 依然需要至少一方有Rigidbody才能检测
这里有个细节很多人不知道:Trigger的检测精度依赖Collider的形状和尺寸。一个BoxTrigger如果尺寸设得比视觉模型小一圈,玩家走到边缘看着已经进去了,但实际没触发。反过来设大了,还没碰到就触发了。我的习惯是给Trigger加一个半透明的Gizmo可视化(在Scene视图里能看到绿色线框),调尺寸的时候对着视觉边界微调,确保触发范围和玩家感知一致。
3.3 Rigidbody的关键参数:质量、阻力与检测模式
Rigidbody上几个参数直接影响碰撞和触发行为,逐个说:
Mass(质量):影响碰撞时的动量交换。两个物体碰撞,质量大的推质量小的。但注意,Trigger检测跟质量无关,只跟重叠有关。所以做触发检测时不用纠结质量设置。
Drag(阻力)和Angular Drag(角阻力):影响物体运动衰减。做子弹时如果Drag设太大,子弹飞一半就停了,看起来像"射程不够"。一般子弹Drag设0,靠脚本控制生命周期。
Use Gravity:是否受重力。做漂浮的检测区域时记得关掉,不然Trigger区域会往下掉。
Is Kinematic:勾上后不受物理力,但能参与触发检测。适合"跟着动画或脚本移动的检测体"。注意,isKinematic的刚体之间不会产生碰撞回调,但可以和普通刚体产生碰撞回调(如果对方不是kinematic)。
Collision Detection(碰撞检测模式):
| 模式 | 适用场景 | 性能开销 |
|---|---|---|
| Discrete(离散) | 普通慢速物体 | 最低 |
| Continuous(连续) | 快速移动物体 | 中等 |
| Continuous Dynamic(连续动态) | 高速子弹、快速角色 | 最高 |
| Continuous Speculative(连续推测) | 高速但可接受轻微误差 | 中等 |
高速子弹必须用Continuous Dynamic,否则一帧位移超过目标厚度就穿透了。我实测过:一颗速度50m/s的子弹,60帧下每帧移动约0.83米,如果目标碰撞体厚度只有0.2米,Discrete模式下必然穿透。改成Continuous Dynamic后命中率从六成提升到接近百分之百。
Interpolate(插值):让物理更新和渲染帧率对齐,视觉上更平滑。做角色移动时建议开Interpolate,不然高速移动会有抖动感。
3.4 Layer碰撞矩阵:被忽视的过滤利器
Unity的Layer碰撞矩阵(Edit > Project Settings > Physics > Layer Collision Matrix)决定了哪些层的物体之间会发生碰撞和触发。这个功能用好了能大幅减少无效检测。
举个实际例子:玩家子弹层和玩家层之间的碰撞应该关掉,不然子弹一出膛就撞到自己。敌人层和敌人层之间的碰撞如果不需要互相推挤,也可以关掉来省性能。UI检测层和场景物体层之间通常也要隔离。
配置方法:给不同类别的物体分配Layer,然后在矩阵里取消勾选不需要交互的组合。注意,Trigger检测同样受Layer矩阵控制——两个层如果在矩阵里没勾选,它们的Trigger也不会触发。这个点很多人不知道,排查"触发不生效"时经常是这里的问题。
4. 实操流程:从零搭一套可用的碰撞触发系统
4.1 场景搭建与基础配置
假设我们要做一个最小可用的演示:一个玩家角色(CapsuleCollider + Rigidbody),一面墙(BoxCollider),一个传送门区域(BoxCollider + Is Trigger),一个可拾取的金币(SphereCollider + Is Trigger)。
第一步,创建玩家。新建Capsule,挂CapsuleCollider,再挂Rigidbody。Rigidbody设置:Mass 1,Drag 0,Angular Drag 0.05,Use Gravity 勾上,Is Kinematic 不勾,Interpolate 选Interpolate,Collision Detection 选Continuous。这样角色移动平滑,撞墙有物理反馈。
第二步,创建墙。新建Cube,缩放成墙的形状,挂BoxCollider(Cube默认自带)。墙不需要Rigidbody,作为静态碰撞体即可。
第三步,创建传送门。新建Cube,调整到门的大小,挂BoxCollider,勾选Is Trigger。传送门不需要Rigidbody,因为玩家有Rigidbody,满足"至少一方有刚体"的条件。
第四步,创建金币。新建Sphere,缩小,挂SphereCollider,勾选Is Trigger,Radius设为0.3左右(比视觉模型略大一点,方便拾取)。金币同样不需要Rigidbody。
这里有个关键点:金币的Trigger半径要比视觉模型大一圈。玩家操作有误差,视觉上"擦边"的时候应该能捡到,所以触发范围适当放宽是合理的体验优化。我一般设成视觉半径的1.2到1.5倍。
4.2 脚本编写:三个回调的完整实现
给玩家挂一个脚本,处理碰撞和触发:
using UnityEngine; public class PlayerInteraction : MonoBehaviour { private void OnCollisionEnter(Collision collision) { // 撞到墙等实体,可以播放撞击音效 Debug.Log($"撞到了: {collision.gameObject.name},接触点数量: {collision.contactCount}"); } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Coin")) { Debug.Log("拾取金币"); Destroy(other.gameObject); } else if (other.CompareTag("Portal")) { Debug.Log("进入传送门"); // 传送逻辑 } } private void OnTriggerStay(Collider other) { if (other.CompareTag("DamageZone")) { // 持续伤害区域,每帧扣血 Debug.Log("受到持续伤害"); } } private void OnTriggerExit(Collider other) { if (other.CompareTag("Portal")) { Debug.Log("离开传送门"); } } }几个要点说明:
OnCollisionEnter的参数是Collision,它包含接触点(contacts)、相对速度(relativeVelocity)、碰撞体信息(collider)。做撞击特效时用collision.contacts[0].point取接触点位置,用collision.relativeVelocity.magnitude判断撞击强度。
OnTriggerEnter的参数是Collider,只有对方碰撞体的信息,没有接触点。因为Trigger不做物理求解,自然没有接触点数据。需要接触点的话得自己算,或者改用Collision。
Tag判断是必须的。Trigger区域里可能进来各种东西,不判断Tag直接处理逻辑,很容易出bug。比如传送门区域里飞进来一颗子弹,不判断Tag就会把子弹也传送走。
4.3 高速子弹的命中判定方案
前面提到高速子弹用Collider会穿透,这里给一个完整方案。子弹用Trigger + Rigidbody + Continuous Dynamic:
using UnityEngine; public class Bullet : MonoBehaviour { public float speed = 50f; public float lifeTime = 2f; private void Start() { GetComponent<Rigidbody>().velocity = transform.forward * speed; Destroy(gameObject, lifeTime); } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Enemy")) { Debug.Log($"命中敌人: {other.name}"); // 伤害逻辑 Destroy(gameObject); } else if (other.CompareTag("Wall")) { // 打到墙,销毁 Destroy(gameObject); } } }子弹的Rigidbody配置:Mass 0.01,Use Gravity 关掉,Collision Detection 选Continuous Dynamic,Interpolate 选Interpolate。Trigger的Collider用SphereCollider,半径设小一点(0.1左右),减少误判。
如果子弹速度再快(比如100m/s以上),Continuous Dynamic也可能漏检。这时候改用射线检测更稳:
private void FixedUpdate() { float moveDistance = speed * Time.fixedDeltaTime; if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, moveDistance)) { if (hit.collider.CompareTag("Enemy")) { Debug.Log($"射线命中: {hit.collider.name}"); } Destroy(gameObject); } else { transform.position += transform.forward * moveDistance; } }射线检测的原理是"从当前位置向前发射一条长度为本帧位移的射线",只要射线碰到东西就算命中,不存在穿透问题。代价是射线检测不感知物理形状的厚度,只判断射线与碰撞体的交点,所以对薄片状目标反而更准。
4.4 触发区域的尺寸校准与可视化
Trigger的尺寸校准是个细致活。我的做法是:
- 在Scene视图里选中Trigger物体,Collider的绿色线框会显示出来
- 把视觉模型调成半透明(或者在Inspector里临时隐藏MeshRenderer)
- 对比绿色线框和视觉边界,用Collider的Center和Size参数微调
- 对于需要"提前感知"的区域(如敌人警戒范围),线框比视觉大20%到30%
- 对于需要"精确判定"的区域(如拾取),线框和视觉基本重合或略大
另外,给Trigger加一个调试用的Gizmo脚本,运行时也能看到范围:
using UnityEngine; public class TriggerGizmo : MonoBehaviour { private void OnDrawGizmos() { Gizmos.color = new Color(0, 1, 0, 0.3f); var box = GetComponent<BoxCollider>(); if (box != null) { Gizmos.matrix = transform.localToWorldMatrix; Gizmos.DrawCube(box.center, box.size); } } }这个脚本在Scene视图里画一个半透明绿色方块,运行时也能看到,调尺寸的时候非常直观。
5. 常见问题排查与避坑实录
5.1 触发不生效的排查清单
OnTriggerEnter不执行,按这个顺序排查,九成问题能定位:
| 排查项 | 检查方法 | 常见错误 |
|---|---|---|
| 是否有Rigidbody | 看两个物体是否至少一方有Rigidbody | 两边都只有Collider |
| Is Trigger是否勾选 | 看Collider组件 | 忘了勾,走的是Collision |
| Layer矩阵 | Project Settings > Physics | 两个层之间没勾选 |
| 脚本挂载位置 | 看脚本挂在哪个物体上 | 挂在没有Rigidbody的物体上 |
| Collider尺寸 | Scene视图看绿色线框 | 尺寸太小,实际没重叠 |
| 物体是否激活 | 看GameObject的active状态 | 父物体被禁用 |
| 刚体isKinematic | 看Rigidbody设置 | 两个都是kinematic,不触发 |
我遇到最多的两个坑:一是脚本挂错物体。OnTriggerEnter要挂在有Rigidbody的那个物体上,或者挂在Trigger物体上(如果Trigger物体有Rigidbody)。如果两边都没Rigidbody,脚本挂哪都没用。二是Layer矩阵。新建的Layer默认和所有层都勾选,但如果你手动改过矩阵,很容易漏掉某个组合。
5.2 碰撞穿透的三种成因与对策
穿透(tunneling)是高速物体常见问题,成因有三种:
成因一:Collision Detection模式不对。Discrete模式下,物理引擎只在每帧末尾检测一次位置,高速物体一帧跨过目标就漏了。对策:改成Continuous Dynamic。
成因二:目标碰撞体太薄。即使开了连续检测,如果目标厚度小于物理引擎的最小检测阈值(默认约0.01米),依然可能漏。对策:给薄物体加厚碰撞体,或者用射线检测。
成因三:Fixed Timestep太大。Project Settings > Time > Fixed Timestep默认0.02秒(50Hz),如果子弹速度是100m/s,每物理帧移动2米,很容易跨过薄目标。对策:把Fixed Timestep调到0.01或更小,代价是物理计算频率翻倍,性能开销增加。我的经验是0.0167(60Hz)是个平衡点,配合Continuous Dynamic基本够用。
5.3 OnTriggerStay的性能陷阱
OnTriggerStay是每物理帧都调用的,如果Trigger区域里有很多物体,或者Stay里做了复杂计算,性能会明显下降。我见过一个项目在OnTriggerStay里做射线检测和UI更新,帧率直接腰斩。
优化思路:能不用Stay就不用。持续伤害可以用计时器在Enter时启动、Exit时停止,而不是每帧在Stay里扣血。如果必须用Stay,把逻辑做轻量判断,重计算放到协程或定时器里。另外,Stay的调用频率跟Fixed Timestep绑定,调小Timestep会让Stay调用更频繁,这点要心里有数。
5.4 触发器与物理材质的配合
Trigger本身不参与物理求解,所以物理材质(Physic Material)对Trigger无效。但Collider的物理材质会影响碰撞行为:摩擦力(Dynamic Friction/Static Friction)决定物体在表面滑动的难易,弹性(Bounciness)决定反弹强度。
做冰面效果时,给地面Collider挂一个低摩擦的Physic Material;做弹跳球时,给球挂高Bounciness的材质。注意,物理材质只对Collider生效,Trigger上挂材质没有任何效果,别白费功夫。
5.5 触发器的嵌套与层级问题
一个常见需求是"Trigger里面再套Trigger",比如一个大的检测区域里包含多个小的交互点。这里要注意:子物体的Collider默认会合并到父物体的碰撞检测里(如果父物体有Rigidbody)。如果不想合并,给子物体单独挂Rigidbody并设isKinematic,或者调整层级结构。
另外,禁用父物体会导致子物体的Trigger全部失效。做UI面板或可开关的检测区域时,用SetActive(false)会连带禁用子物体上的所有Collider和脚本。如果只想临时关闭检测,应该禁用Collider组件而不是整个GameObject。
6. 进阶技巧:让碰撞触发系统更稳更快
6.1 用Physics.Overlap系列做主动检测
除了被动的回调,Unity还提供了主动查询API:Physics.OverlapSphere、Physics.OverlapBox、Physics.CheckSphere等。这些方法在调用时立即返回重叠的碰撞体列表,适合做"范围攻击""视野检测"这类需要即时结果的场景。
Collider[] hits = Physics.OverlapSphere(transform.position, 5f, LayerMask.GetMask("Enemy")); foreach (var hit in hits) { Debug.Log($"范围内敌人: {hit.name}"); }注意第三个参数LayerMask,只检测指定层,能大幅减少返回结果数量。Overlap系列的开销跟场景里碰撞体总数相关,调用频率高的话建议加LayerMask过滤,或者用NonAlloc版本避免GC。
6.2 触发器事件与事件系统的解耦
项目大了之后,直接在OnTriggerEnter里写业务逻辑会导致脚本臃肿、耦合严重。我的做法是用C#事件或UnityEvent把触发事件抛出去,业务逻辑在别处订阅:
public class TriggerZone : MonoBehaviour { public event System.Action<Collider> OnEnter; public event System.Action<Collider> OnExit; private void OnTriggerEnter(Collider other) => OnEnter?.Invoke(other); private void OnTriggerExit(Collider other) => OnExit?.Invoke(other); }这样Trigger区域只负责"感知",具体做什么由订阅方决定。同一个区域可以被多个系统复用,比如既触发剧情又触发音效,互不干扰。
6.3 碰撞层与查询层的分离配置
Unity的Layer矩阵控制的是"碰撞层"(哪些层之间发生物理交互),但射线检测和Overlap查询用的是"查询层"(通过LayerMask参数指定)。这两者是独立的:一个物体可以在碰撞矩阵里和某层不交互,但射线依然能检测到它。
利用这个特性可以做精细控制:比如敌人的碰撞体和玩家子弹不产生物理碰撞(避免子弹被弹开),但射线检测能命中敌人(用于瞄准辅助)。配置方法是把敌人放到Enemy层,子弹的Collider和Enemy层在矩阵里取消勾选,但射线检测的LayerMask包含Enemy层。
6.4 触发器在2D项目中的差异
如果做2D项目,用的是BoxCollider2D、CircleCollider2D等,回调是OnTriggerEnter2D,参数是Collider2D。2D物理(Box2D)和3D物理(PhysX)在触发检测上的逻辑基本一致,但有几个差异要注意:
- 2D的Rigidbody2D有Body Type(Dynamic/Kinematic/Static),Static类型不参与触发检测
- 2D的Layer矩阵在Physics2D设置里,和3D的分开配置
- 2D的Trigger检测精度受Collider2D的Edge Radius影响,圆角边缘会影响触发范围
做2D平台游戏时,角色的地面检测通常用一个小BoxCollider2D做Trigger,配合OnTriggerStay判断是否着地。这个方案比射线检测更宽容,角色站在边缘时也能正确判定。
6.5 物理更新与渲染更新的时序问题
Unity的物理更新(FixedUpdate)和渲染更新(Update)是分开的,物理回调(OnCollision/OnTrigger)在FixedUpdate之后、Update之前执行。这意味着在OnTriggerEnter里修改Transform位置,可能会和渲染插值产生冲突,表现为物体"抖动"或"瞬移"。
解决方案:在触发回调里只记录状态,实际的位置修改放到Update或协程里做。或者给Rigidbody开Interpolate,让物理位置和渲染位置平滑过渡。我做过一个传送门项目,在OnTriggerEnter里直接改position会导致角色瞬移后画面撕裂,改成"标记待传送、下一帧执行"后就正常了。
7. 我踩过的几个真实坑
说几个印象深刻的。有一次做拾取系统,金币的Trigger死活不触发,排查了半天发现金币的Layer被设成了"Default",而玩家的检测脚本里LayerMask只包含了"Interactable"层,Overlap查询根本没扫到金币。这个问题的隐蔽之处在于:金币的Trigger本身是正常的,玩家走过去物理上确实重叠了,但脚本用的是主动查询而不是被动回调,查询的LayerMask漏了Default层。教训是:主动查询和被动回调的过滤条件要分开检查,别以为Trigger配好了就万事大吉。
还有一次做敌人警戒范围,用了一个大SphereTrigger,结果敌人一多,OnTriggerStay每帧调用几百次,帧率掉得厉害。后来改成在Enter时启动一个协程做周期性检测,Exit时停止协程,Stay回调直接删掉,性能立刻回来了。这个经验让我养成了一个习惯:OnTriggerStay能不用就不用,持续性的逻辑用计时器或协程替代。
最后一个关于MeshCollider的。给一个动态破碎的物体挂MeshCollider,勾了Convex,结果破碎后的小块形状复杂,Convex简化后碰撞体和视觉严重不符,碎片悬空或者穿地。后来改成每个碎片用BoxCollider近似,虽然精度低了点,但稳定性和性能都好得多。动态物体用简单碰撞体近似,这个原则在大多数项目里都成立。
Trigger的尺寸校准也值得多说一句。我现在的习惯是给所有Trigger加一个运行时可视化的调试开关,按一个键就显示所有Trigger的线框,调场景的时候一目了然。这个习惯帮我省掉了大量"为什么没触发"的排查时间——很多时候就是Trigger尺寸差了一点点,肉眼看不出来,线框一显示就清楚了。