Skip to content

Collision Detection

This tutorial walks through adding collision detection to a scene using PhysicsBodyComponent and Box2DPhysicsSystem.


Prerequisites

  • Basic familiarity with scenes and the ECS (Entity, Component, TransformComponent)
  • AddPhysics() called in your app startup (see Collision System)

1. Register Physics

In Program.cs (or wherever you configure services):

builder.Services.AddPhysics(opts =>
{
    opts.Gravity = new Vector2(0, 980); // Y-down, pixels per second squared
});

2. Add the System to Your Scene

protected override void OnLoadAsync(IEntityWorld world)
{
    world.AddSystem<Box2DPhysicsSystem>();
}

3. Create a Static Floor

var floor = world.CreateEntity("Floor");
floor.AddComponent<TransformComponent>(t => t.Position = new Vector2(400, 550));
floor.AddComponent<PhysicsBodyComponent>(b =>
{
    b.Shape    = new BoxShape(800, 20);
    b.BodyType = PhysicsBodyType.Static;
});

4. Create a Dynamic Box

var box = world.CreateEntity("Box");
box.AddComponent<TransformComponent>(t => t.Position = new Vector2(400, 100));
box.AddComponent<PhysicsBodyComponent>(b =>
{
    b.Shape    = new BoxShape(32, 32);
    b.BodyType = PhysicsBodyType.Dynamic;
    b.Mass     = 1f;
    b.Material = PhysicsMaterial.Default;
});

The box will fall under gravity and land on the floor.


5. Respond to Collisions

Subscribe to events on the component before or after it is added:

var boxBody = box.GetComponent<PhysicsBodyComponent>();

boxBody.OnCollisionEnter += (other, contact) =>
{
    Logger.LogInformation("Box hit {Other}! Normal: {Normal}",
        other.Entity?.Name, contact.Normal);
};

boxBody.OnCollisionExit += other =>
{
    Logger.LogInformation("Box separated from {Other}", other.Entity?.Name);
};

6. Triggers (Sensors)

A trigger overlaps other bodies but does not generate collision forces. Use them for pickups, damage zones, and area detection:

var pickup = world.CreateEntity("CoinPickup");
pickup.AddComponent<TransformComponent>(t => t.Position = new Vector2(300, 400));
pickup.AddComponent<PhysicsBodyComponent>(b =>
{
    b.Shape     = new CircleShape(16);
    b.BodyType  = PhysicsBodyType.Static;
    b.IsTrigger = true;

    b.OnTriggerEnter += other =>
    {
        if (other.Entity?.Name == "Player")
        {
            CollectCoin();
            pickup.IsActive = false;
        }
    };
});

7. Collision Layers

Prevent enemies from colliding with each other while still colliding with terrain and the player:

// In startup
services.AddPhysics()
        .AddPhysicsLayers(layers =>
        {
            layers.Register("Terrain", 0);
            layers.Register("Player",  1);
            layers.Register("Enemies", 2);
        });
// On the enemy body
var layers = services.GetRequiredService<PhysicsLayerRegistry>();

enemyBody.Layer         = layers.GetLayer("Enemies");
enemyBody.CollisionMask = layers.GetMask("Terrain", "Player"); // Collides with terrain and player only

8. Compound Bodies (Sub-Shapes)

Attach an extra sensor hitbox to a body for attack range detection:

entity.AddComponent<PhysicsBodyComponent>(b =>
{
    b.Shape = new CapsuleShape(new Vector2(0, -12), new Vector2(0, 12), 10);
});

var hitbox = body.AddSubShape(new CircleShape(32), isTrigger: true);

body.OnTriggerEnterWithShape += (other, contact, selfSubShape, otherSubShape) =>
{
    if (selfSubShape == hitbox)
        DealDamage(other.Entity);
};

Next Steps