Unity Term Book
Physics

Collision Events

Three physics callbacks — OnCollisionEnter, Stay, and Exit — report contact details: contact points, impact force, relative velocity, and the other Collider.

Imagine...

Collision Events are like accident insurance. When two cars hit (OnCollisionEnter), the report records who hit whom, how fast, and where. While they stay in contact (OnCollisionStay), it keeps logging. When they separate (OnCollisionExit), the file closes. The `Collision` object is that report — impact force, world contact point, and who caused it.

The concept in detail

When two Colliders collide in Unity Physics (not a Trigger), the engine fires three callbacks: OnCollisionEnter(Collision) on the first frame, OnCollisionStay(Collision) on every following physics frame, and OnCollisionExit(Collision) when they separate. The Collision argument holds the full contact data.

Collision exposes: collision.gameObject (the other object), collision.contacts (contact points with position and normal), collision.relativeVelocity (relative speed — typically used as impact strength), and collision.impulse (total impulse).

These callbacks run on the physics step, after the solver. Do not spawn lots of objects or load assets here — set a flag or enqueue an event, and do heavy work in Update. OnCollisionStay is especially expensive when many bodies rest against each other — consider Physics.reuseCollisionCallbacks = true.

Both objects need a Collider, and at least one needs a non-kinematic Rigidbody. A static Collider can receive callbacks from a Dynamic Rigidbody hitting it, but two static Colliders never report collisions with each other.

Data inside the Collision object

Collision collision

├─
collision.gameObject// The object that hit
├─
collision.rigidbody// The other object's Rigidbody
├─
collision.collider// The specific Collider
├─
collision.relativeVelocity// Vector3: relative velocity (magnitude = impact strength)
├─
collision.impulse// Vector3: total impulse exchanged
└─
collision.contacts[ ]// ContactPoint array
├─.point// Vector3: world-space contact position
└─.normal// Vector3: surface direction at the contact point

Hands-on steps

1

Add the callback to a MonoBehaviour

OnCollisionEnter(Collision collision) — Unity calls it for you; no event registration needed.

2

Read collision.relativeVelocity.magnitude

Use it as impact strength: light → a quiet tap, heavy → damage, shatter...

3

Take the first contact point

collision.GetContact(0) or collision.contacts[0].point — spawn particles or a decal at the exact hit.

4

Filter by tag or layer

collision.gameObject.CompareTag("Enemy") before running logic — ignore unrelated hits.

5

Do not overuse OnCollisionStay

If you only need “is touching”, set a bool in Enter/Exit and read it in Update instead of Stay.

Interactive simulator

Click "Fire" to launch a projectile into the wall — watch collision events log in order.

velocity: —

Press "Fire" to see collision events...

Code example

Basic

Play a sound and spawn particles at the contact point.

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));
      }
  }
}

Code example

Advanced

Shatter when the impact is strong enough — spawn fragments and destroy the original.

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);
  }
}

📌 Quick recap

  • Enter → Stay → Exit, always in that order
  • relativeVelocity.magnitude = impact strength
  • contacts[0].point = world-space hit position
  • contacts[0].normal = surface direction at the hit
  • Static-vs-static never calls OnCollisionEnter

⚠️ Common mistakes

  • ❌ Tweaking physics inside OnCollisionStay every frame

    Unstable jitter and hitching

    ✅ Set flags in Enter/Exit, run logic in FixedUpdate

  • ❌ Indexing contacts[] without a length check

    contacts can be empty → IndexOutOfRange

    ✅ Use GetContact(0) or check contacts.Length > 0