Skip to content

@relu-interactives/spatial-ecs


@relu-interactives/spatial-ecs / index / ScriptObject

Class: ScriptObject ​

Defined in: ScriptObject.ts:51

Base class for script objects — entities whose THREE.js visual is built entirely by user code (procedural geometry, shaders, particle systems, anything the editor can't author natively).

The critical difference from Behaviour: a ScriptObject runs in the editor AND at runtime — build() and update() execute identically in both. ScriptObjectSystem loads the module, instantiates the class, calls build() to get the visual, attaches it under the entity's Object3D, and ticks update() every frame. Do NOT gate anything on world.isEditorMode() — running everywhere is the whole point. Use Behaviour for gameplay logic that must NOT run in the editor.

Subclasses are resolved by the inherited static marker __reluScriptObjectClass (survives bundler identity splits). Do not override the marker.

Example ​

ts
import { ScriptObject, type ColorInput } from "@relu-interactives/spatial-ecs";
import * as THREE from "three";

export default class GlowOrb extends ScriptObject {
  declare data: { color: ColorInput };
  private mat!: THREE.MeshStandardMaterial;

  build(): THREE.Object3D {
    this.data.color ??= { type: "color", value: "#66ccff" };
    this.mat = new THREE.MeshStandardMaterial({
      color: this.data.color.value,
      emissive: this.data.color.value,
    });
    return new THREE.Mesh(new THREE.SphereGeometry(0.5, 32, 32), this.mat);
  }

  update(dt: number): void {
    this.mat.emissiveIntensity = 0.5 + 0.5 * Math.sin(performance.now() * 0.002);
  }

  dispose(): void {
    this.mat.dispose();
  }
}

Constructors ​

Constructor ​

ts
new ScriptObject(
   world, 
   entityId, 
   data?): ScriptObject;

Defined in: ScriptObject.ts:84

Engine-managed constructor. ScriptObjectSystem instantiates subclasses with the owning world, the entity id, and the component's data. User code should not call this directly.

Parameters ​

ParameterType
worldWorld
entityIdnumber
data?Record<string, unknown>

Returns ​

ScriptObject

Properties ​

PropertyModifierTypeDefault valueDescriptionDefined in
__reluScriptObjectClassreadonlytrueundefinedStatic marker used by ScriptObjectSystem to detect ScriptObject subclasses even when a bundler ships a second copy of the package into the script blob (breaking instanceof). Do NOT override.ScriptObject.ts:57
datapublicRecord<string, unknown>{}The script's data payload (the ScriptObjectComponent.data). Declare a concrete shape on subclasses with declare data: MyShape for type safety. Use the same typed input envelopes as Behaviours (ColorInput, SliderInput, EntityInput, …) to get inspector fields.ScriptObject.ts:65
worldpublicWorldundefinedThe owning ECS world.ScriptObject.ts:68
entityIdpublicnumberundefinedThe entity this script object is bound to.ScriptObject.ts:71
rootpublicObject3D<Object3DEventMap> | nullnullThe Object3D returned by ScriptObject.build. Assigned by ScriptObjectSystem after build() runs; null until then.ScriptObject.ts:77

Methods ​

build() ​

ts
build(): Object3D;

Defined in: ScriptObject.ts:96

REQUIRED — build and return the visual root. Called once by ScriptObjectSystem after construction. The returned object is attached under the entity's Object3D (so it inherits the entity transform, selection and visibility). Subclasses MUST override.

Returns ​

Object3D


update()? ​

ts
optional update(deltaTime): void;

Defined in: ScriptObject.ts:106

OPTIONAL — per-frame hook. Runs in the editor AND at runtime (e.g. advance a shader time uniform, procedural animation). Keep per-frame work on local THREE state / uniforms — do NOT write ECS components every frame (that churns the editor's undo history).

Parameters ​

ParameterType
deltaTimenumber

Returns ​

void


dispose()? ​

ts
optional dispose(): void;

Defined in: ScriptObject.ts:114

OPTIONAL — free any GPU resources this script created (geometries, materials, textures, render targets). Called by ScriptObjectSystem when the object is removed, disabled, or reloaded. THREE dispose() is idempotent, so double-dispose is safe.

Returns ​

void