@relu-interactives/spatial-ecs 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{System-BjwOBx81.d.cts → System-CYWZ_Zx5.d.cts} +9 -4
- package/dist/{System-BjwOBx81.d.ts → System-CYWZ_Zx5.d.ts} +9 -4
- package/dist/{Video-B8yVI1tF.d.cts → Video-C1IOat7L.d.ts} +38 -9
- package/dist/{Video-B9z5AtIf.d.ts → Video-ppAXaKt0.d.cts} +38 -9
- package/dist/{chunk-HUHN6IFD.js → chunk-52CHDPRQ.js} +118 -92
- package/dist/chunk-52CHDPRQ.js.map +1 -0
- package/dist/{chunk-67MFQDHX.js → chunk-GZ2NKSMV.js} +609 -90
- package/dist/chunk-GZ2NKSMV.js.map +1 -0
- package/dist/{chunk-Z6OMYEXU.js → chunk-JHCPRVBC.js} +34 -5
- package/dist/chunk-JHCPRVBC.js.map +1 -0
- package/dist/{chunk-DY4ETMP2.js → chunk-WKGCC7YL.js} +3 -3
- package/dist/{chunk-DY4ETMP2.js.map → chunk-WKGCC7YL.js.map} +1 -1
- package/dist/components/index.cjs +31 -2
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.cts +3 -3
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +1 -1
- package/dist/docs/guide/authoring-a-behaviour.md +3 -40
- package/dist/docs/guide/authoring-a-script-object.md +102 -0
- package/dist/docs/guide/behaviour-lifecycle.md +4 -4
- package/dist/docs/guide/prefabs.md +24 -5
- package/dist/docs/guide/spawning/script-object.md +36 -0
- package/dist/docs/guide/systems/_sidebar.json +4 -0
- package/dist/docs/guide/systems/script-behaviour-system.md +1 -17
- package/dist/docs/guide/systems/script-object-system.md +44 -0
- package/dist/factories/index.cjs +164 -117
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +5 -3
- package/dist/factories/index.d.ts +5 -3
- package/dist/factories/index.js +3 -3
- package/dist/{index-LHLKpJhZ.d.cts → index-Cu2Pt7wN.d.cts} +33 -14
- package/dist/{index-BBI2YdCu.d.ts → index-DdiWu1b6.d.ts} +33 -14
- package/dist/index.cjs +1132 -302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +219 -32
- package/dist/index.d.ts +219 -32
- package/dist/index.js +254 -11
- package/dist/index.js.map +1 -1
- package/dist/systems/index.cjs +429 -43
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +44 -3
- package/dist/systems/index.d.ts +44 -3
- package/dist/systems/index.js +3 -3
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/worldSnapshotUtils.d.cts +1 -1
- package/dist/worldSnapshotUtils.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-67MFQDHX.js.map +0 -1
- package/dist/chunk-HUHN6IFD.js.map +0 -1
- package/dist/chunk-Z6OMYEXU.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as
|
|
2
|
-
export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-
|
|
3
|
-
export { P as PrefabInstance, a as PrefabNode } from '../index-
|
|
1
|
+
export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as ScriptObjectComponent, X as ScriptObjectComponentInput, Y as ScriptObjectRuntimeState, Z as Selectable, _ as SelectionTransformState, $ as SpriteComponent, a0 as TextureFilteringValue, a1 as TransformModeValue, a2 as TransformSpaceValue, a3 as VideoComponent, a4 as defaultMaterialValue, a5 as normalizePhysicsState } from '../Video-ppAXaKt0.cjs';
|
|
2
|
+
export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-CYWZ_Zx5.cjs';
|
|
3
|
+
export { P as PrefabInstance, a as PrefabNode } from '../index-Cu2Pt7wN.cjs';
|
|
4
4
|
import 'three';
|
|
5
5
|
import '@dimforge/rapier3d-compat';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as
|
|
2
|
-
export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-
|
|
3
|
-
export { P as PrefabInstance, a as PrefabNode } from '../index-
|
|
1
|
+
export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as ScriptObjectComponent, X as ScriptObjectComponentInput, Y as ScriptObjectRuntimeState, Z as Selectable, _ as SelectionTransformState, $ as SpriteComponent, a0 as TextureFilteringValue, a1 as TransformModeValue, a2 as TransformSpaceValue, a3 as VideoComponent, a4 as defaultMaterialValue, a5 as normalizePhysicsState } from '../Video-C1IOat7L.js';
|
|
2
|
+
export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-CYWZ_Zx5.js';
|
|
3
|
+
export { P as PrefabInstance, a as PrefabNode } from '../index-DdiWu1b6.js';
|
|
4
4
|
import 'three';
|
|
5
5
|
import '@dimforge/rapier3d-compat';
|
package/dist/components/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VideoComponent, defaultMaterialValue, normalizePhysicsState } from '../chunk-
|
|
1
|
+
export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, ScriptObjectComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VideoComponent, defaultMaterialValue, normalizePhysicsState } from '../chunk-JHCPRVBC.js';
|
|
2
2
|
import '../chunk-OY7ZODZP.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -122,45 +122,8 @@ See [Accessing Components](./accessing-components) to read and mutate built-in c
|
|
|
122
122
|
|
|
123
123
|
See [Reading Input](./reading-input) for the full keyboard and mouse input API, `KeyboardKeys` reference, and scripting examples.
|
|
124
124
|
|
|
125
|
-
##
|
|
125
|
+
## Behaviours run at preview/runtime only
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
`Behaviour` scripts execute **only** at preview and runtime — never inside the editor. Gameplay logic, input handling, physics, and anything that should not run while authoring belongs in a Behaviour.
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
133
|
-
import * as THREE from "three";
|
|
134
|
-
|
|
135
|
-
export default class GrassPlane extends Behaviour {
|
|
136
|
-
private mesh: THREE.Mesh | null = null;
|
|
137
|
-
|
|
138
|
-
protected init() {
|
|
139
|
-
// Access the live Three.js scene directly.
|
|
140
|
-
const scene = this.world.getScene();
|
|
141
|
-
|
|
142
|
-
const geo = new THREE.PlaneGeometry(10, 10, 32, 32);
|
|
143
|
-
const mat = new THREE.MeshStandardMaterial({ color: 0x228b22 });
|
|
144
|
-
this.mesh = new THREE.Mesh(geo, mat);
|
|
145
|
-
scene.add(this.mesh);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
protected dispose() {
|
|
149
|
-
if (this.mesh) {
|
|
150
|
-
this.world.getScene().remove(this.mesh);
|
|
151
|
-
this.mesh = null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
> [!WARNING]
|
|
158
|
-
> **Undo-history churn** — avoid writing to ECS components (e.g. `TransformComponent`) on every frame when `executeInEditor` is `true`. The editor's change-detection system records a new undo entry each time a component snapshot differs, which will flood the undo stack and cause performance degradation. Keep per-frame mutations in local Three.js state (objects, materials, uniforms) rather than in ECS component fields.
|
|
159
|
-
|
|
160
|
-
Available world accessors in editor mode:
|
|
161
|
-
|
|
162
|
-
| Accessor | Returns |
|
|
163
|
-
| --- | --- |
|
|
164
|
-
| `this.world.getScene()` | `THREE.Scene` |
|
|
165
|
-
| `this.world.getCamera()` | `THREE.PerspectiveCamera` |
|
|
166
|
-
| `this.world.getRenderer()` | `THREE.WebGLRenderer` |
|
|
129
|
+
For a visual that must render and animate **inside the editor** (custom shaders, procedural geometry, grass, particle systems — anything the object palette can't produce), use a [Script Object](./authoring-a-script-object) instead. A `ScriptObject` builds a `THREE.Object3D` that renders in the editor AND at runtime.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Authoring a Script Object
|
|
2
|
+
|
|
3
|
+
A **Script Object** is an entity whose THREE.js visual is built entirely by your code — custom shaders, procedural geometry, particle systems, anything the editor's object palette can't produce. Unlike a [Behaviour](./authoring-a-behaviour), a Script Object runs in the **editor AND at runtime**: `build()` and `update()` execute identically in both, so what you see while authoring is exactly what ships.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> **Do NOT gate anything on `world.isEditorMode()`.** Running everywhere is the whole point of a Script Object. Use a [Behaviour](./authoring-a-behaviour) for gameplay logic that must NOT run in the editor.
|
|
7
|
+
|
|
8
|
+
## The contract
|
|
9
|
+
|
|
10
|
+
Subclass `ScriptObject` and export it (named export matching the class, or default export):
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { ScriptObject, type ColorInput } from "@relu-interactives/spatial-ecs";
|
|
14
|
+
import * as THREE from "three";
|
|
15
|
+
|
|
16
|
+
export default class WavePlane extends ScriptObject {
|
|
17
|
+
// Typed data → inspector fields (same envelopes as Behaviours).
|
|
18
|
+
declare data: { color: ColorInput };
|
|
19
|
+
|
|
20
|
+
private mat!: THREE.ShaderMaterial;
|
|
21
|
+
|
|
22
|
+
// REQUIRED — build and return the visual root.
|
|
23
|
+
build(): THREE.Object3D {
|
|
24
|
+
this.data.color ??= { type: "color", value: "#3aa0ff" };
|
|
25
|
+
this.mat = new THREE.ShaderMaterial({
|
|
26
|
+
uniforms: {
|
|
27
|
+
uTime: { value: 0 },
|
|
28
|
+
uColor: { value: new THREE.Color(this.data.color.value) },
|
|
29
|
+
},
|
|
30
|
+
vertexShader: /* glsl */ `
|
|
31
|
+
uniform float uTime;
|
|
32
|
+
varying float vH;
|
|
33
|
+
void main() {
|
|
34
|
+
vec3 p = position;
|
|
35
|
+
p.z += sin(p.x * 4.0 + uTime) * 0.1;
|
|
36
|
+
vH = p.z;
|
|
37
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
|
|
38
|
+
}`,
|
|
39
|
+
fragmentShader: /* glsl */ `
|
|
40
|
+
uniform vec3 uColor;
|
|
41
|
+
varying float vH;
|
|
42
|
+
void main() { gl_FragColor = vec4(uColor * (0.6 + vH), 1.0); }`,
|
|
43
|
+
});
|
|
44
|
+
const geo = new THREE.PlaneGeometry(2, 2, 64, 64);
|
|
45
|
+
geo.rotateX(-Math.PI / 2);
|
|
46
|
+
return new THREE.Mesh(geo, this.mat);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// OPTIONAL — per-frame, runs in editor AND runtime.
|
|
50
|
+
update(deltaTime: number): void {
|
|
51
|
+
this.mat.uniforms.uTime.value += deltaTime;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// OPTIONAL — free GPU resources you created.
|
|
55
|
+
dispose(): void {
|
|
56
|
+
this.mat.dispose();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`ScriptObjectSystem` calls `build()` once, attaches the returned object under the entity's Object3D (so it inherits the entity's transform, selection, and visibility), then calls `update(deltaTime)` every frame. `dispose()` runs on removal, disable, or hot-reload.
|
|
62
|
+
|
|
63
|
+
## Members available
|
|
64
|
+
|
|
65
|
+
| Member | Type | Notes |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `this.world` | `World` | The owning ECS world. |
|
|
68
|
+
| `this.entityId` | `number` | The entity this script is bound to. |
|
|
69
|
+
| `this.data` | `Record<string, unknown>` | Inspector-bound payload. Declare a shape with `declare data: MyShape`. |
|
|
70
|
+
| `this.root` | `THREE.Object3D \| null` | The object `build()` returned (set after build). |
|
|
71
|
+
|
|
72
|
+
## Data fields (inspector)
|
|
73
|
+
|
|
74
|
+
Type `data` fields with the same input envelopes as Behaviours to get inspector controls:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
declare data: {
|
|
78
|
+
color: ColorInput; // color swatch
|
|
79
|
+
amplitude: SliderInput; // slider
|
|
80
|
+
target: EntityInput; // entity picker
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Read a field's value as `this.data.color.value`. See [Data binding](./behaviour-data-binding/) for the full envelope catalog. Edits made in the inspector are pushed to the live instance's `this.data` between frames.
|
|
85
|
+
|
|
86
|
+
## Rules
|
|
87
|
+
|
|
88
|
+
- **`build()` must return a `THREE.Object3D`** — a `Group`, `Mesh`, `Points`, etc. Returning anything else marks the object as errored.
|
|
89
|
+
- **Do NOT gate on `world.isEditorMode()`.** Build and animate unconditionally.
|
|
90
|
+
- **`dispose()` must free what you allocated** — geometries, materials, textures, render targets. THREE `dispose()` is idempotent, so it's safe to over-dispose. The engine removes the built subtree from the scene for you; freeing GPU memory is yours.
|
|
91
|
+
- **Keep per-frame work in local THREE state / uniforms.** Do NOT write ECS components every frame from `update()` — that churns the editor's undo history.
|
|
92
|
+
- **Pivot at the origin, meters for scale.** The built object rides the entity's transform, so build it centered on `(0,0,0)` at real-world scale.
|
|
93
|
+
- **Imports**: only `three` (and `three/examples/jsm/*`) and `@relu-interactives/spatial-ecs`. Other bare imports won't resolve.
|
|
94
|
+
|
|
95
|
+
## Behaviour vs Script Object
|
|
96
|
+
|
|
97
|
+
| | Behaviour | Script Object |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| Runs in editor | ❌ preview/runtime only | ✅ editor AND runtime |
|
|
100
|
+
| Purpose | gameplay logic, input, physics | visuals: shaders, procedural geometry, particles |
|
|
101
|
+
| Produces | side effects on the world | a `THREE.Object3D` (the entity's visual) |
|
|
102
|
+
| Hooks | `init` / `onUpdate` / `dispose` | `build` / `update` / `dispose` |
|
|
@@ -28,13 +28,13 @@ This ordering means:
|
|
|
28
28
|
|
|
29
29
|
| Hook | Override? | Frequency | Runs in editor? | Purpose |
|
|
30
30
|
| --- | --- | --- | --- | --- |
|
|
31
|
-
| `init()` | Yes (protected) | Once before first `onUpdate` |
|
|
32
|
-
| `onUpdate()` | Yes (protected) | Every frame |
|
|
33
|
-
| `dispose()` | Yes | Once on detach/destroy |
|
|
31
|
+
| `init()` | Yes (protected) | Once before first `onUpdate` | No | Default and validate `data`, cache references |
|
|
32
|
+
| `onUpdate()` | Yes (protected) | Every frame | No | Per-frame logic |
|
|
33
|
+
| `dispose()` | Yes | Once on detach/destroy | No | Release timers, listeners, resources |
|
|
34
34
|
| `onSelect()` / `onDeselect()` | Optional | Editor selection toggles | Yes | UX-only hooks |
|
|
35
35
|
| `onTargetFound()` / `onTargetLost()` | Optional | Image tracking | No | React to AR target visibility |
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Behaviours never run in the editor. For a visual that must render and animate in the editor, use a [Script Object](./authoring-a-script-object).
|
|
38
38
|
|
|
39
39
|
<!-- ## Tips
|
|
40
40
|
|
|
@@ -55,15 +55,34 @@ keep their per-instance values while everything else syncs from the file.
|
|
|
55
55
|
Always per-instance (never synced): the instance **root's** position /
|
|
56
56
|
rotation / scale, name, and place in the scene hierarchy.
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Rules of thumb:
|
|
59
59
|
|
|
60
60
|
- Components or children you *add* to an instance are yours — prefab
|
|
61
61
|
updates never remove them.
|
|
62
62
|
- Components or children you *delete* from an instance come back on the
|
|
63
|
-
next prefab apply (deletion tracking is not
|
|
64
|
-
- "Unpack" in the inspector detaches
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
next prefab apply (deletion tracking is not supported).
|
|
64
|
+
- "Unpack" in the inspector detaches ONE level — the objects lose their
|
|
65
|
+
link but any nested instances inside survive as standalone instances.
|
|
66
|
+
|
|
67
|
+
## Nested prefabs
|
|
68
|
+
|
|
69
|
+
Prefabs can contain other prefab instances: drag a prefab into an open
|
|
70
|
+
prefab (Prefab Mode), or create a prefab from a subtree that contains whole
|
|
71
|
+
instances. Node identity is scoped per placement, so a prefab may nest two
|
|
72
|
+
copies of the same child prefab and each keeps independent overrides.
|
|
73
|
+
|
|
74
|
+
- Editing a child prefab **cascades**: every prefab file that nests it is
|
|
75
|
+
patched (overrides preserved), and every scene instance updates when you
|
|
76
|
+
close the editor — transitively through any depth of nesting.
|
|
77
|
+
- A prefab can never contain itself, directly or through another prefab
|
|
78
|
+
(the editor rejects the drop).
|
|
79
|
+
- Only *whole* instances nest — a partial slice of an instance can't become
|
|
80
|
+
a prefab.
|
|
81
|
+
- In the scene, content can't be re-parented across a nested instance
|
|
82
|
+
boundary (its identity would change); open the host prefab to
|
|
83
|
+
restructure.
|
|
84
|
+
- Runtime spawning is unaffected: files are fully materialized, so
|
|
85
|
+
`spawnPrefab` needs no resolution regardless of nesting depth.
|
|
67
86
|
|
|
68
87
|
## Spawning at runtime
|
|
69
88
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Script Object
|
|
2
|
+
|
|
3
|
+
An entity whose visual is built by a user [`ScriptObject`](../authoring-a-script-object) script (custom shaders, procedural geometry, particles). Renders and animates in the editor AND at runtime. Starts as an empty `THREE.Group`; `ScriptObjectSystem` builds the visual once a script is linked.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
7
|
+
|
|
8
|
+
export default class Spawner extends Behaviour {
|
|
9
|
+
init() {
|
|
10
|
+
const objects = this.world.getObjectManagementSystem();
|
|
11
|
+
objects?.requestCreate("scriptObject", {
|
|
12
|
+
name: "Wave",
|
|
13
|
+
transform: { position: { x: 0, y: 0, z: -3 } },
|
|
14
|
+
options: {
|
|
15
|
+
scriptUrl: "localasset://.../scripts/WavePlane.js",
|
|
16
|
+
className: "WavePlane",
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Payload
|
|
24
|
+
|
|
25
|
+
| Field | Type | Default | Description |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| `name` | `string` | `"Script Object"` | Display name. |
|
|
28
|
+
| `transform` | `TransformData` | identity | Position / rotation (radians) / scale. |
|
|
29
|
+
| `options.scriptUrl` | `string \| null` | `null` | Compiled script URL. Optional — can be assigned later in the inspector. |
|
|
30
|
+
| `options.className` | `string \| null` | `null` | Exported `ScriptObject` class name. |
|
|
31
|
+
| `options.enabled` | `boolean` | `true` | When `false`, the visual isn't built/ticked. |
|
|
32
|
+
| `options.data` | `Record<string, unknown>` | `{}` | Initial inspector data. |
|
|
33
|
+
|
|
34
|
+
## Components attached
|
|
35
|
+
|
|
36
|
+
`Transform`, `Object3DRef`, `Name`, `EntityType`, `ParentId`, `Selectable`, `ScriptObjectComponent`.
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"text": "ScriptBehaviourSystem",
|
|
60
60
|
"link": "/guide/systems/script-behaviour-system"
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
"text": "ScriptObjectSystem",
|
|
64
|
+
"link": "/guide/systems/script-object-system"
|
|
65
|
+
},
|
|
62
66
|
{
|
|
63
67
|
"text": "TransformSyncSystem",
|
|
64
68
|
"link": "/guide/systems/transform-sync-system"
|
|
@@ -34,23 +34,7 @@ If no valid `Behaviour` subclass is found the entry is skipped and an error is w
|
|
|
34
34
|
- **Instance keying** — each script instance is keyed by `entityId + scriptUrl + className`. A new instance is created only when any of these three values changes.
|
|
35
35
|
- **Error isolation** — errors thrown inside `init()` or `onUpdate()` are caught per-instance and logged to the console. A failing script does not crash the frame loop or affect other scripts.
|
|
36
36
|
- **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.
|
|
37
|
-
- **Editor vs preview** —
|
|
38
|
-
|
|
39
|
-
## Editor mode execution
|
|
40
|
-
|
|
41
|
-
When `ScriptEntry.executeInEditor` is `true`:
|
|
42
|
-
|
|
43
|
-
1. The script module is loaded and the class is instantiated as normal.
|
|
44
|
-
2. `init()` is called once (same as preview).
|
|
45
|
-
3. `onUpdate()` is called every frame while the script is enabled.
|
|
46
|
-
4. Direct Three.js scene access is available via `this.world.getScene()`, `getCamera()`, and `getRenderer()`.
|
|
47
|
-
|
|
48
|
-
This allows scripts to create geometry, shaders, materials, and any other Three.js construct that the editor's object palette does not expose.
|
|
49
|
-
|
|
50
|
-
> [!WARNING]
|
|
51
|
-
> 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.
|
|
52
|
-
|
|
53
|
-
See [Running scripts in the editor](../authoring-a-behaviour#running-scripts-in-the-editor) for usage examples.
|
|
37
|
+
- **Editor vs preview** — Behaviours execute **only** at preview/runtime. In an editor world (`isEditorMode()`), every `ScriptComponent` entry stays in `idle` state and `init()`/`onUpdate()` never run. For visuals that must render in the editor, use [`ScriptObjectSystem`](./script-object-system) + [`ScriptObject`](../authoring-a-script-object).
|
|
54
38
|
|
|
55
39
|
## API reference
|
|
56
40
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ScriptObjectSystem
|
|
2
|
+
|
|
3
|
+
Loads, builds, ticks, and disposes [`ScriptObject`](../authoring-a-script-object) scripts attached via `ScriptObjectComponent`. Unlike [`ScriptBehaviourSystem`](./script-behaviour-system), it runs in the **editor AND at runtime** — there is no `isEditorMode()` gating.
|
|
4
|
+
|
|
5
|
+
## Lifecycle
|
|
6
|
+
|
|
7
|
+
For each entity with an enabled `ScriptObjectComponent` that has a `scriptUrl` + `className`:
|
|
8
|
+
|
|
9
|
+
1. **Load** — the module is fetched via the `moduleLoader`, the class resolved (named export → `default` → `__reluScriptObjectClass` marker), and instantiated. `runtimeState` transitions `idle → loading`.
|
|
10
|
+
2. **Build** — `build()` runs; the returned `THREE.Object3D` is tagged (`userData.__scriptObjectRoot`) and attached under the entity's `Object3DRef.object`. `runtimeState → ready`.
|
|
11
|
+
3. **Update** — every frame while enabled and visible, `update(deltaTime)` is called. Inspector data edits are pushed to `this.data` between frames (dirty-checked, not per-frame).
|
|
12
|
+
4. **Dispose** — on removal, disable, script change, or `invalidate*()`, `dispose()` is called and the built subtree is removed and its geometries/materials/textures freed (shared model-cache resources are skipped).
|
|
13
|
+
|
|
14
|
+
`runtimeState` is written **only on transitions** (never every frame). A `build()`/`update()` error sets `runtimeState: "error"` with a sanitized message and tombstones the record so a broken script doesn't reload every frame (cleared by a script/enabled change or `invalidate*()`).
|
|
15
|
+
|
|
16
|
+
## Keying
|
|
17
|
+
|
|
18
|
+
Instances are keyed by **entity id** — a duplicated entity gets its own fresh instance, and two entities sharing one `scriptUrl` get independent instances.
|
|
19
|
+
|
|
20
|
+
## Hot reload
|
|
21
|
+
|
|
22
|
+
- `invalidateAll()` — disposes every live instance so the next `update()` reloads with freshly compiled modules. The editor calls this on its asset-changed epoch.
|
|
23
|
+
- `invalidate(scriptUrl)` — same, scoped to one script.
|
|
24
|
+
|
|
25
|
+
The `moduleLoader` owns cache-busting: with the default dynamic-import loader the URL must change for fresh code to load; `invalidate*()` guarantees re-instantiation.
|
|
26
|
+
|
|
27
|
+
## Wiring
|
|
28
|
+
|
|
29
|
+
Construct it and pass it to `World.setup`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { ScriptObjectSystem } from "@relu-interactives/spatial-ecs";
|
|
33
|
+
|
|
34
|
+
world.setup({
|
|
35
|
+
systems: [new ScriptObjectSystem(myModuleLoader)],
|
|
36
|
+
isEditor: false,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API reference
|
|
41
|
+
|
|
42
|
+
- Class: [`ScriptObjectComponent`](../../api/components/classes/ScriptObjectComponent.md)
|
|
43
|
+
- Base class: [`ScriptObject`](../../api/classes/ScriptObject.md)
|
|
44
|
+
- Source: [`core/systems/ScriptObjectSystem.ts`](https://github.com/Relu-Interactives/relu-spatial-ecs-engine/blob/main/core/systems/ScriptObjectSystem.ts)
|