---
title: "Collision"
description: "Colliders, the ICollision interface, pair data, and world collision groups."
canonical_url: "https://docs.ctrl-neo.dev/en/neogamelib/collision"
---
# Collision

The collider bus detects overlapping collider pairs once per frame (after the
world update) and notifies `ICollision` components on both objects. Colliders
are plain rectangles in world space; they don't need sprites.

## The two colliders

Both register themselves with `NeoGame.ColliderBus` in `OnAwake` and
unregister on destroy.

### NeoBoxCollider

A rectangle defined by `Offset` + `Size`, relative to the parent transform:

```csharp
NeoObject wall = world.Instantiate("Wall", new Transform(new Vector2(200, 200)));
NeoBoxCollider wallCol = wall.AddComponent<NeoBoxCollider>();
wallCol.Size = new Vector2(64, 32);        // centered on the object position
wallCol.Offset = new Vector2(0, -16);      // shift the box center
```

<table>
<thead>
  <tr>
    <th>
      Member
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        Offset
      </code>
    </td>
    
    <td>
      <code>
        (0, 0)
      </code>
    </td>
    
    <td>
      box center offset from the position
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Size
      </code>
    </td>
    
    <td>
      <code>
        (1, 1)
      </code>
    </td>
    
    <td>
      box dimensions
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Bounds
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      world-space <code>
        Rectangle
      </code>
      
      , computed per access
    </td>
  </tr>
</tbody>
</table>

`Bounds` follows position and scale (offset mirrors with a negative scale,
size stays absolute). **Rotation is ignored** — rotated colliders would need
oriented boxes.

### NeoSpriteCollider

A box collider whose `Bounds` come from the `NeoSpriteRenderer` on the *same
object* — no need to keep `Size` synced with the texture:

```csharp
NeoObject player = world.Instantiate("Player");
player.AddComponent(new NeoSpriteRenderer(RenderBus, sheet));
player.AddComponent<NeoSpriteCollider>();   // auto-sizes to the current frame
```

It follows the animation frame (via `SourceRectangle`), the sprite's anchor,
scale, and position. Without a renderer on the object, `Bounds` is empty.

## Reacting: ICollision

Implement `ICollision` on any component of a collider-holding object; the bus
calls it on **both** objects of a pair when they overlap.

```csharp
public class HurtBox : NeoComponent, ICollision
{
    public void OnCollision(NeoBoxCollider other, NeoCollision collision)
    {
        // `other` is the collider on the object you hit
        // `collision` is the shared pair data (same instance for both sides)
    }
}

player.AddComponent<NeoSpriteCollider>();
player.AddComponent(new HurtBox());
```

- Called every frame the pair overlaps (enter/stay/exit tracking doesn't
exist yet).
- All `ICollision` components on an object are called, not just the first.
- Callbacks fire only for enabled, attached colliders.
- Add/remove these components to add/remove collision behavior — no collider
subclassing.

### The pair data

`NeoCollision` is a dumb holder: `A`, `B` (both colliders) and `Overlap` (the
shared `Rectangle` of the two bounds — useful for push-out). Both sides
receive the same instance, so state you stash on it is shared between them.

## When detection runs

`NeoGame.Update` ticks worlds first, then `ColliderBus.Update()`. So
`ICollision` callbacks see positions from *this* frame's updates, and moving a
collider in `OnUpdate` is reflected the same frame.

`ColliderBus.Update()` returns the frame's `List<NeoCollision>`; the bus also
keeps it in `ColliderBus.Collisions`. Only pairs whose callbacks actually ran
are recorded.

The detection loop is an O(n^2) pair test with per-frame cached bounds (fine
for the current scale; a broadphase is a known future optimization).

## World collision groups

By default, ungrouped worlds collide with all other ungrouped worlds — even
across different worlds. UI-in-a-separate-world setups usually don't want UI
colliding with gameplay. Groups fix that:

```csharp
WorldManager.GroupCollisions("ui", "ui");                          // ui only collides with ui
WorldManager.GroupCollisions("gameplay", "players", "enemies");    // these three share detection
WorldManager.UngroupCollisions("ui");                              // back to the ungrouped pool
```

Rule: two colliders interact when both are ungrouped, or both are in the same
group. Ungrouped never interacts with grouped.

## Ordering and safety

Callbacks are user code and may do anything mid-pass (destroy objects, remove
colliders, unload/load worlds). The pass snapshots colliders with cached
bounds and groups, so:

- a collider removed mid-pass gets no further callbacks this pass;
- a collider added mid-pass starts colliding next pass;
- pair callbacks respect group membership captured at pass start.

## NeoMover integration

`NeoMover` (see [Systems](https://docs.ctrl-neo.dev/8.systems#neomover)) does its own per-axis
correction against the live collider list for movement, and uses the frame's
`Collisions` for ground detection. It needs its own `NeoBoxCollider` on the
same object.


## Sitemap

See the full [sitemap](https://docs.ctrl-neo.dev/sitemap.md) for all pages.
