Collider Debugging
When colliders do not line up with meshes, you get invisible walls and objects falling through floors. ColliderDebugManager wraps BabylonJS's PhysicsViewer so a collider can be drawn with one call, and NodeEntity carries two methods that walk its own node hierarchy and delegate to it.
Three ways in
| Approach | For |
|---|---|
debugCollider in a level's spawn or entity definition | Always-on during development, written into the entity's state |
entity.showCollider() and hideCollider() | Runtime toggling, inspector tools, the debug console |
engine.debug.colliders | One TransformNode at a time, from your own tooling |
Entity methods
showCollider(config?, color?) takes a ColliderDebugConfig and an optional color; hideCollider(name?) takes an optional node name. Both walk the entity's node hierarchy, the entity's own node included, for every node carrying a physicsBody, whether the node was freshly built, drawn from the pool, or adopted.
type ColliderDebugConfig = boolean | string | Record<string, string | false>;| Call | Shows or hides |
|---|---|
showCollider() or showCollider(true) | Every physics node on or under the entity's node, default color |
showCollider('#ff0000') | Every physics node on or under the entity's node, in that color |
showCollider('head') | The descendant named head alone, default color, when it carries a physics body |
showCollider('head', '#ff0000') | The descendant named head alone, in that color |
showCollider({ head: '#ff0000', shield: false }) | head in that color; shield and every unnamed node left alone |
showCollider(false) | Nothing |
hideCollider() | Every physics node on or under the entity's node currently shown |
hideCollider('head') | The descendant named head alone |
A string starting with # is a color; any other string is a node name. color applies only to the node-name form. A node name no descendant carries is a no-op, not a throw. Colors are #RRGGBB hex strings; a named CSS color throws inside Color3.FromHexString.
Calling either before the entity's mesh finishes loading walks whatever the hierarchy holds at that moment and finds nothing to show. onMeshLoaded is the first point at which the mesh, and any physics body a behavior attached to it, exists.
const hit = simulation.pickEntityForward(camera, 10);
if(hit !== undefined)
{
hit.entity.showCollider('#00ff00');
}Destroying an entity hides every collider it has shown, poolable or not. A pooled node is detached and hidden rather than disposed on despawn, so without this a collider shown for one entity would reappear on whichever entity claims that node next.
Config-driven: the debugCollider state field
NodeEntityState carries an optional debugCollider, typed the same as showCollider's config argument. When state carries it, showCollider(state.debugCollider) runs at the point construction reaches for the behaviors' onMeshLoaded, just before those hooks: on a fresh spawn, a pooled claim, an adopted node, and a restored entity alike. Left absent, nothing runs.
A level config's SpawnDefinition.debugCollider and EntityDefinition.debugCollider write into that field at spawn, the same way name writes displayName:
name: battlefield
scene: /assets/levels/battlefield.glb
physics: true
spawns:
player_spawn:
entity: PlayerEntity
debugCollider: true
entities:
soldier:
entity: SoldierEntity
debugCollider: '#00ff00'A game can also supply it directly at spawn:
simulation.spawn('CrateEntity', undefined, { debugCollider: { body: '#ff0000' } });Once written, debugCollider is in a snapshot like any other field. A restored entity runs the identical call from the identical state, so visualization follows the entity's own state, not the level that first spawned it.
ColliderDebugManager
The manager lives at engine.debug.colliders and operates on individual TransformNode references. The entity methods delegate to it.
const colliders = engine.debug.colliders;
colliders.showColliderForNode(node);
colliders.showColliderForNode(node, '#ff0000');
colliders.hideColliderForNode(node);
colliders.hideAll();
colliders.isShown(node);
colliders.dispose();showColliderForNode is a no-op on a node with no physicsBody and on a node already shown. The underlying PhysicsViewer is not created until the first show, so there is no cost until something is drawn. Color materials are created once per hex string and reused.
sage constructs one manager for every game. There is no opting out.
import { ColliderDebugManager } from '@skewedaspect/sage';
import type { ColliderDebugConfig } from '@skewedaspect/sage';Related pages
- Debug Console:
sage.entities[id].showCollider()from the browser console - Physics: building physics bodies in a behavior
- Levels: spawn and entity definitions
