@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 ​
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 ​
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 ​
| Parameter | Type |
|---|---|
world | World |
entityId | number |
data? | Record<string, unknown> |
Returns ​
ScriptObject
Properties ​
| Property | Modifier | Type | Default value | Description | Defined in |
|---|---|---|---|---|---|
__reluScriptObjectClass | readonly | true | undefined | Static 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 |
data | public | Record<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 |
world | public | World | undefined | The owning ECS world. | ScriptObject.ts:68 |
entityId | public | number | undefined | The entity this script object is bound to. | ScriptObject.ts:71 |
root | public | Object3D<Object3DEventMap> | null | null | The Object3D returned by ScriptObject.build. Assigned by ScriptObjectSystem after build() runs; null until then. | ScriptObject.ts:77 |
Methods ​
build() ​
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()? ​
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 ​
| Parameter | Type |
|---|---|
deltaTime | number |
Returns ​
void
dispose()? ​
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

