Recursive AI

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.

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

Terminal
dotnet add package \
  RecursiveAI.OneGame.Physics

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
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, 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

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

Prints:

Text
TriggerEnter

Create 2D physics bodies · Shift the 3D 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 documents the event kinds, tick order and saved-state behavior used here.

All onegame recipes