DEMIENGINEDOCUMENTATION Everything is free
Project & file formats · Start here

The named collision matrix and how bodies filter each other.

Physics layers

Layers decide which bodies collide with which. Without them, everything hits everything. With them, players pass through pickups, bullets ignore their own team, and triggers only notice the objects you care about.

Declare the collision matrix

physics.layers is an object: each key is a layer name, and its value is the list of layer names it collides with.

"physics": {
  "layers": {
    "goal": ["player"],
    "player": ["goal", "wall"],
    "wall": ["player"]
  }
}

Read it like a sentence: the goal layer collides with player; player collides with goal and wall; wall collides with player. Rules:

  • Each key is a layer name. Every collider picks exactly one of these names via its layer field.
  • The value lists the layers that key collides with. Both sides must accept each other: a contact only happens when each layer lists the other. In the example above, player lists goal and goal lists player, so they meet. If either side omits the other, the pair is ignored — no collision, no trigger, no contact event.
  • A layer name only needs to appear as a key once. Listing a name in a value without declaring it as a key is not enough to create the layer.
  • Maximum 16 properties.
  • Authored order does not assign bit indices. The parser sorts layers by name, so the same matrix always resolves the same way regardless of how the keys are written.

If you omit physics.layers, everything sits on a single implicit default layer and all bodies collide with each other.

How filtering works

Every collider carries a layer name. When two bodies overlap, the engine looks up both names in the matrix and asks one question: does each side list the other?

layers[colliderA.layer]  contains colliderB.layer
layers[colliderB.layer]  contains colliderA.layer

If both answers are yes, the contact is generated. If either says no, the pair is ignored. That is the whole authoring story — no bit math, no masks to maintain by hand.

Colliders also expose low-level category_bits / mask_bits fields for special cases, but they are derived from the matrix and are not how you author collision rules. Prefer the matrix.

Worked example

"physics": {
  "layers": {
    "player": ["world", "enemy", "pickup"],
    "enemy": ["world", "player"],
    "world": ["player", "enemy"],
    "pickup": ["player"]
  }
}
BodylayerCollides with
PlayerplayerWorld, enemies, pickups
EnemyenemyWorld and player, not other enemies
BulletenemySame as enemy — enemy bullets hit the player
WallworldPlayer and enemies
CoinpickupPlayer only

Enemies walk through each other because enemy does not list enemy. Coins never bounce off walls because pickup lists only player, and world does not list pickup.

Triggers and layers

{
  "CircleCollider2D": {
    "radius": 1.5,
    "is_trigger": true,
    "layer": "pickup"
  }
}

A trigger uses the same matrix. It generates physics_trigger_enter/stay/exit instead of solid collision. A pickup area that should only notice the player sits on a layer that lists only player — and player must list that layer back.

2D and 3D

This matrix is the 2D collision matrix (it drives the Box2D world). 3D colliders also take a layer string field, but there is no separate 3D matrix in the project file. Keep the layer names coherent so 2D HUD hit areas and 3D world bodies can share vocabulary.

Debugging filters

demi run \
  --debug-overlays colliders,contacts

Colliders draw in their layer. If two shapes overlap and no contact appears, check the matrix on both sides — a one-way listing is the usual cause.

Choosing layers

  • Start with player, enemy, world, pickup.
  • Add trigger, debris, ragdoll as needed.
  • Prefer a few broad layers over one layer per entity type.
  • Remember the 16-layer ceiling when designing large games.
  • Keep the matrix symmetric where you want contact: if A should hit B, list each in the other's value.

See Colliders and Physics events for what to do once contacts happen.