logo

Babylon.js Market

By Lawrence

7 minutes

Ever wish you could tweak your game parameters in real-time without recompiling? Dat.GUI gives you sliders, dropdowns, and color pickers that connect directly to your component properties. No more guessing what the perfect jump height should be.

Let's build a system that automatically generates GUI controls for every entity and component in your scene.

The DatGui Component: Configuration Central

src/components/DatGui.ts
import * as dat from "dat.gui";
import { Component, Entity, World, System } from "~/lib/ECS";

export interface DatGuiComponentInput {
  enabled?: boolean;
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
  width?: number;
  closed?: boolean;
}

export class DatGuiComponent extends Component {
  public gui: dat.GUI | null = null;
  public enabled: boolean;
  public position: string;
  public width: number;
  public closed: boolean;
  public entityFolders: Map<string, dat.GUI> = new Map();

  constructor(data: DatGuiComponentInput = {}) {
    super(data);
    this.enabled = data.enabled ?? true;
    this.position = data.position || "top-right";
    this.width = data.width || 300;
    this.closed = data.closed ?? false;
  }
}

The DatGuiComponent holds our GUI instance and tracks folders for each entity. Unlike other components that live on individual entities, this one typically sits on your World entity to manage everything globally.

The DatGui System: Auto-Control Generator

src/components/DatGui.ts
export class DatGuiSystem extends System {
  private gui: dat.GUI | null = null;
  private entityFolders: Map<string, dat.GUI> = new Map();
  private componentFolders: Map<string, dat.GUI> = new Map();
  private controls: Map<string, any> = new Map();

  constructor(world: World, componentClasses = [DatGuiComponent]) {
    super(world, componentClasses);
    window.addEventListener("keydown", this.keyDown.bind(this));
  }

  keyDown(event: KeyboardEvent) {
    if (event.key === "4") {
      this.toggleGui();
    }
  }

  toggleGui() {
    if (this.gui) {
      if (this.gui.domElement.style.display === "none") {
        this.gui.domElement.style.display = "block";
      } else {
        this.gui.domElement.style.display = "none";
      }
    }
  }

Press 4 to toggle the entire GUI on and off. The system tracks folders and controls separately so we can organize everything hierarchically.

Initializing the GUI

src/components/DatGui.ts
  loadEntity(entity: Entity) {
    const datGuiComponent = entity.getComponent(DatGuiComponent);
    if (!datGuiComponent || !datGuiComponent.enabled) return;

    // Initialize the main GUI if it doesn't exist
    if (!this.gui) {
      this.gui = new dat.GUI({
        width: datGuiComponent.width,
        closed: datGuiComponent.closed
      });

      // Position the GUI
      this.positionGui(datGuiComponent.position);
      datGuiComponent.gui = this.gui;
    }

    // Create controls for all entities in the world
    this.createEntityControls();
  }

  private positionGui(position: string) {
    if (!this.gui) return;

    const domElement = this.gui.domElement;
    domElement.style.position = "fixed";
    domElement.style.zIndex = "1000";

    switch (position) {
      case "top-left":
        domElement.style.top = "10px";
        domElement.style.left = "10px";
        break;
      case "top-right":

        domElement.style.top = "10px";
        domElement.style.right = "10px";
        break;
      case "bottom-left":
        domElement.style.bottom = "10px";
        domElement.style.left = "10px";
        break;
      case "bottom-right":
        domElement.style.bottom = "10px";
        domElement.style.right = "10px";
        break;
    }
  }

When the DatGuiComponent loads, we create the main GUI instance and position it where requested. The GUI automatically becomes a floating panel that won't interfere with your game.

Auto-Generating Entity Controls

src/components/DatGui.ts
  private createEntityControls() {
    if (!this.gui) return;

    // Clear existing folders
    this.entityFolders.clear();
    this.componentFolders.clear();

    // Iterate through all entities in the world
    for (const [entityName, entity] of this.world.entities) {
      this.createEntityFolder(entityName, entity);
    }
  }

  private createEntityFolder(entityName: string, entity: Entity) {
    if (!this.gui || this.entityFolders.has(entityName)) return;

    // Create folder for this entity
    const entityFolder = this.gui.addFolder(entityName);
    this.entityFolders.set(entityName, entityFolder);

    // Add controls for each component
    for (const [componentType, component] of entity.components) {
      this.createComponentControls(entityFolder, componentType, component, entityName);
    }

    // Start with folders closed to avoid clutter
    entityFolder.close();
  }

For each entity in the world, we create a folder. Then we dive into each component and create controls for their properties. Starting with folders closed keeps the interface manageable.

Smart Component Control Creation

src/components/DatGui.ts
  private createComponentControls(
    entityFolder: dat.GUI,
    componentType: string,
    component: Component,
    entityName: string
  ) {
    const componentKey = `${entityName}_${componentType}`;

    if (this.componentFolders.has(componentKey)) return;

    // Create subfolder for this component
    const componentFolder = entityFolder.addFolder(componentType);
    this.componentFolders.set(componentKey, componentFolder);

    // Add controls for each property
    for (const [key, value] of Object.entries(component)) {
      this.addPropertyControl(componentFolder, component, key, value, componentKey);
    }

    componentFolder.close();
  }

  private addPropertyControl(
    folder: dat.GUI,
    target: any,
    key: string,
    value: any,
    parentKey: string
  ) {
    const controlKey = `${parentKey}_${key}`;

    // Skip private properties, functions, and complex objects
    if (key.startsWith('_') ||
        typeof value === 'function' ||
        value === null ||
        value === undefined ||
        this.isComplexObject(value)) {
      return;
    }

Continue Reading

Unlock the full course to access all content and examples.

$8
↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search