Colliders
Colliders make entities visible to collision detection and spatial queries. A collider needs Position and automatically requires CollisionLayer. Add RigidBody only when the built-in resolver should move or bounce the entity.
Collider shapes
Section titled “Collider shapes”Circle
Section titled “Circle”import { Define } from '@shell/ecs';import { CircleCollider } from '@shell/physics';import { Position } from '@shell/transform';
const CircleColliderPrefab = Define.prefab() .with(Position, { x: 100, y: 100, z: 0 }) .with(CircleCollider, { radius: 16 });
commands.spawn(CircleColliderPrefab);| Field | Type | Default | Description |
|---|---|---|---|
radius | number | 1 | Radius around the entity’s Position |
const BoxColliderPrefab = Define.prefab() .with(Position, { x: 200, y: 100, z: 0 }) .with(BoxCollider, { width: 64, height: 32 });
commands.spawn(BoxColliderPrefab);| Field | Type | Default | Description |
|---|---|---|---|
width | number | 1 | Full width centered on Position |
height | number | 1 | Full height centered on Position |
Box colliders are axis-aligned. Rotation, pivot, and collider offsets are not applied.
If an entity has both collider components, collision detection uses its circle collider.
Collision layers
Section titled “Collision layers”CollisionLayer controls which colliders interact. Define layers as bit flags so masks can combine them:
export const Layers = { Player: 1 << 0, Enemy: 1 << 1, World: 1 << 2, Pickup: 1 << 3,} as const;Configure each entity with its own layer and the layers it accepts:
const PlayerColliderPrefab = Define.prefab() .with(Position) .with(CollisionLayer, { layer: Layers.Player, mask: Layers.Enemy | Layers.World | Layers.Pickup, }) .with(CircleCollider, { radius: 16 });
const WallColliderPrefab = Define.prefab() .with(Position) .with(CollisionLayer, { layer: Layers.World, mask: Layers.Player | Layers.Enemy, }) .with(BoxCollider, { width: 64, height: 32 });Place a custom CollisionLayer before the collider in the prefab definition. This makes the intended layer data explicit instead of relying on the default supplied by the collider requirement.
Normal collision detection is mutual. Both expressions must match:
(player.mask & wall.layer) !== 0;(wall.mask & player.layer) !== 0;Adding CircleCollider or BoxCollider automatically adds CollisionLayer with both fields set to 1 when it is missing.
Detection without response
Section titled “Detection without response”A collider does not require a rigid body. This is useful for triggers and query-only entities:
const PickupPrefab = Define.prefab() .with(Position, { x: 250, y: 100, z: 0 }) .with(CollisionLayer, { layer: Layers.Pickup, mask: Layers.Player, }) .with(CircleCollider, { radius: 10 });
const pickup = world.get('commands').spawn(PickupPrefab).root;The pickup participates in detection and emits collision events, but the built-in resolver ignores the contact because both entities need RigidBody for response.
Collision response
Section titled “Collision response”The built-in resolver handles contacts between two entities that both have RigidBody and Position. Every dynamic participant also needs Velocity.
Response includes:
- Impulses along the collision normal
- Restitution using the lower restitution of the two bodies
- Positional correction to reduce overlap
- Infinite effective mass for static and kinematic bodies
RigidBody.friction is stored but is not currently applied. Collision response does not generate angular impulses, and boxes remain axis-aligned even when Rotation.z is integrated. The resolver is suited to simple push-apart behavior rather than a full constraint-based simulation.
Collision timing
Section titled “Collision timing”During each fixed step, CollisionSystem:
- Rebuilds the spatial index from current collider and position data.
- Filters candidate pairs using collision layers.
- Tests circle-circle, circle-box, and box-box intersections.
- Emits collision lifecycle events.
CollisionResolverSystem then consumes active contacts and applies response. Systems that need current collision data should run after PhysicsSet.Collide in Schedule.FixedPostUpdate.
const GameplayCollisionSystem = Define.system() .after(PhysicsSet.Collide) .update(({ events }) => { // Read collision events here. });See Collision Events for contact payloads and event handling.