Collision Events
Collision detection emits events when an overlapping pair starts, remains active, or separates. Each event contains every contact of that lifecycle phase detected during the fixed step.
Events
Section titled “Events”| Event | Emitted when |
|---|---|
CollisionStarted | Two colliders overlap for the first fixed step |
CollisionActive | The pair remains overlapping on a later fixed step |
CollisionStopped | A previously overlapping pair no longer overlaps |
Only penetrating colliders produce collision lifecycle events. Shapes that exactly touch without overlap do not produce a contact.
Read contacts in a system
Section titled “Read contacts in a system”Run collision gameplay after PhysicsSet.Collide in Schedule.FixedPostUpdate:
import { Define, Schedule } from '@shell/ecs';import { CollisionStarted, PhysicsSet } from '@shell/physics';
export const HandleCollisionStartedSystem = Define.system() .after(PhysicsSet.Collide) .update(({ events }) => { const event = events.get(CollisionStarted); if (!event) return;
for (const contact of event.data.contacts) { console.log(contact.entityA, contact.entityB); console.log(contact.depth, contact.normalX, contact.normalY); } });
world.registerSystem(Schedule.FixedPostUpdate, HandleCollisionStartedSystem);The same pattern works with CollisionActive and CollisionStopped.
Observe events
Section titled “Observe events”An observer runs immediately after collision detection triggers the event:
const OnCollisionStarted = Define.system().update(({ events }) => { const event = events.get(CollisionStarted); if (!event) return;
for (const contact of event.data.contacts) { // React before the next scheduled system runs. }});
world.observe(CollisionStarted, OnCollisionStarted);Use an observer for immediate reactions. Use a scheduled system when the reaction must be ordered relative to other physics system sets.
Contact data
Section titled “Contact data”Each item in event.data.contacts has these fields:
| Field | Type | Description |
|---|---|---|
entityA | Entity | First collider entity |
entityB | Entity | Second collider entity |
depth | number | Penetration depth |
normalX, normalY | number | Contact normal from entityB toward entityA |
separationX, separationY | number | Normal multiplied by penetration depth |
The event payload also mirrors the first contact at event.data.entityA, event.data.depth, and the other contact fields. Prefer iterating contacts so multiple contacts from the same fixed step are handled.
CollisionStopped contacts identify the entities but set depth, normal, and separation fields to 0 because the shapes are no longer intersecting.
Detection and response
Section titled “Detection and response”Events describe detected contacts independently of physical response. Colliders without rigid bodies still emit events. The built-in resolver only responds when both entities have RigidBody and Position, and every dynamic participant has Velocity. Response does not generate angular impulses.
The resolver consumes CollisionStarted and CollisionActive after detection. It does not consume CollisionStopped.
For general event lifetime and observer behavior, see Events.