U
09 / 22 · 9 分钟
常见问题

碰撞与触发器:为什么事件从不触发

简短回答

为什么 OnCollisionEnter 和 OnTriggerEnter 不触发?

两个物体都需要 collider,且其中一个需要 Rigidbody:OnCollisionEnter 要非 kinematic 的那种,OnTriggerEnter 则任何都行(包括 kinematic)。然后再检查方法名和它的参数、脚本是否挂在持有 collider 的那个物体上,以及这两个层在 Layer Collision Matrix 里是否仍互相勾选。

两个物体在屏幕上明明碰到了,你的方法却不执行。Console 里一点动静都没有——没报错、没警告——Unity 压根就没调用过它。原因只有六个,全部排查一遍大约只要一分钟。

第一个问题:这两个事件你到底要哪个?

✓ OnCollisionEnter

solid hits solid

两个物体不能互相穿过。碰撞是实打实的撞击时就用它:被撞翻的木箱、砸墙弹回的球。你会拿到一个 Collision,里面带着接触点和撞击时的速度。

✓ OnTriggerEnter

something enters an area

两个物体直接互相穿过,你只会被告知这件事刚刚发生。用它来做金币、门框、伤害区域、剑的攻击范围。你会拿到一个 Collider:只有刚进来的那个东西,不附带任何撞击信息。

六个排查项,按顺序来

  1. 1

    Rigidbody 得在某一边。只有当物理系统在盯着两个物体中的至少一个时,Unity 才会上报接触。碰撞事件要求至少一侧有一个非 kinematic 的 Rigidbody;trigger 事件只要求至少一侧有 Rigidbody,kinematic 也可以。两个物体都静静躺着,就什么都不会上报。

  2. 2

    方法名和参数。OnTriggerEnter(Collider other) 和 OnCollisionEnter(Collision collision)——拼写必须分毫不差。Unity 是按名字找这些方法的,所以写成 onTriggerEnter,或者在 3D 场景里用 Collider2D,编译毫无问题,但永远不会被调用。

  3. 3

    脚本要挂在正确的 object 上。消息会发给持有该 collider 的 GameObject,以及持有这个 collider 所属 Rigidbody 的 GameObject。脚本挂在这两者之上的任何一层,都什么都听不到。

  4. 4

    两个 collider 都存在且处于启用状态。看 Inspector 最快:按 Play,走过去撞一下那个物体,盯着 Scene 窗口里两个 collider 线框是不是真的相碰。保持默认大小的 collider 往往整个缩在模型内部。

  5. 5

    两个 layer 被允许相遇吗。Edit▸Project Settings▸Physics 里放着 Layer Collision Matrix。那里只要有一个格子没打勾,两个物体就会彻底无视彼此——没有碰撞、没有 trigger、没有任何消息。

  6. 6

    2D 和 3D 永远不会相遇。Collider2D 和 Collider 是住在同一个场景里的两套独立物理引擎。2D collider 永远碰不到 3D collider,而 2D 的事件是那些结尾带 2D 的函数。

一个能用的 trigger,和一个能用的碰撞

Coin.cs — on the coin: a trigger collider, no Rigidbody needed on the coin itself
using UnityEngine;

// The coin sits still, so the Rigidbody lives on the player instead.
[RequireComponent(typeof(Collider))]
public class Coin : MonoBehaviour
{
    public int value = 1;

    void Reset()
    {
        // Runs when the component is added: tick Is Trigger for whoever forgets.
        GetComponent<Collider>().isTrigger = true;
    }

    void OnTriggerEnter(Collider other)
    {
        // Anything can walk in here — check who it was before scoring.
        if (!other.CompareTag("Player")) return;

        // Wire this into whatever keeps your score.
        Debug.Log($"+{value}");
        Destroy(gameObject);
    }
}
Crate.cs — on the crate: a solid collider and a Rigidbody that physics may push
using UnityEngine;

[RequireComponent(typeof(Rigidbody), typeof(Collider))]
public class Crate : MonoBehaviour
{
    public AudioSource thud;

    void OnCollisionEnter(Collision collision)
    {
        // Collision carries the impact: a light nudge should not sound like a crash.
        if (collision.relativeVelocity.magnitude < 2f) return;

        thud.Play();
    }
}

还有两个常踩的坑

  • CharacterController 不是 Rigidbody。它永远不会调 OnCollisionEnter——取而代之的是 OnControllerColliderHit——但它移动时照样会触发 trigger。所以才会有“角色推不动箱子、却捡得起金币”这种怪事。

  • 速度足够快的东西会在两帧之间径直穿过一面薄墙。给那个 Rigidbody 把 Collision Detection 设成 Continuous,顺便把墙也加厚一点。

  • 直接改写 transform.position 来移动 Rigidbody 等于瞬移,而瞬移不会留下任何一条路径给物理去检测。改用 Rigidbody.MovePosition 或用速度来移动,接触就会回来。

做出这个的那一课

弹道、伤害与特效

这篇指南单独成篇,是一份可以直接照着做的配方。在课程里,同样的东西会作为贯穿全部五个章的那个项目的一部分来搭建 —— 角色与战斗,第 05 课。