Collider Debug Visualization
When physics colliders don't line up with your meshes, you get invisible walls, objects falling through floors, and a lot of confused swearing at your monitor. SAGE wraps BabylonJS's PhysicsViewer into a three-level API that lets you visualize colliders with a single line of code or a single line of YAML.
Three ways to use it
| Approach | Best for |
|---|---|
Config-driven (debugCollider in entity defs / YAML) | Always-on during development |
Entity methods (entity.showCollider()) | Runtime toggling, inspector tools |
Low-level manager (gameEngine.debug.colliders) | Custom debug UIs, fine-grained control |
Most of the time you want the config-driven approach. Drop debugCollider: true into your entity definition and forget about it until you ship.
Config-driven: debugCollider
Add debugCollider to an entity definition, a level spawn, or an entity node config. SAGE applies it automatically when the entity is created during level loading.
In entity definitions (TypeScript)
const soldier : GameEntityDefinition = {
type: 'soldier',
defaultState: { /* ... */ },
behaviors: [ SoldierBehavior ],
debugCollider: true, // all colliders, default color
};Other forms:
debugCollider: '#ff0000' // all colliders in red
debugCollider: { head: '#ff0000', body: '#00ff00' } // per-node colors
debugCollider: { head: '#ff0000', shield: false } // show head, explicitly hide shieldIn level config (YAML)
Level config overrides the entity definition. This means you can leave debugCollider off in your entity definitions and turn it on for specific levels or spawns without touching TypeScript.
name: battlefield
scene: /assets/levels/battlefield.glb
physics: true
# Override for all soldiers in this level
entities:
soldier:
debugCollider: '#00ff00'
# Override for a specific spawn point
spawns:
player_spawn:
entity: player
debugCollider: trueThe priority chain: spawn/entity config in YAML > entity definition debugCollider > nothing.
Entity methods: showCollider() / hideCollider()
These convenience methods on GameEntity find all physics nodes in the entity's node tree and delegate to the debug manager. Useful when you want runtime control — a debug key binding, an inspector panel, etc.
showCollider()
entity.showCollider(); // all colliders, default color
entity.showCollider(true); // same (useful for config-driven patterns)
entity.showCollider('#ff0000'); // all colliders in red
entity.showCollider('head'); // single node by name
entity.showCollider('head', '#ff0000'); // single node by name, in red
entity.showCollider({ // per-node control
head: '#ff0000',
body: '#00ff00',
shield: false,
});The node names (like 'head') correspond to the node names in your scene file. In Blender, that's the object name in the outliner.
hideCollider()
entity.hideCollider(); // hide all
entity.hideCollider('head'); // hide one by nameExample: Toggle colliders with a key binding
let collidersVisible = false;
gameEngine.managers.inputManager.on('action:toggleColliders', () =>
{
collidersVisible = !collidersVisible;
for(const entity of gameEngine.managers.entityManager.getAllEntities())
{
if(collidersVisible)
{
entity.showCollider('#00ff00');
}
else
{
entity.hideCollider();
}
}
});Low-level: ColliderDebugManager
The manager lives at gameEngine.debug.colliders and operates on individual TransformNode references. This is what the entity methods delegate to under the hood.
const debugColliders = gameEngine.debug.colliders;
// Show a node's collider
debugColliders.showColliderForNode(node);
debugColliders.showColliderForNode(node, '#ff0000');
// Hide it
debugColliders.hideColliderForNode(node);
// Hide everything
debugColliders.hideAll();
// Check if a node's collider is currently visible
debugColliders.isShown(node);
// Clean up all debug visualization
debugColliders.dispose();Imports
import { ColliderDebugManager } from '@skewedaspect/sage';
import type { ColliderDebugConfig } from '@skewedaspect/sage';ColliderDebugConfig is the union type accepted by debugCollider and showCollider():
type ColliderDebugConfig = boolean | string | Record<string, string | false>;How it works
A few details worth knowing:
Lazy initialization. The underlying
PhysicsVieweris not created until the first call toshowColliderForNode(). If you never use debug visualization, there is zero overhead.Utility layer rendering. Debug meshes render on a separate
UtilityLayerRenderer, so they don't interfere with raycasts, picking, or your scene's render pipeline.Material caching. Color materials are created once per hex string and reused. Showing 50 colliders in
#ff0000creates one material, not 50.Colors are hex strings. Use
#RRGGBBformat. Named CSS colors (red,green) are not supported — they will be passed directly toColor3.FromHexString()and throw.Node tree walking.
entity.showCollider()recursively finds all nodes with aphysicsBodyin the entity's node hierarchy. This covers compound physics setups where colliders are on child nodes, not the root mesh.
Related pages
- Physics API Reference — Havok integration,
PhysicsAggregate, forces, collisions - Physics Playground guide — hands-on physics example
- Level Loading guide — YAML level config, spawn points, entity definitions
