Unity Term Book
Physics

Collision Events

Ba callback vật lý — OnCollisionEnter, Stay và Exit — báo chi tiết va chạm: điểm tiếp xúc, lực tác động, vận tốc tương đối và Collider của đối phương.

Hãy tưởng tượng...

Collision Events giống như bảo hiểm tai nạn. Khi hai xe đâm nhau (OnCollisionEnter), bảo hiểm ghi lại: ai đâm ai, tốc độ bao nhiêu, điểm đâm chỗ nào. Trong khi xe còn chạm nhau (OnCollisionStay), vẫn tiếp tục ghi nhận. Khi tách ra (OnCollisionExit), đóng hồ sơ. Thông tin trong `Collision` object giống như báo cáo tai nạn — bạn có thể đọc lực va chạm, toạ độ va chạm, và "ai" gây ra nó.

Khái niệm chi tiết

Khi hai Collider va chạm trong Unity Physics (không phải Trigger), engine gọi ba phương thức callback: OnCollisionEnter(Collision) vào frame đầu tiên, OnCollisionStay(Collision) mỗi physics frame tiếp theo, và OnCollisionExit(Collision) khi tách ra. Tham số Collision chứa toàn bộ dữ liệu va chạm.

Object Collision cung cấp: collision.gameObject (object đâm vào), collision.contacts (mảng điểm tiếp xúc bao gồm vị trí và normal vector), collision.relativeVelocity (vận tốc tương đối — thường dùng để tính sức mạnh va chạm), và collision.impulse (xung lực tổng).

Callback này được gọi trong Physics thread, sau bước tính toán vật lý. Vì vậy, bạn không nên thực hiện các thao tác nặng (tạo nhiều object, tải asset) trong đây — thay vào đó, chỉ cập nhật trạng thái (biến bool, queue event) và xử lý logic nặng ở Update. OnCollisionStay đặc biệt tốn kém nếu có nhiều object đang đứng yên chạm nhau — có thể tối ưu bằng Physics.reuseCollisionCallbacks = true.

Để nhận callback, cả hai object tham gia va chạm phải có Collider, và ít nhất một phải có Rigidbody (non-kinematic). Static Collider (không Rigidbody) có thể nhận callback từ Dynamic Rigidbody đâm vào, nhưng hai Static Collider không bao giờ báo cáo collision với nhau.

Dữ liệu trong Collision Object

Collision collision

├─
collision.gameObject// Object đâm vào
├─
collision.rigidbody// Rigidbody của object kia
├─
collision.collider// Collider cụ thể
├─
collision.relativeVelocity// Vector3: vận tốc tương đối (magnitude = sức mạnh)
├─
collision.impulse// Vector3: tổng xung lực trao đổi
└─
collision.contacts[ ]// Mảng ContactPoint
├─.point// Vector3: vị trí tiếp xúc trong world
└─.normal// Vector3: hướng bề mặt tại điểm chạm

Hướng dẫn thực hành

1

Thêm phương thức callback vào MonoBehaviour

OnCollisionEnter(Collision collision) — Unity tự gọi khi va chạm, không cần đăng ký event.

2

Đọc collision.relativeVelocity.magnitude

Dùng để phán đoán sức mạnh va chạm: nhẹ → tiếng kêu nhỏ, mạnh → gây damage, vỡ vụn...

3

Lấy điểm tiếp xúc đầu tiên

collision.GetContact(0) hoặc collision.contacts[0].point — dùng để spawn particle, decal tại đúng vị trí va chạm.

4

Lọc theo tag hoặc layer

collision.gameObject.CompareTag("Enemy") trước khi xử lý logic — tránh phản ứng nhầm với mọi va chạm.

5

Tránh lạm dụng OnCollisionStay

Nếu chỉ cần biết "đang chạm", dùng biến bool set trong Enter/Exit, rồi đọc trong Update thay vì Stay.

Trình mô phỏng tương tác

Click "Bắn" để phóng projectile vào tường — xem các sự kiện collision được ghi lại theo thứ tự.

velocity: —

Nhấn "Bắn" để xem collision events...

Ví dụ Code

Cơ bản

Phát âm thanh và spawn particle tại điểm va chạm.

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;

      // Chọn âm thanh dựa vào tốc độ va chạm
      AudioClip clip = speed > hardHitThreshold ? hardHit : softHit;
      AudioSource.PlayClipAtPoint(clip, collision.GetContact(0).point);

      // Spawn hiệu ứng tại điểm va chạm đầu tiên
      if (impactFX != null)
      {
          ContactPoint contact = collision.GetContact(0);
          Instantiate(impactFX, contact.point,
              Quaternion.LookRotation(contact.normal));
      }
  }
}

Ví dụ Code

Nâng cao

Vật thể vỡ vụn khi va chạm đủ mạnh — spawn mảnh vỡ và xoá original.

using UnityEngine;

public class BreakableObject : MonoBehaviour
{
  public GameObject[] fragmentPrefabs; // Các mảnh vỡ
  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 và phân tán mảnh vỡ
      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);
  }
}

📌 Ghi nhớ nhanh

  • Enter → Stay → Exit, đúng theo thứ tự
  • relativeVelocity.magnitude = sức mạnh va chạm
  • contacts[0].point = vị trí va chạm trong world
  • contacts[0].normal = hướng bề mặt bị chạm
  • Static-static không bao giờ gọi OnCollisionEnter

⚠️ Lỗi thường gặp

  • ❌ Chỉnh physics trong OnCollisionStay mỗi frame

    Gây dao động không ổn định (jitter) và giật lag

    ✅ Chỉ dùng Enter/Exit để set cờ, xử lý logic trong FixedUpdate

  • ❌ Truy cập contacts[] bằng index không kiểm tra

    contacts có thể rỗng, gây IndexOutOfRange exception

    ✅ Dùng GetContact(0) hoặc kiểm tra contacts.Length > 0