---
title: "Collision events and trigger zones in C#"
url: https://recursiveai.org/onephysics/cookbook/events-and-sensors/
description: "Collision events and trigger zones in C# 2D physics: react to contacts, impacts, sleep and sensor overlaps with handlers or polled events, in the same order on every machine. Runnable code, with OnePhysics (preview)."
version: "0.4.0-preview.1"
---

# Collision events and trigger zones in C#

Subscribe to `OnCollisionEnter` and the other body events, or poll the world’s event list after each `Step`. A sensor shape is a trigger zone: it reports bodies entering and leaving without pushing them. Below, a ball falls through a sensor zone onto the ground.

```shell
dotnet new console -o Events && cd Events
dotnet add package RecursiveAI.OnePhysics --version 0.4.0-preview.1
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using RecursiveAI.OneMath;
using RecursiveAI.OnePhysics;
using RecursiveAI.OnePhysics.Physics2D;

var world = new PhysicsWorld2D(new WorldOptions2D { Events = EventMask.Default | EventMask.ContactHit });
var ground = world.AddBox(new Vector2F(20, 1), at: new Vector2D(0, -0.5), type: BodyType.Static);

var ball = world.AddCircle(0.5f, at: new Vector2D(0, 6));
ball.OnCollisionEnter += c => Console.WriteLine($"enter: ball touches {(c.Other == ground ? "ground" : c.Other.ToString())}");
ball.OnCollisionHit += c => Console.WriteLine($"hit: landed at {c.ApproachSpeed:F3} m/s");
ball.OnSleep += b => Console.WriteLine($"the ball fell asleep");

// A sensor: reports overlaps, never pushes.
var zone = world.CreateBody(new BodyDef { Type = BodyType.Static, Position = new Vector2D(0, 3) });
world.CreateBox(zone, new Vector2F(4, 1), def: new ShapeDef { IsSensor = true });
zone.OnTriggerEnter += t => Console.WriteLine("trigger: something entered the zone");
zone.OnTriggerExit += t => Console.WriteLine("trigger: it left the zone");

int begins = 0;
for (int i = 0; i < 300; i++)
{
    world.Step(1f / 60);
    begins += world.GetEvents(EventKind.ContactBegin).Length;   // polled: a span, valid until the next step
}
Console.WriteLine($"polled ContactBegin events: {begins}");
```

```text
trigger: something entered the zone
trigger: it left the zone
enter: ball touches ground
hit: landed at 10.464 m/s
the ball fell asleep
polled ContactBegin events: 1
```

## Two ways to listen

- **Handlers on a body:** `OnCollisionEnter`, `OnCollisionStay`, `OnCollisionExit`, `OnCollisionHit`, `OnTriggerEnter`, `OnTriggerExit`, `OnSleep` and `OnWake`. A `Collision` carries the other body and shape, the normal, the contact points, the normal impulse and the approach speed.
- **Polled events:** `world.Events` is every event of the last step and `world.GetEvents(kind)` the slice of one kind. Both are spans, valid until the next `Step`, and allocate nothing. Prefer them in rollback code.

`WorldOptions2D.Events` chooses what is recorded: `ContactHit` (impacts with their speed) and `ContactPersist` are off by default, and a kind that is off costs nothing.

## Order

Events are sorted after each step, first by kind (`ContactBegin`, `ContactPersist`, `ContactEnd`, `SensorEnter`, `SensorExit`, `JointBreak`, `Sleep`, `Wake`, `ContactHit`) and then by the slots of the bodies and shapes, and handlers run in that order: first for body A of a pair, then body B. The same inputs give the same list on every platform and thread count. Details in [events](https://recursiveai.org/onephysics/docs/events/).

## Limits

- **OnePhysics is a preview** (0.4.0-preview.1, .NET 10) and is paused for now. The API and results may change between previews.
- **Events arrive after the step,** not during it, so a handler can’t stop a contact that already happened; filter pairs with layers instead ([Raycasts and collision layers](https://recursiveai.org/onephysics/cookbook/queries-and-layers/)).
