ScriptBehaviourSystem
Loads user-authored Behaviour scripts via dynamic import(), instantiates their classes, and drives the full Behaviour lifecycle (init, onUpdate, onDestroy) each frame.
Overview
ScriptBehaviourSystem runs every frame and is responsible for:
- Discovering entities with a
ScriptComponentand iterating theirentriesarray. - Dynamically importing each script's compiled URL the first time it is seen (or when the URL changes).
- Resolving the exported
Behavioursubclass by name (or falling back to the default export). - Instantiating the class and calling
init()once. - Calling
onUpdate(deltaTime)on every active script instance each frame. - Detecting stale instances (removed entries, changed URLs, destroyed entities) and calling
onDestroy()before discarding them.
Queried components
| Component | Access |
|---|---|
ScriptComponent | Read (entries, defaultData, runtimeData) |
Behaviour resolution
The system resolves the Behaviour constructor from the imported module using:
- Named export matching
ScriptEntry.className. defaultexport as a fallback.- A
__reluBehaviourClassstatic marker check to handle cases where esbuild bundles a second copy of@relu-interactives/spatial-ecsinto the script blob (breaking theinstanceofidentity check).
If no valid Behaviour subclass is found the entry is skipped and an error is written to the console.
Behavior notes
- Instance keying — each script instance is keyed by
entityId + scriptUrl + className. A new instance is created only when any of these three values changes. - Error isolation — errors thrown inside
init()oronUpdate()are caught per-instance and logged to the console. A failing script does not crash the frame loop or affect other scripts. - Destroyed entity cleanup — when an entity is removed from the world, all of its script instances are destroyed and removed from the registry on the next update.
- Editor vs preview — the system is used in both the editor and preview. By default, scripts are not executed in the editor. Set
executeInEditor: trueon aScriptEntryto opt a script into full lifecycle execution (includinginit()) while the editor is open. Scripts without this flag stay inidlestate in editor mode and never callinit()oronUpdate().
Editor mode execution
When ScriptEntry.executeInEditor is true:
- The script module is loaded and the class is instantiated as normal.
init()is called once (same as preview).onUpdate()is called every frame while the script is enabled.- Direct Three.js scene access is available via
this.world.getScene(),getCamera(), andgetRenderer().
This allows scripts to create geometry, shaders, materials, and any other Three.js construct that the editor's object palette does not expose.
WARNING
Writing to ECS components (e.g. TransformComponent) every frame in editor mode will generate an undo history entry on every tick. Keep per-frame mutations in local Three.js state to avoid flooding the undo stack.
See Running scripts in the editor for usage examples.
API reference
- Class:
ScriptComponent - Base class:
Behaviour - Source:
core/systems/ScriptBehaviourSystem.ts

