3.3 碰撞器、刚体与触发器:碰撞事件的值班记录 本节摘要:碰撞器(Collider)定义物理外形,刚体(Rigidbody)让物体参与模拟,两者的组合决定碰撞事件如何分发:实打实的碰撞走 OnCollision 名单,虚拟感应区走 OnTrigger 名单。本节给出事件分发的完整规则表与三个典型玩法实现。 两种见面方式 两个物体的碰撞器在空间里重叠时,引擎要回答一个问题:这是一次物理碰撞,还是一次路过?答案由触发器开关决定。碰撞器默认是"实心"的——互相阻挡、交换动量、产生反弹,并触发 OnCollision 系列回调;
本节摘要:碰撞器(Collider)定义物理外形,刚体(Rigidbody)让物体参与模拟,两者的组合决定碰撞事件如何分发:实打实的碰撞走 OnCollision 名单,虚拟感应区走 OnTrigger 名单。本节给出事件分发的完整规则表与三个典型玩法实现。
两个物体的碰撞器在空间里重叠时,引擎要回答一个问题:这是一次物理碰撞,还是一次路过?答案由触发器开关决定。碰撞器默认是"实心"的——互相阻挡、交换动量、产生反弹,并触发 OnCollision 系列回调;勾上 Is Trigger 后,碰撞器变成"感应门"——不产生任何物理阻挡,只报告"有东西进来了、在里面、离开了",对应 OnTriggerEnter、OnTriggerStay、OnTriggerExit 三个回调。
事件能不能触发,取决于双方的组件组合,规则值得背下来:
碰撞与触发的成立条件 碰撞事件(OnCollisionEnter 等):双方都有碰撞器,且至少一方有刚体(非运动学) 触发事件(OnTriggerEnter 等):双方都有碰撞器(至少一方勾了 Is Trigger), 且至少一方有刚体——静态碰撞器对静态碰撞器,两边都不报告 网格碰撞器(Mesh Collider)的特例:凸模式(Convex)才能与刚体正常交互
最常踩的坑在这条规则里:给一堵墙只挂碰撞器、不给刚体(这没问题,静态墙合法),却期望它对另一个"只有碰撞器、没有刚体"的物体报触发事件——不会报。经验法则是:任何需要收到事件的物体,身上至少有一个刚体,不动就把刚体设为运动学(Is Kinematic),由代码驱动而物理引擎不施力。
OnCollision 系列回调给的是 Collision 对象,里面有接触点、相对速度、对方碰撞器等完整档案;OnTrigger 系列给的是 Collider,只有对方的形状与身份。回调一式三份:Enter 进入瞬间一次,Stay 每个物理步一次,Exit 离开一次。Stay 是高频回调,里面别放重逻辑,状态判断尽量在 Enter 里做完。
背景:平台关卡需要三样东西:撞上会死的尖刺(碰撞)、飞过去会拾取的金币(触发)、踩上去开门的压力板(触发加状态)。三者覆盖碰撞与触发的全部典型用法。
操作:三段代码分别挂在尖刺、金币与压力板上:
using UnityEngine; // 一、尖刺:实体碰撞,撞上即死 public class SpikeHazard : MonoBehaviour { void OnCollisionEnter(Collision collision) { // 按层过滤:只对玩家生效,敌人与道具随便撞 if (collision.gameObject.layer == LayerMask.NameToLayer("Player")) { Debug.Log("玩家踩刺,任务失败"); // 通知 GameManager 重生(此处用日志代替) } } } // 二、金币:触发器拾取,碰到的必须是玩家 public class CoinTrigger : MonoBehaviour { void OnTriggerEnter(Collider other) { // 用标签判断身份,比层级更语义化 if (!other.CompareTag("Player")) return; Debug.Log("拾取金币"); gameObject.SetActive(false); // 收起来,留给对象池复用(第5章) } } // 三、压力板:进入激活、离开取消,带状态 public class PressurePlate : MonoBehaviour { public GameObject door; // Inspector 里拖门对象 int pressCount; // 支持多物体同时压板 void OnTriggerEnter(Collider other) { if (!other.CompareTag("Pushable")) return; pressCount++; door.SetActive(false); } void OnTriggerExit(Collider other) { if (!other.CompareTag("Pushable")) return; pressCount--; if (pressCount <= 0) door.SetActive(true); } }
结果:角色撞尖刺立刻判负;穿过金币被拾取(金币不阻挡移动);把可推动的箱子推上压力板,门打开,箱子移开后门关闭。
解读:三个实现藏着四条通用模式。其一,身份过滤双轨制:层级(Layer)做粗过滤(物理层面的类别,还能直接用于碰撞矩阵,让某两层根本不互相检测),标签(Tag)做语义判断("Player"这个概念属于业务,不属于物理层)。其二,金币用 SetActive(false) 而不是 Destroy,为第 5 章对象池留后路——激活与停用是池化复用的基本操作。其三,压力板用计数器而不是布尔值,一只箱子加一只角色同时压板、先移走角色时门不会提前关。其四,碰撞回调里只做"记录事实与转发",复杂逻辑发给 GameManager——回调可能一帧多次,把状态机搅乱。
变式:给尖刺改成触发器(Is Trigger 勾上),实现"穿越即受伤但不阻挡"的毒气区;再给角色加短暂无敌帧(受击后 0.5 秒忽略再次进入),体会 Enter 配合计时器做免伤窗口——格斗与动作游戏的公共组件就是这么长出来的。
两个细节让碰撞表现从"能玩"到"有质感"。物理材质(Physic Material)控制表面的摩擦与弹性:给球体配一个弹性 0.8 的物理材质,落地就会弹跳;冰面低摩擦、传送带高摩擦,全是这一个组件的事。碰撞矩阵(Project Settings 的 Physics 面板)则从全局规定哪两层之间做不做检测:把"Player 层"与"PlayerBullet 层"互相关掉,子弹就永远不会打中携带者——比在回调里逐个判断高效得多,这属于物理岗的编制规则,先配置后写码。
输入与物理岗到此结业,角色已经是个能跳能撞能拾取的活物。第 4 章让值班室亮起来:渲染与表现岗开工。