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
Hands-on steps
Add the callback to a MonoBehaviour
OnCollisionEnter(Collision collision) — Unity calls it for you; no event registration needed.
Read collision.relativeVelocity.magnitude
Use it as impact strength: light → a quiet tap, heavy → damage, shatter...
Take the first contact point
collision.GetContact(0) or collision.contacts[0].point — spawn particles or a decal at the exact hit.
Filter by tag or layer
collision.gameObject.CompareTag("Enemy") before running logic — ignore unrelated hits.
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.
Press "Fire" to see collision events...
Code example
BasicPlay 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
AdvancedShatter 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