---
title: "Physics trigger enter and exit in C#"
url: https://recursiveai.org/onegame/cookbook/handle-physics-trigger-events/
description: "Physics trigger enter and exit in C# with onegame. Includes a runnable .NET project, sample output and the game integration details."
version: "1.26.0"
---

# Physics trigger enter and exit in C#

**onegame** is a free, open-source game engine for .NET from Recursive AI. MIT or Apache-2.0, no editor required.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneGame.Physics) · [GitHub](https://github.com/recursive-ai-oss/onegame) · [Docs](https://recursiveai.org/onegame/docs/) · [Download example (.zip)](https://recursiveai.org/onegame/cookbook/handle-physics-trigger-events/handle-physics-trigger-events.zip)

A trigger is a region that reports when a body enters or leaves, without blocking its movement.

```shell
dotnet add package \
  RecursiveAI.OneGame.Physics
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using OneGame.Core;
using OneGame.Physics;

var space = new PhysicsSpace2D();
var zone = space.AddBox(
    new FVec2(4, 4), new FVec2(0, 3),
    new BodyOptions
    { Kind = BodyKind.Static, IsTrigger = true });
space.AddBox(new FVec2(1, 1), new FVec2(0, 9));
for (var tick = 0; tick < 240; tick++)
{
    space.Step();
    foreach (var entry in space.Events)
        if (entry.Involves(zone))
            Console.WriteLine(entry.Kind);
}
```

Prints:

```text
TriggerEnter
TriggerExit
```

The box falls through the zone. The loop steps the simulation, then reads the events from that step before the next step replaces them.

A removed body reference throws `StaleBodyException` when used to access that body; do not keep using a reference after removing its body.

Unzip [the example](https://recursiveai.org/onegame/cookbook/handle-physics-trigger-events/handle-physics-trigger-events.zip), then `dotnet run`.

`Fixed` is a deterministic fixed-point number type; `FVec3` is a three-dimensional vector of those numbers. The 2D example uses its two-dimensional counterpart, `FVec2`.

## Variations

### Handle only entries

```csharp
// Inside the loop above, replace the event filter with:
if (entry.Kind == PhysicsEventKind.TriggerEnter && entry.Involves(zone))
    Console.WriteLine(entry.Kind);
```

Prints:

```text
TriggerEnter
```

## Related recipes

[Create 2D physics bodies](https://recursiveai.org/onegame/cookbook/2d-physics/) · [Shift the 3D physics origin](https://recursiveai.org/onegame/cookbook/shift-physics-origin/)

---

## The short answer

In onegame, add a static body with `IsTrigger = true`, then poll `PhysicsSpace2D.Events`. Filter on `PhysicsEventKind.TriggerEnter` or `PhysicsEventKind.TriggerExit`, and use `Involves(zone)` to select the region you care about. Events are data, not callbacks: declaring a method with an enter or exit name does not subscribe it to anything.

The example creates a box above a trigger and advances the space until the box has passed through. There is no floor in this sample, so the box continues falling after it leaves. The two printed lines are the region’s entry and exit, not two collision responses.

## How do trigger enter and exit events work in 2D?

A trigger detects passage without pushing a body out of the region. Use it for a goal zone, checkpoint or pickup area. Use a normal solid body when the object must stop at a wall or stand on a floor. `PhysicsSpace2D` reports trigger events for dynamic bodies, kinematic bodies and characters; a character does not need a separate callback path.

Each event carries two body references, `A` and `B`. Do not assume your zone is always `A`. `Involves(zone)` tests both sides, so the filter works regardless of pair ordering. If a zone accepts only a particular actor, also check `Involves(actorBody)` before changing game state. This avoids awarding a goal because an unrelated crate entered the same region.

The event list describes the most recent physics step. In an integrated game, commands execute first, entity ticks (updates to game objects) run next, and the registered physics system steps afterward. An entity’s `OnTick` therefore reads the events produced at the end of the preceding tick. Read all relevant entries there, once per tick. A rendering loop can run at a different rate and is not the place to award deterministic game outcomes.

The standalone example owns stepping itself. It calls `space.Step()` and then inspects `space.Events`, which is why its loop can print the new result immediately. Do not add that manual step to an entity when `PhysicsSpace2D.AddTo(world)` already registered automatic stepping: you would advance the physics space twice.

The library delivers events by kind and then by body slots. That order is the same on every node and on replay. The list is also part of saved physics state. Restoring a world preserves the previous step’s events, so the next entity tick reads the same list the original run read. Avoid interpreting a restored list as a fresh external notification to send twice to a service.

A step replaces the list rather than accumulating a permanent history. If game logic needs to remember that a player reached a checkpoint, store that result in synced state, the game properties saved and replicated with the world. Keep a body reference, rather than a raw numeric slot, because the reference includes a generation: reusing a removed slot must not silently identify another object.

Contact events and trigger events serve different purposes. `ContactBegin` and `ContactEnd` describe solid contacts; optional stay and hit recording does not turn a trigger into a solid collider. The [physics guide](https://recursiveai.org/onegame/docs/physics/) documents the event kinds, tick order and saved-state behavior used here.
