Collisions work using two nodes, CollisionDetectionNode and ColliderNode.
CollisionDetectionNode sets up a collision detection arena. Whenever a ColliderNode
is added to a scene, it finds the closest CollisionDetectionNode above it and registers itself.
Today, collision detection has two significant limitations:
- It works only with
Shape: circles and rectangles. - It only reports the other
ColliderNode, not where they collided.
In exchange for these limitations, the arena is quite performant. See how many balls you can spawn before your FPS drops!
Layers and Masks#
ColliderNode supports specifying two bitmasks, layer and mask, to exclude certain collisions from consideration.
layer indicates the physics layers the collider exists on, while mask indicates which physics layers it collides with. A pair only reports a collision to a side whose
mask intersects the other's layer.
While layer and mask are defined as integers, they should be treated as bitmasks. Each position in the integer is a separate physics layer. That also means Ignis only supports up to 32 collision detection layers. By default,
layer and mask are -1 (all bits are 1), letting all colliders interact.
The code below excludes wall-wall collisions altogether, greatly improving collision detection performance and removing the need to handle those cases in your code.
const TERRAIN_LAYER = 1 << 0;
const UNIT_LAYER = 1 << 1;
wall
..layer = TERRAIN_LAYER
..mask = 0;
player
..layer = UNIT_LAYER
..mask = TERRAIN_LAYER | UNIT_LAYER;
The names layer and mask come from Godot's collision system.
Examples#
Reporting a pair#
final collider = mover.add(
ColliderNode(
shape: mover.shape,
anchor: mover.anchor,
),
);
collider
..onCollisionStart((_) => log('colliding', .red))
..onCollisionEnd((_) => log('not colliding', .green));
Everything you touch#
final collider = mover.add(
ColliderNode(
shape: mover.shape,
anchor: mover.anchor,
),
);
tick((_) {
log(switch (collider.active.length) {
0 => "can't touch this!",
final n => 'touching $n',
});
});
A spinning hitbox#
final blade = add(
ShapeNode(
shape: .rectangle(.new(70, 10)),
anchor: .center,
position: _CENTER,
paint: Paint()..color = _IDLE_COLOR,
),
);
final collider = blade.add(
ColliderNode(
shape: blade.shape,
anchor: blade.anchor,
),
);
blade.add(SpinEffect(speed: pi / 2));
Filtering with layers#
blue.add(
ColliderNode(
shape: blue.shape,
anchor: blue.anchor,
layer: BLUE_LAYER,
),
);
orange.add(
ColliderNode(
shape: orange.shape,
anchor: orange.anchor,
layer: ORANGE_LAYER,
),
);
final collider = mover.add(
ColliderNode(
shape: mover.shape,
anchor: mover.anchor,
mask: ORANGE_LAYER,
),
);
Many at once#
final collider = add(
ColliderNode(
shape: shape,
anchor: anchor,
),
);
collider
..onCollisionStart((_) {
paint.color = _HIT_COLOR;
})
..onCollisionEnd((_) {
if (!collider.isColliding) {
paint.color = _IDLE_COLOR;
}
});
