Unity Term Book
物理

Collision Events

Unity 三个物理回调——OnCollisionEnter、Stay、Exit——报告接触点、冲击力、相对速度以及对方 Collider。它们配合 Rigidbody 使用,适合伤害结算、脚步音效、撞击反馈等玩法;理解与 Trigger 事件的差异,能帮程序选对回调路径并控制物理回调带来的性能开销。

想象一下...

Collision Events 就像事故保险报告。两车相撞时(OnCollisionEnter),报告记下谁撞谁、多快、撞在哪。继续接触时(OnCollisionStay)持续记录。分开时(OnCollisionExit)结案。`Collision` 对象就是那份报告——冲击力、世界空间接触点,以及是谁造成的。

概念详解

当两个 Collider 在 Unity Physics 中发生碰撞(非 Trigger)时,引擎触发三个回调: 第一帧的 OnCollisionEnter(Collision)、 之后每个物理帧的 OnCollisionStay(Collision),以及 分离时的 OnCollisionExit(Collision)Collision 参数 携带完整的接触数据。

Collision 提供:collision.gameObject(对方物体)、 collision.contacts(带位置与法线的接触点)、 collision.relativeVelocity(相对速度——通常当作冲击强度), 以及 collision.impulse(总冲量)。

这些回调跑在物理步、求解器之后。不要在这里大量生成对象或加载资源—— 设一个标志或入队事件,把重活放到 Update。许多刚体互相靠着时, OnCollisionStay 尤其昂贵——可考虑 Physics.reuseCollisionCallbacks = true

双方都需要 Collider,且至少一方需要非 kinematic 的 Rigidbody。静态 Collider 可从撞来的 Dynamic Rigidbody 收到回调,但两个静态 Collider 永远不会互相报告碰撞。

Collision 对象里的数据

Collision collision

├─
collision.gameObject// 撞上来的对象
├─
collision.rigidbody// 对方对象的 Rigidbody
├─
collision.collider// 具体的 Collider
├─
collision.relativeVelocity// Vector3:相对速度(magnitude = 撞击强度)
├─
collision.impulse// Vector3:交换的总冲量
└─
collision.contacts[ ]// ContactPoint 数组
├─.point// Vector3:世界空间中的接触点
└─.normal// Vector3:接触点处的表面方向

动手步骤

1

在 MonoBehaviour 中写回调

OnCollisionEnter(Collision collision) —— Unity 自动调用,无需注册事件。

2

读取 collision.relativeVelocity.magnitude

当作冲击强度:轻 → 轻敲声,重 → 伤害、碎裂……

3

取第一个接触点

collision.GetContact(0) 或 collision.contacts[0].point —— 在精确命中处生成粒子或贴花。

4

按 Tag 或 Layer 过滤

先 collision.gameObject.CompareTag("Enemy") 再跑逻辑——忽略无关碰撞。

5

不要滥用 OnCollisionStay

若只需“是否接触”,在 Enter/Exit 设 bool,在 Update 里读,而不是用 Stay。

交互模拟器

点击 "Fire" 把弹体射向墙壁——按顺序观察碰撞事件日志。

velocity: —

按下“发射”查看 collision events...

代码示例

基础

在接触点播放音效并生成粒子。

using UnityEngine;

public class CollisionSounds : MonoBehaviour
{
  public AudioClip softHit, hardHit;
  public ParticleSystem impactFX;
  public float hardHitThreshold = 5f;

  void OnCollisionEnter(Collision collision)
  {
      float speed = collision.relativeVelocity.magnitude;

      // Pick a clip from impact speed
      AudioClip clip = speed > hardHitThreshold ? hardHit : softHit;
      AudioSource.PlayClipAtPoint(clip, collision.GetContact(0).point);

      // Spawn FX at the first contact
      if (impactFX != null)
      {
          ContactPoint contact = collision.GetContact(0);
          Instantiate(impactFX, contact.point,
              Quaternion.LookRotation(contact.normal));
      }
  }
}

代码示例

进阶

冲击够强时碎裂——生成碎片并销毁原物体。

using UnityEngine;

public class BreakableObject : MonoBehaviour
{
  public GameObject[] fragmentPrefabs; // Debris pieces
  public float breakForceThreshold = 8f;
  public float fragmentExplosionForce = 200f;
  bool broken = false;

  void OnCollisionEnter(Collision collision)
  {
      if (broken) return;
      if (collision.relativeVelocity.magnitude < breakForceThreshold) return;

      broken = true;
      ContactPoint contact = collision.GetContact(0);

      // Spawn and scatter fragments
      foreach (GameObject frag in fragmentPrefabs)
      {
          GameObject piece = Instantiate(frag, transform.position,
              Random.rotation);

          Rigidbody rb = piece.GetComponent<Rigidbody>();
          if (rb != null)
              rb.AddExplosionForce(fragmentExplosionForce,
                  contact.point, 1f);
      }

      Destroy(gameObject);
  }
}

📌 快速记忆

  • Enter → Stay → Exit,顺序固定
  • relativeVelocity.magnitude = 冲击强度
  • contacts[0].point = 世界空间命中位置
  • contacts[0].normal = 命中处表面方向
  • 静态对静态永远不会调用 OnCollisionEnter

⚠️ 常见错误

  • ❌ 在 OnCollisionStay 里每帧改物理

    不稳定的抖动与卡顿

    ✅ 在 Enter/Exit 设标志,在 FixedUpdate 跑逻辑

  • ❌ 不检查长度就索引 contacts[]

    contacts 可能为空 → IndexOutOfRange

    ✅ 用 GetContact(0) 或先检查 contacts.Length > 0