The Babylon Inspector is powerful out of the box, but it becomes truly indispensable when you bend it to your will. We've covered the basics of getting it running - now let's dive into the juicy stuff: extending it with custom panels, injecting your own debugging data, and creating developer tools that actually solve your specific problems.
Sure, pressing backtick to show/hide the inspector is neat, but that's just the appetizer. But what about hooking into the inspector's lifecycle and adding our own functionality.
src/components/Inspector.ts
export class InspectorSystem extends System {
private inspectorExtensions: Map<string, any> = new Map();
loadEntity(entity: Entity, deltaTime: number) {
const inspectorComponent = entity.getComponent(InspectorComponent);
const { triggerKey, debugLayerId } = inspectorComponent;
window.addEventListener("keydown", (ev) => {
if (ev.code === triggerKey) {
if (scene.debugLayer?.isVisible()) {
scene.debugLayer.hide();
this.cleanupExtensions();
} else {
scene.debugLayer
.show({
overlay: true,
embedMode: true,
globalRoot: document.getElementById(debugLayerId) as HTMLElement,
})
.then(() => {
this.initializeCustomExtensions();
this.setupCameraToggle();
});
}
}
});
}
}
One of the most powerful features is the ability to inject your own debug information directly into the inspector. This is perfect for displaying game state, performance metrics, or any custom data that's relevant to your project.
src/components/Inspector.ts
private initializeCustomExtensions() {
this.scene.meshes.forEach(mesh => {
if (!mesh.metadata) mesh.metadata = {};
mesh.metadata.customDebugInfo = {
entityId: this.getEntityIdForMesh(mesh),
componentCount: this.getComponentCountForMesh(mesh),
lastUpdated: Date.now(),
performance: {
renderTime: this.getLastRenderTime(mesh),
triangleCount: mesh.getTotalVertices() / 3,
memoryUsage: this.estimateMemoryUsage(mesh)
}
};
});
this.addPerformanceTab();
this.addEntitySystemTab();
this.addSceneStatisticsTab();
}
private addPerformanceTab() {
const inspector = this.scene.debugLayer;
if (!inspector) return;
const customPanel = document.createElement('div');
customPanel.id = 'performance-panel';
customPanel.innerHTML = `
<div class="custom-debug-panel">
<h3>Performance Monitor</h3>
<div id="fps-counter">FPS: --</div>
<div id="draw-calls">Draw Calls: --</div>
<div id="active-meshes">Active Meshes: --</div>
<canvas id="performance-graph" width="300" height="100"></canvas>
</div>
`;
const tabContainer = document.querySelector('.inspector-header');
if (tabContainer) {
const customTab = document.createElement('div');
customTab.class = 'tab custom-tab';
customTab.textContent = 'Performance';
customTab.onclick = () => this.showPerformancePanel();
tabContainer.appendChild(customTab);
}
}
Let's create a performance monitor that updates in real-time and displays meaningful data about your scene:
src/components/Inspector.ts
private setupPerformanceMonitoring() {
const performanceData: number[] = [];
const maxDataPoints = 60;
this.scene.registerBeforeRender(() => {
if (!this.scene.debugLayer?.isVisible()) return;
const engine = this.scene.getEngine();
const fps = engine.getFps();
const drawCalls = engine.drawCalls;
const activeMeshes = this.scene.getActiveMeshes().length;
performanceData.push(fps);
if (performanceData.length > maxDataPoints) {
performanceData.shift();
}
const fpsElement = document.getElementById('fps-counter');
const drawCallsElement = document.getElementById('draw-calls');
const activeMeshesElement = document.getElementById('active-meshes');
if (fpsElement) fpsElement.textContent = `FPS: ${fps.toFixed(1)}`;
if (drawCallsElement) drawCallsElement.textContent = `Draw Calls: ${drawCalls}`;
if (activeMeshesElement) activeMeshesElement.textContent = `Active Meshes: ${activeMeshes}`;
this.drawPerformanceGraph(performanceData);
});
}
private drawPerformanceGraph(data: number[]) {
const canvas = document.getElementById('performance-graph') as HTMLCanvasElement;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#333';
ctx.lineWidth = 0.5;
for (let i = 0; i < 5; i++) {
const y = (canvas.height / 4) * i;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
if (data.length > 1) {
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 2;
ctx.beginPath();
const maxFps = Math.max(...data, 60);
data.forEach((fps, index) => {
const x = (canvas.width / data.length) * index;
const y = canvas.height - ((fps / maxFps) * canvas.height);
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
}
}
If you're using an ECS architecture (like in our example), you can create custom inspector panels that show entity and component information:
src/components/Inspector.ts
private addEntitySystemTab() {
const customPanel = document.createElement('div');
customPanel.id = 'ecs-panel';
customPanel.innerHTML = `
<div class="custom-debug-panel">
<h3>Entity Component System</h3>
<div class="ecs-controls">
<button id="refresh-entities">Refresh</button>
<input type="text" id="entity-filter" placeholder="Filter entities...">
</div>
<div id="entity-list"></div>
<div id="component-details"></div>
</div>
`;
setTimeout(() => {
const refreshButton = document.getElementById('refresh-entities');
const filterInput = document.getElementById('entity-filter') as HTMLInputElement;
if (refreshButton) {
refreshButton.onclick = () => this.refreshEntityList();
}
if (filterInput) {
filterInput.oninput = (e) => {
const target = e.target as HTMLInputElement;
this.filterEntities(target.value);
};
}
this.refreshEntityList();
}, 100);
}
private refreshEntityList() {
const entityListElement = document.getElementById('entity-list');
if (!entityListElement) return;
const entities = this.world.entities;
entityListElement.innerHTML = '';
entities.forEach((entity, id) => {
const entityElement = document.createElement('div');
entityElement.class = 'entity-item';
entityElement.innerHTML = `
<div class="entity-header" onclick="toggleEntity('${id}')">
<span class="entity-id">Entity ${id}</span>
<span class="component-count">${entity.components.size} components</span>
</div>
<div class="entity-components" id="components-${id}" style="display: none;">
${Array.from(entity.components.values()).map(component =>
`<div class="component-item">${component.constructor.name}</div>`
).join('')}
</div>
`;
entityListElement.appendChild(entityElement);
});
}
Remember that basic camera toggle we had? Let's make it actually useful by creating a proper debug camera system:
src/components/Inspector.ts
private setupAdvancedCameraControls() {