Babylon.js Editor

repository·master·Indexed 22 days ago

https://github.com/babylonjs/editor

A desktop application and web application designed to streamline 3D scene creation and editing using the Babylon.js engine. It includes a CLI for packaging projects, a runtime package (babylonjs-editor-tools) for script attachment and scene loading, and templates for Next.js, Nuxt, and SolidJS.

Tokens
39.3K
Snippets
111
Records
162
Agent score
75%

What's inside Babylon.js Editor

  1. Importing modules in automation scripts

    master

    Automation scripts use standard ESM imports. The editor handles module resolution and auto-rewrites @babylonjs/* imports.

    • Babylon.js Core: import { ... } from "babylonjs" (also supports "babylonjs-gui", "babylonjs-loaders", "babylonjs-materials", "babylonjs-post-process", "babylonjs-procedural-textures", "babylonjs-addons").
    • Editor API: import { UniqueNumber } from "babylonjs-editor". This resolves to the running editor instance at runtime.

    Note: The editor injects its own Babylon instance, so objects created in your script automatically share the live scene.

  2. Global Instructions and the #1 Rule for MCP Agents

    master

    When configuring the McpServer via the ServerOptions (2nd argument), you should pass global instructions to guide the agent's behavior.

    The #1 Rule for Agents: Agents must AUTHOR the scene using editor tools (real meshes, materials, instances, lights, cameras, particles, physics) so the user can hand-edit the result.

    Strict Prohibitions:

    • DO NOT clear the scene.
    • DO NOT create empty placeholder meshes.
    • DO NOT generate geometry, materials, lighting, or levels procedurally inside scripts.

    Correct Usage of Scripts: Scripts (create_script, write_script, attach_script) are for runtime BEHAVIOR only (e.g., input handling, game rules, AI, runtime spawning from already-authored assets).

    Best Practices for Agents:

    • Use execute_batch as much as possible to minimize round-trips and UI refreshes.
    • Prefer instances over clones.
    • Use clustered lights.
    • Use cm units / glTF ×100.
    • Place scripts under src/.
    • Use set_mesh_physics for physics.
    • Use set_node_properties for collisions and deep properties.
    • Verify all changes with get_screenshot.
  3. Implement scripts using the `IScript` contract

    master

    Scripts add interactivity to Babylon.js scenes. They are written in TypeScript and can implement up to three optional lifecycle methods. To ensure movement is frame-rate independent, use scene.getAnimationRatio() inside the onUpdate method.

    Supported lifecycle methods:

    • onStart(object?: any): Called once when the script loads and the scene is ready.
    • onUpdate(object?: any): Called every rendered frame.
    • onStop(object?: any): Called when the script is stopped or the attached object is disposed.
    export interface IScript {
        /** Called once when the script loads and the scene is ready. */
        onStart?(object?: any): void;
        /** Called every rendered frame. */
        onUpdate?(object?: any): void;
        /** Called when the script is stopped or the attached object is disposed. */
        onStop?(object?: any): void;
    }
  4. Best practices for automation scripts

    master

    Follow these conventions when writing automation scripts:

    • Units: Use centimeters. (Note: Imported glTF/glb files are automatically scaled by ×100).
    • Scene Integration: Always add objects to editor.layout.preview.scene.
    • Entity IDs: Always set id and uniqueId on every created entity.
    • Performance: Use sourceMesh.createInstance(name) for creating multiple copies instead of cloning. If instancing, set instance.parent = sourceMesh.parent.
    • Lighting: Add non-shadow lights to editor.layout.preview.clusteredLightContainer.addLight(light).
    • UI Updates: After modifying the scene graph, call await editor.layout.graph.refresh() and use editor.layout.graph.setSelectedNode(node) to select a representative node.
    • Safety: Do not clear the scene or delete existing user content unless explicitly requested.
    • Distinction: Do not confuse automation scripts (run in editor to build content) with behavior scripts (run in the final game via create_script/attach_script).
  5. How AdvancedAssetContainer works for sub-scenes

    master

    The AdvancedAssetContainer is an extension of the standard Babylon.js AssetContainer designed for the Babylon.js Editor. Its primary advantage is that scripts attached to contained nodes are automatically re-applied to instantiated or cloned copies.

    When you load a scene as a container using the @sceneAsset(file) decorator, the container is automatically instantiated once and its nodes are added to the main scene by default.

    There are two main ways to use it:

    1. One-shot scenes (e.g., a map): Load the asset and keep the default instance that was automatically added to the scene.
    2. Repeated scenes (e.g., enemies): Call .removeDefault() to remove the auto-added instance, then use .instantiate() to create as many copies as needed on demand.
    import { sceneAsset, AdvancedAssetContainer } from "babylonjs-editor-tools";
    
    export default class Spawner {
        @sceneAsset("enemy.scene")
        private _enemy: AdvancedAssetContainer | null = null;
    
        public constructor(public mesh: Mesh) {}
        // ...
    }
  6. Understand the MCP Tools Contract architecture

    master

    The Babylon.js Editor uses a Model Context Protocol (MCP) architecture to allow AI agents to interact with the editor and manipulate the scene. This system relies on two synchronized implementations:

    1. MCP Server (mcp/): Registers tools and forwards requests to the editor.
    2. Editor Side (editor/src/mcp/): Runs an HTTP server that executes endpoints against the live Babylon.js scene.

    For an AI agent to successfully build games (e.g., composing scenes, managing assets, writing scripts, or optimizing via instancing), both sides must strictly adhere to the endpoint names, input fields, and output shapes defined in the contract.

  7. Understand the babylonjs-editor-tools mental model

    master

    babylonjs-editor-tools is the runtime library used in projects exported from the Babylon.js Editor. It provides a scene loader, a script lifecycle contract, and decorators for interacting with the editor environment.

    Core Concepts

    • Scripts: A script is a class (recommended) or a set of functions attached to a scene object (Mesh, TransformNode, Light, Camera, etc.). The object the script is attached to is passed to the constructor or as the first argument to functions.
    • Decorator Lifecycle: Decorators are processed by the loader after construction. Decorated properties are null or undefined inside the constructor; you must wait until onStart to use them.
    • Class vs Function Scripts: Decorators only work on class-based scripts.
    • Units: The editor uses centimeters. glTF/GLB imports are auto-scaled by $\times 100$.

    Script Lifecycle (IScript)

    MethodCalledDescription
    onStart(object?)OnceWhen the script loads and the scene is ready.
    onUpdate(object?)Every frameUse scene.getAnimationRatio() for frame-rate independence.
    onStop(object?)OnceWhen the script is stopped or the object is disposed.
    import { loadScene, nodeFromScene, visibleAsNumber, onPointerEvent } from "babylonjs-editor-tools";
  8. How inspector decorators work in Babylon.js Editor

    master

    Decorating properties in class-based scripts with @visibleAs* decorators exposes them as editable fields in the editor's inspector. These fields are unique per object and per script, meaning the same script can have different configurations on different objects.

    Values set by a user in the editor are applied to the property at runtime before the onStart lifecycle method is called.

    Every decorator supports:

    • An optional label (defaults to the property name).
    • An optional configuration object which supports a description (shown as a tooltip).
  9. Understand the generated `scriptsMap`

    master

    The editor automatically maintains src/scripts.ts. This file contains a scriptsMap that maps script paths (relative to src) to their modules. This map is used by loadScene to re-attach scripts to objects during scene loading.

    Do not edit src/scripts.ts by hand.

    Each entry in the ScriptMap conforms to the following type:

    export type ScriptMap = Record<
        string,
        { default?: new (object: any) => IScript } & IScript
    >;

    This means a module can provide its logic via a default export (the class) and/or via exported onStart, onUpdate, or onStop functions.

    // src/scripts.ts (generated — do not edit by hand)
    import { loadScene } from "babylonjs-editor-tools";
    import * as scripts_box from "./scripts/box";
    
    export const scriptsMap = {
        "scripts/box.ts": scripts_box,
    };
    
    export { loadScene };
  10. How scene decorators work in Babylon.js Editor

    master

    Scene decorators are used in class-based scripts to link a class property to an object that exists elsewhere in the scene.

    Key Lifecycle Rules:

    • The loader resolves these decorators after the script's constructor runs.
    • Values are available from onStart() onward.
    • Never attempt to access decorated properties inside the constructor.

    These decorators automate the process of finding objects (like scene.getMeshById(...)), making them easier to use within the editor workflow.

  11. Mandatory entity identification for automation scripts

    master

    When creating any entity (mesh, instance, light, camera, transform node, material, etc.) via an automation script, you must manually set both an id and a uniqueId. Failure to do so will break the editor's inspector, selection, and serialization systems.

    Use Tools.RandomId() from babylonjs and UniqueNumber.Get() from babylonjs-editor to satisfy this requirement.

    import { Tools } from "babylonjs";
    import { UniqueNumber } from "babylonjs-editor";
    
    entity.id = Tools.RandomId();
    entity.uniqueId = UniqueNumber.Get();