@relu-interactives/spatial-ecs 0.2.12 → 0.2.14
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-CHCucemW.d.cts → System-BeuJ1xt5.d.cts} +291 -2
- package/dist/{System-CHCucemW.d.ts → System-BeuJ1xt5.d.ts} +291 -2
- package/dist/{Video-B9ZKmBIT.d.ts → Video-Cym2C52L.d.ts} +2 -241
- package/dist/{Video-ACTSfWjH.d.cts → Video-D4B-z74P.d.cts} +2 -241
- package/dist/{chunk-PH43YHUT.js → chunk-27RBPOVO.js} +3 -3
- package/dist/{chunk-PH43YHUT.js.map → chunk-27RBPOVO.js.map} +1 -1
- package/dist/{chunk-ZCEMOTH4.js → chunk-7QBONTBW.js} +4 -4
- package/dist/{chunk-ZCEMOTH4.js.map → chunk-7QBONTBW.js.map} +1 -1
- package/dist/{chunk-XPCVEXMY.js → chunk-FTIQQK62.js} +2 -2
- package/dist/chunk-FTIQQK62.js.map +1 -0
- package/dist/{chunk-ON4E5E5O.js → chunk-KDZ6VBRO.js} +4 -4
- package/dist/{chunk-ON4E5E5O.js.map → chunk-KDZ6VBRO.js.map} +1 -1
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.cts +2 -2
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +1 -1
- package/dist/docs/api/Behaviour/classes/Behaviour.md +29 -29
- package/dist/docs/api/Component/classes/Component.md +1 -1
- package/dist/docs/api/components/classes/AnimationComponent.md +26 -26
- package/dist/docs/api/components/classes/AudioComponent.md +19 -19
- package/dist/docs/api/components/classes/CameraComponent.md +14 -14
- package/dist/docs/api/components/classes/EntityType.md +3 -3
- package/dist/docs/api/components/classes/Environment.md +7 -3
- package/dist/docs/api/components/classes/ImageComponent.md +7 -7
- package/dist/docs/api/components/classes/ImageTarget.md +7 -7
- package/dist/docs/api/components/classes/ImageTargetAnchor.md +4 -4
- package/dist/docs/api/components/classes/InstanceSlotComponent.md +4 -4
- package/dist/docs/api/components/classes/InstancedMeshComponent.md +10 -10
- package/dist/docs/api/components/classes/LightComponent.md +6 -6
- package/dist/docs/api/components/classes/MaterialComponent.md +11 -11
- package/dist/docs/api/components/classes/MeshComponent.md +3 -3
- package/dist/docs/api/components/classes/MeshGeometryComponent.md +9 -9
- package/dist/docs/api/components/classes/ModelComponent.md +4 -4
- package/dist/docs/api/components/classes/Name.md +3 -3
- package/dist/docs/api/components/classes/Object3DRef.md +4 -4
- package/dist/docs/api/components/classes/ParentId.md +3 -3
- package/dist/docs/api/components/classes/PhysicsComponent.md +6 -6
- package/dist/docs/api/components/classes/Postprocessing.md +7 -3
- package/dist/docs/api/components/classes/ScriptComponent.md +5 -5
- package/dist/docs/api/components/classes/Selectable.md +3 -3
- package/dist/docs/api/components/classes/SelectionTransformState.md +5 -5
- package/dist/docs/api/components/classes/SpriteComponent.md +7 -7
- package/dist/docs/api/components/classes/TransformComponent.md +12 -12
- package/dist/docs/api/components/classes/VideoComponent.md +28 -28
- package/dist/docs/api/components/functions/normalizePhysicsState.md +1 -1
- package/dist/docs/api/components/index.md +1 -1
- package/dist/docs/api/components/type-aliases/AnimationComponentOptions.md +9 -9
- package/dist/docs/api/components/type-aliases/CameraComponentOptions.md +13 -13
- package/dist/docs/api/components/type-aliases/CameraProjection.md +1 -1
- package/dist/docs/api/components/type-aliases/CameraRuntimeType.md +1 -1
- package/dist/docs/api/components/type-aliases/ColliderShape.md +1 -1
- package/dist/docs/api/components/type-aliases/ColliderState.md +20 -20
- package/dist/docs/api/components/type-aliases/CubeMeshParameters.md +4 -4
- package/dist/docs/api/components/type-aliases/EditableMeshObjectKind.md +1 -1
- package/dist/docs/api/components/type-aliases/EnvironmentFogState.md +8 -8
- package/dist/docs/api/components/type-aliases/EnvironmentFogType.md +1 -1
- package/dist/docs/api/components/type-aliases/EnvironmentGradientEffect.md +1 -1
- package/dist/docs/api/components/type-aliases/EnvironmentSkyboxState.md +7 -7
- package/dist/docs/api/components/type-aliases/EnvironmentSkyboxType.md +1 -1
- package/dist/docs/api/components/type-aliases/EnvironmentState.md +5 -5
- package/dist/docs/api/components/type-aliases/ImageTargetAnchorOptions.md +3 -3
- package/dist/docs/api/components/type-aliases/ImageTargetReference.md +7 -7
- package/dist/docs/api/components/type-aliases/InstanceTransform.md +13 -13
- package/dist/docs/api/components/type-aliases/InstancedMeshKind.md +1 -1
- package/dist/docs/api/components/type-aliases/LightValueState.md +11 -11
- package/dist/docs/api/components/type-aliases/MaterialTypeValue.md +1 -1
- package/dist/docs/api/components/type-aliases/MaterialValueState.md +28 -28
- package/dist/docs/api/components/type-aliases/MeshGeometryParameters.md +1 -1
- package/dist/docs/api/components/type-aliases/MeshGeometryParametersByKind.md +7 -7
- package/dist/docs/api/components/type-aliases/PhysicsState.md +3 -3
- package/dist/docs/api/components/type-aliases/PlaneMeshParameters.md +3 -3
- package/dist/docs/api/components/type-aliases/PostprocessingState.md +62 -62
- package/dist/docs/api/components/type-aliases/RadiusHeightMeshParameters.md +3 -3
- package/dist/docs/api/components/type-aliases/RadiusMeshParameters.md +2 -2
- package/dist/docs/api/components/type-aliases/RigidbodyState.md +17 -17
- package/dist/docs/api/components/type-aliases/RigidbodyType.md +1 -1
- package/dist/docs/api/components/type-aliases/ScriptEntry.md +9 -9
- package/dist/docs/api/components/type-aliases/TextureFilteringValue.md +1 -1
- package/dist/docs/api/components/type-aliases/TransformData.md +4 -4
- package/dist/docs/api/components/type-aliases/TransformModeValue.md +1 -1
- package/dist/docs/api/components/type-aliases/TransformSpaceValue.md +1 -1
- package/dist/docs/api/components/type-aliases/Vec3.md +4 -4
- package/dist/docs/api/components/variables/DEFAULT_COLLIDER_STATE.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_ENVIRONMENT_STATE.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_INSTANCED_MESH_PARAMETERS.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_INSTANCE_TRANSFORM.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_MESH_GEOMETRY_PARAMETERS.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_PHYSICS_STATE.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_POSTPROCESSING_STATE.md +1 -1
- package/dist/docs/api/components/variables/DEFAULT_RIGIDBODY_STATE.md +1 -1
- package/dist/docs/api/index/classes/AnimationStateMachine.md +16 -16
- package/dist/docs/api/index/classes/AnimationStateManager.md +13 -13
- package/dist/docs/api/index/classes/AnimationSystem.md +3 -3
- package/dist/docs/api/index/classes/AudioSystem.md +4 -4
- package/dist/docs/api/index/classes/CameraSyncSystem.md +2 -2
- package/dist/docs/api/index/classes/EnvironmentSyncSystem.md +4 -4
- package/dist/docs/api/index/classes/InputSystem.md +33 -33
- package/dist/docs/api/index/classes/InstancedMeshSystem.md +2 -2
- package/dist/docs/api/index/classes/LightSyncSystem.md +2 -2
- package/dist/docs/api/index/classes/MaterialSyncSystem.md +3 -3
- package/dist/docs/api/index/classes/MeshGeometrySystem.md +2 -2
- package/dist/docs/api/index/classes/ObjectManagementSystem.md +14 -14
- package/dist/docs/api/index/classes/ObjectVisibilitySystem.md +2 -2
- package/dist/docs/api/index/classes/PhysicsSyncSystem.md +4 -4
- package/dist/docs/api/index/classes/PostprocessingSyncSystem.md +4 -4
- package/dist/docs/api/index/classes/RenderSystem.md +3 -3
- package/dist/docs/api/index/classes/ScriptBehaviourSystem.md +4 -4
- package/dist/docs/api/index/classes/TransformSyncSystem.md +2 -2
- package/dist/docs/api/index/classes/VideoSystem.md +4 -4
- package/dist/docs/api/index/classes/World.md +186 -82
- package/dist/docs/api/index/functions/createAnimationComponent.md +1 -1
- package/dist/docs/api/index/functions/createAudioComponent.md +1 -1
- package/dist/docs/api/index/functions/createCameraComponent.md +1 -1
- package/dist/docs/api/index/functions/createEntityTypeComponent.md +1 -1
- package/dist/docs/api/index/functions/createEnvironmentComponent.md +1 -1
- package/dist/docs/api/index/functions/createImageComponent.md +1 -1
- package/dist/docs/api/index/functions/createImageTargetAnchorComponent.md +1 -1
- package/dist/docs/api/index/functions/createImageTargetComponent.md +1 -1
- package/dist/docs/api/index/functions/createInstanceSlotComponent.md +1 -1
- package/dist/docs/api/index/functions/createInstancedMeshComponent.md +1 -1
- package/dist/docs/api/index/functions/createLightComponent.md +1 -1
- package/dist/docs/api/index/functions/createMaterialComponent.md +1 -1
- package/dist/docs/api/index/functions/createMeshComponent.md +1 -1
- package/dist/docs/api/index/functions/createMeshGeometryComponent.md +1 -1
- package/dist/docs/api/index/functions/createModelComponent.md +1 -1
- package/dist/docs/api/index/functions/createNameComponent.md +1 -1
- package/dist/docs/api/index/functions/createObject3DRefComponent.md +1 -1
- package/dist/docs/api/index/functions/createParentIdComponent.md +1 -1
- package/dist/docs/api/index/functions/createPhysicsComponent.md +1 -1
- package/dist/docs/api/index/functions/createPostprocessingComponent.md +1 -1
- package/dist/docs/api/index/functions/createScriptComponent.md +1 -1
- package/dist/docs/api/index/functions/createSelectableComponent.md +1 -1
- package/dist/docs/api/index/functions/createSelectionTransformStateComponent.md +1 -1
- package/dist/docs/api/index/functions/createSpriteComponent.md +1 -1
- package/dist/docs/api/index/functions/createTransformComponent.md +1 -1
- package/dist/docs/api/index/functions/createVideoComponent.md +1 -1
- package/dist/docs/api/index/functions/hydrateWorldFromSnapshot.md +1 -1
- package/dist/docs/api/index/functions/hydrateWorldFromSnapshotDetailed.md +1 -1
- package/dist/docs/api/index/functions/registerDefaultComponentFactories.md +1 -1
- package/dist/docs/api/index/functions/registerDefaultFactories.md +1 -1
- package/dist/docs/api/index/interfaces/EcsSystem.md +3 -3
- package/dist/docs/api/index/interfaces/StateMachine.md +13 -13
- package/dist/docs/api/index/type-aliases/AnimationStateCallbacks.md +4 -4
- package/dist/docs/api/index/type-aliases/ComponentFactory.md +1 -1
- package/dist/docs/api/index/type-aliases/EcsComponentFactoryKind.md +1 -1
- package/dist/docs/api/index/type-aliases/EcsWorldComponentSnapshot.md +3 -3
- package/dist/docs/api/index/type-aliases/EcsWorldStateSnapshotLike.md +4 -4
- package/dist/docs/api/index/type-aliases/HydrateWorldFromSnapshotResult.md +3 -3
- package/dist/docs/api/index/type-aliases/KeyboardKey.md +1 -1
- package/dist/docs/api/index/type-aliases/MouseButton.md +1 -1
- package/dist/docs/api/index/type-aliases/SelectionContext.md +4 -4
- package/dist/docs/api/index/variables/HYDRATABLE_ENTITY_KINDS.md +1 -1
- package/dist/docs/api/index/variables/KeyboardKeys.md +89 -89
- package/dist/docs/api/types/type-aliases/AssetInput.md +3 -3
- package/dist/docs/api/types/type-aliases/CheckboxInput.md +12 -2
- package/dist/docs/api/types/type-aliases/ColorInput.md +1 -1
- package/dist/docs/api/types/type-aliases/ComponentInput.md +4 -4
- package/dist/docs/api/types/type-aliases/ComponentKind.md +1 -1
- package/dist/docs/api/types/type-aliases/DropdownInput.md +3 -3
- package/dist/docs/api/types/type-aliases/DropdownOption.md +3 -3
- package/dist/docs/api/types/type-aliases/EcsEntityEditorKind.md +1 -1
- package/dist/docs/api/types/type-aliases/EcsEntityKind.md +1 -1
- package/dist/docs/api/types/type-aliases/EntityId.md +1 -1
- package/dist/docs/api/types/type-aliases/EntityInput.md +3 -3
- package/dist/docs/api/types/type-aliases/FloatInput.md +1 -1
- package/dist/docs/api/types/type-aliases/IntersectionResult.md +3 -3
- package/dist/docs/api/types/type-aliases/ParentLookup.md +3 -3
- package/dist/docs/api/types/type-aliases/SelectionListener.md +1 -1
- package/dist/docs/api/types/type-aliases/SliderInput.md +5 -5
- package/dist/docs/api/types/type-aliases/TextInput.md +1 -1
- package/dist/docs/api/types/type-aliases/ToggleInput.md +1 -1
- package/dist/docs/api/types/type-aliases/Vec3.md +1 -1
- package/dist/docs/api/types/type-aliases/Vector2Input.md +3 -3
- package/dist/docs/api/types/type-aliases/Vector3Input.md +4 -4
- package/dist/docs/api/types/type-aliases/WorldEntityView.md +1 -1
- package/dist/docs/guide/accessing-components.md +135 -1
- package/dist/docs/guide/authoring-a-behaviour.md +1 -1
- package/dist/docs/guide/behaviour-data-binding/asset-input.md +54 -0
- package/dist/docs/guide/behaviour-data-binding/checkbox-input.md +38 -0
- package/dist/docs/guide/behaviour-data-binding/color-input.md +38 -0
- package/dist/docs/guide/behaviour-data-binding/component-input.md +79 -0
- package/dist/docs/guide/behaviour-data-binding/dropdown-input.md +53 -0
- package/dist/docs/guide/behaviour-data-binding/entity-input.md +100 -0
- package/dist/docs/guide/behaviour-data-binding/float-input.md +35 -0
- package/dist/docs/guide/behaviour-data-binding/index.md +158 -0
- package/dist/docs/guide/behaviour-data-binding/slider-input.md +37 -0
- package/dist/docs/guide/behaviour-data-binding/text-input.md +35 -0
- package/dist/docs/guide/behaviour-data-binding/toggle-input.md +36 -0
- package/dist/docs/guide/behaviour-data-binding/vector2-input.md +36 -0
- package/dist/docs/guide/behaviour-data-binding/vector3-input.md +41 -0
- package/dist/docs/guide/components/parent-id.md +25 -0
- package/dist/docs/guide/runtime-entity-management.md +302 -0
- package/dist/docs/guide/scene/environment.md +5 -6
- package/dist/docs/guide/scene/index.md +5 -10
- package/dist/docs/guide/scene/postprocessing.md +5 -6
- package/dist/docs/guide/systems/object-management-system.md +61 -0
- package/dist/docs/guide/world-api.md +37 -1
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +2 -2
- package/dist/factories/index.d.ts +2 -2
- package/dist/factories/index.js +3 -3
- package/dist/{index-B5JcGMJ5.d.ts → index-BfoOC2CG.d.cts} +2 -2
- package/dist/{index-kx5O2mVU.d.cts → index-BoJ3_6a3.d.ts} +2 -2
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +73 -8
- package/dist/index.js.map +1 -1
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +2 -2
- package/dist/systems/index.d.ts +2 -2
- 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-XPCVEXMY.js.map +0 -1
- package/dist/docs/guide/inspector-data-binding.md +0 -367
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Behaviour Data Binding
|
|
2
|
+
|
|
3
|
+
The `ScriptComponent.data` field is the bridge between the editor inspector and your `Behaviour` subclass. Anything you place on `data` is:
|
|
4
|
+
|
|
5
|
+
1. Saved with the project.
|
|
6
|
+
2. Rendered as a live inspector control in the editor.
|
|
7
|
+
3. Available at runtime as `this.data` inside the script.
|
|
8
|
+
|
|
9
|
+
The **type annotation** on each field tells the editor which control to render. Import the input types from `@relu-interactives/spatial-ecs` and use them as `as` casts on the initial values in your data object.
|
|
10
|
+
|
|
11
|
+
## Input types
|
|
12
|
+
|
|
13
|
+
| Type | Inspector control | Runtime value |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| [`FloatInput`](./float-input) | Number input | `number` |
|
|
16
|
+
| [`TextInput`](./text-input) | Text input | `string` |
|
|
17
|
+
| [`ColorInput`](./color-input) | Color picker | `string` (hex, e.g. `"#ff0000"`) |
|
|
18
|
+
| [`Vector2Input`](./vector2-input) | XY number pair | `{ x: number; y: number }` |
|
|
19
|
+
| [`Vector3Input`](./vector3-input) | XYZ number triple | `{ x: number; y: number; z: number }` |
|
|
20
|
+
| [`SliderInput`](./slider-input) | Range slider | `{ min, max, step, value }` — read `field.value` at runtime |
|
|
21
|
+
| [`DropdownInput`](./dropdown-input) | Select dropdown | `{ options: DropdownOption[]; value: string }` — read `field.value` |
|
|
22
|
+
| [`CheckboxInput`](./checkbox-input) | Checkbox | `{ type: "checkbox"; value: boolean }` — read `.value` |
|
|
23
|
+
| [`ToggleInput`](./toggle-input) | Toggle switch | `boolean` |
|
|
24
|
+
| [`AssetInput`](./asset-input) | Asset file picker | `{ path: string; type: string }` — read `field.path` |
|
|
25
|
+
| [`EntityInput`](./entity-input) | Entity picker | `{ type: "entity"; value: WorldEntityView \| null }` — read `field.value` |
|
|
26
|
+
| [`ComponentInput`](./component-input) | Entity + component picker | `{ type: "component"; componentKind?: ComponentKind; value: Component \| null }` — read `field.value` |
|
|
27
|
+
|
|
28
|
+
## Declaring inspector fields
|
|
29
|
+
|
|
30
|
+
Import the input types you need alongside `Behaviour`, then declare them directly on the `data` class property using `as` casts:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
Behaviour,
|
|
35
|
+
type FloatInput,
|
|
36
|
+
type TextInput,
|
|
37
|
+
type ColorInput,
|
|
38
|
+
type Vector2Input,
|
|
39
|
+
type Vector3Input,
|
|
40
|
+
type SliderInput,
|
|
41
|
+
type DropdownInput,
|
|
42
|
+
type CheckboxInput,
|
|
43
|
+
type ToggleInput,
|
|
44
|
+
type AssetInput,
|
|
45
|
+
type EntityInput,
|
|
46
|
+
type ComponentInput,
|
|
47
|
+
} from "@relu-interactives/spatial-ecs";
|
|
48
|
+
|
|
49
|
+
export default class Demo extends Behaviour {
|
|
50
|
+
data = {
|
|
51
|
+
speed: 1 as FloatInput,
|
|
52
|
+
label: "Hello" as TextInput,
|
|
53
|
+
tint: "#ffffff" as ColorInput,
|
|
54
|
+
offset: { x: 0, y: 0 } as Vector2Input,
|
|
55
|
+
origin: { x: 0, y: 0, z: 0 } as Vector3Input,
|
|
56
|
+
range: { min: 0, max: 10, step: 0.1, value: 5 } as SliderInput,
|
|
57
|
+
axis: {
|
|
58
|
+
value: "y",
|
|
59
|
+
options: [
|
|
60
|
+
{ label: "X", value: "x" },
|
|
61
|
+
{ label: "Y", value: "y" },
|
|
62
|
+
{ label: "Z", value: "z" },
|
|
63
|
+
],
|
|
64
|
+
} as DropdownInput,
|
|
65
|
+
active: true as ToggleInput,
|
|
66
|
+
visible: { type: "checkbox", value: true } as CheckboxInput,
|
|
67
|
+
texture: { path: "", type: "image" } as AssetInput,
|
|
68
|
+
target: { type: "entity", value: null } as EntityInput,
|
|
69
|
+
camera: { type: "component", componentKind: "Camera", value: null } as ComponentInput,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Each field in `data` maps directly to a live inspector control. The script above produces the following inspector panel:
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
## Reading values at runtime
|
|
79
|
+
|
|
80
|
+
Most types map directly to their underlying primitive. The exceptions are `SliderInput`, `DropdownInput`, `AssetInput`, `EntityInput`, and `ComponentInput` — read the nested value:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
onUpdate() {
|
|
84
|
+
// Primitives — use directly
|
|
85
|
+
this.transform.rotation.y += this.data.speed * this.deltaTime;
|
|
86
|
+
|
|
87
|
+
// SliderInput → read .value
|
|
88
|
+
const volume = this.data.range.value;
|
|
89
|
+
|
|
90
|
+
// DropdownInput → read .value
|
|
91
|
+
const axis = this.data.axis.value as "x" | "y" | "z";
|
|
92
|
+
|
|
93
|
+
// AssetInput → read .path
|
|
94
|
+
const src = this.data.texture.path;
|
|
95
|
+
|
|
96
|
+
// EntityInput → cast to WorldEntityView | null
|
|
97
|
+
const entity = this.data.target.value as WorldEntityView | null;
|
|
98
|
+
|
|
99
|
+
// ComponentInput → cast to the concrete component type
|
|
100
|
+
const cam = this.data.camera.value as CameraComponent | null;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Practical example — spinner with inspector controls
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import {
|
|
108
|
+
Behaviour,
|
|
109
|
+
type FloatInput,
|
|
110
|
+
type ColorInput,
|
|
111
|
+
type DropdownInput,
|
|
112
|
+
type ToggleInput,
|
|
113
|
+
} from "@relu-interactives/spatial-ecs";
|
|
114
|
+
|
|
115
|
+
export default class Spinner extends Behaviour {
|
|
116
|
+
data = {
|
|
117
|
+
speed: 1 as FloatInput,
|
|
118
|
+
color: "#00aaff" as ColorInput,
|
|
119
|
+
enabled: true as ToggleInput,
|
|
120
|
+
axis: {
|
|
121
|
+
value: "y",
|
|
122
|
+
options: [
|
|
123
|
+
{ label: "X", value: "x" },
|
|
124
|
+
{ label: "Y", value: "y" },
|
|
125
|
+
{ label: "Z", value: "z" },
|
|
126
|
+
],
|
|
127
|
+
} as DropdownInput,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
onUpdate() {
|
|
131
|
+
if (!this.data.enabled) return;
|
|
132
|
+
const axis = this.data.axis.value as "x" | "y" | "z";
|
|
133
|
+
this.transform.rotation[axis] += this.data.speed * this.deltaTime;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Defaulting fields
|
|
139
|
+
|
|
140
|
+
Defaults are set inline on the `data` property. When a saved project reloads, saved values take precedence — the inline defaults only apply when a field has never been saved. This means:
|
|
141
|
+
|
|
142
|
+
- Adding a new field with an inline default is safe — existing saves will pick it up on next load.
|
|
143
|
+
- The inspector always has a valid initial value when the script is first attached.
|
|
144
|
+
- For `SliderInput` and `DropdownInput`, the full object (including `options`) must be present in the inline default — the editor reads `options` to build the control.
|
|
145
|
+
|
|
146
|
+
## Persistence
|
|
147
|
+
|
|
148
|
+
`ScriptComponent.data` is serialized and saved with the project. On reload, your `data` payload is restored verbatim. If you rename or restructure a field, migrate it inside `init()`:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
init() {
|
|
152
|
+
// migrate renamed field
|
|
153
|
+
if ((this.data as any).oldSpeed !== undefined) {
|
|
154
|
+
this.data.speed = (this.data as any).oldSpeed;
|
|
155
|
+
delete (this.data as any).oldSpeed;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SliderInput
|
|
2
|
+
|
|
3
|
+
A range slider. The inspector renders a horizontal slider with the current value displayed alongside it.
|
|
4
|
+
|
|
5
|
+
**Runtime value:** `{ min: number; max: number; step: number; value: number }` — read `field.value`
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
Provide the full object including `min`, `max`, `step`, and the initial `value`. The editor reads `min`/`max`/`step` to configure the slider control.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Behaviour, type SliderInput } from "@relu-interactives/spatial-ecs";
|
|
15
|
+
|
|
16
|
+
export default class Demo extends Behaviour {
|
|
17
|
+
data = {
|
|
18
|
+
volume: { min: 0, max: 1, step: 0.01, value: 0.8 } as SliderInput,
|
|
19
|
+
quality: { min: 1, max: 10, step: 1, value: 5 } as SliderInput,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Runtime usage
|
|
25
|
+
|
|
26
|
+
Read the `.value` property — the other fields (`min`, `max`, `step`) are for the editor control only:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
onUpdate() {
|
|
30
|
+
const volume = this.data.volume.value; // number between 0 and 1
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Notes
|
|
35
|
+
|
|
36
|
+
- Always include `min`, `max`, and `step` in the inline default — the editor needs them to render the control.
|
|
37
|
+
- `step` can be fractional for smooth sliders (e.g. `0.01`).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# TextInput
|
|
2
|
+
|
|
3
|
+
A single-line text field. The inspector renders a plain text input.
|
|
4
|
+
|
|
5
|
+
**Runtime value:** `string`
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Behaviour, type TextInput } from "@relu-interactives/spatial-ecs";
|
|
13
|
+
|
|
14
|
+
export default class Demo extends Behaviour {
|
|
15
|
+
data = {
|
|
16
|
+
label: "Hello world" as TextInput,
|
|
17
|
+
apiKey: "" as TextInput,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Runtime usage
|
|
23
|
+
|
|
24
|
+
Use the value directly as a `string`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
init() {
|
|
28
|
+
console.log(this.data.label); // "Hello world"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
- The default value is what the user sees when the script is first attached.
|
|
35
|
+
- Useful for URLs, display strings, or any freeform text parameter.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# ToggleInput
|
|
2
|
+
|
|
3
|
+
An on/off toggle switch. The inspector renders a pill-shaped toggle.
|
|
4
|
+
|
|
5
|
+
**Runtime value:** `boolean`
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Behaviour, type ToggleInput } from "@relu-interactives/spatial-ecs";
|
|
13
|
+
|
|
14
|
+
export default class Demo extends Behaviour {
|
|
15
|
+
data = {
|
|
16
|
+
enabled: true as ToggleInput,
|
|
17
|
+
loopAudio: false as ToggleInput,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Runtime usage
|
|
23
|
+
|
|
24
|
+
Use the value directly as a `boolean`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
onUpdate() {
|
|
28
|
+
if (!this.data.enabled) return;
|
|
29
|
+
// ... rest of update logic
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- Prefer `ToggleInput` for feature flags (e.g. "Enabled", "Loop") and [`CheckboxInput`](./checkbox-input) for property-style options.
|
|
36
|
+
- Both types resolve to a plain `boolean` at runtime.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Vector2Input
|
|
2
|
+
|
|
3
|
+
An XY number pair. The inspector renders two side-by-side number inputs labelled **X** and **Y**.
|
|
4
|
+
|
|
5
|
+
**Runtime value:** `{ x: number; y: number }`
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Behaviour, type Vector2Input } from "@relu-interactives/spatial-ecs";
|
|
13
|
+
|
|
14
|
+
export default class Demo extends Behaviour {
|
|
15
|
+
data = {
|
|
16
|
+
offset: { x: 0, y: 0 } as Vector2Input,
|
|
17
|
+
uvScale: { x: 1, y: 1 } as Vector2Input,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Runtime usage
|
|
23
|
+
|
|
24
|
+
Read `x` and `y` directly:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
onUpdate() {
|
|
28
|
+
this.transform.position.x += this.data.offset.x * this.deltaTime;
|
|
29
|
+
this.transform.position.y += this.data.offset.y * this.deltaTime;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- Suitable for screen-space positions, UV offsets, or any 2D vector parameter.
|
|
36
|
+
- Use [`Vector3Input`](./vector3-input) for 3D world-space values.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Vector3Input
|
|
2
|
+
|
|
3
|
+
An XYZ number triple. The inspector renders three side-by-side number inputs labelled **X**, **Y**, and **Z**.
|
|
4
|
+
|
|
5
|
+
**Runtime value:** `{ x: number; y: number; z: number }`
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Behaviour, type Vector3Input } from "@relu-interactives/spatial-ecs";
|
|
13
|
+
|
|
14
|
+
export default class Demo extends Behaviour {
|
|
15
|
+
data = {
|
|
16
|
+
spawnPoint: { x: 0, y: 1, z: 0 } as Vector3Input,
|
|
17
|
+
force: { x: 0, y: 0, z: 0 } as Vector3Input,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Runtime usage
|
|
23
|
+
|
|
24
|
+
Read `x`, `y`, and `z` directly:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import * as THREE from "three";
|
|
28
|
+
|
|
29
|
+
init() {
|
|
30
|
+
const point = new THREE.Vector3(
|
|
31
|
+
this.data.spawnPoint.x,
|
|
32
|
+
this.data.spawnPoint.y,
|
|
33
|
+
this.data.spawnPoint.z,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- Ideal for world-space positions, directions, or scale factors.
|
|
41
|
+
- Values are plain numbers — no automatic unit conversion is applied.
|
|
@@ -103,6 +103,31 @@ export default class Example extends Behaviour {
|
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
### Navigate the hierarchy with World methods
|
|
107
|
+
|
|
108
|
+
Instead of querying the `ParentId` component directly, use `world.getEntitiesParent` and `world.getEntitiesChildren` for convenient hierarchy traversal:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
112
|
+
|
|
113
|
+
export default class HierarchyLogger extends Behaviour {
|
|
114
|
+
protected init() {
|
|
115
|
+
// Get parent entity
|
|
116
|
+
const parent = this.world.getEntitiesParent(this.entityId);
|
|
117
|
+
if (parent) {
|
|
118
|
+
console.log(`Parent: ${parent.name} (ID: ${parent.entityId})`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get all children
|
|
122
|
+
const children = this.world.getEntitiesChildren(this.entityId);
|
|
123
|
+
console.log(`${children.length} child(ren):`);
|
|
124
|
+
for (const child of children) {
|
|
125
|
+
console.log(` - ${child.name} (ID: ${child.entityId})`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
106
131
|
::: tip ObjectManagementSystem
|
|
107
132
|
Do not call `THREE.Object3D.add` / `remove` directly to reparent. Write to `ParentId.value` instead — the `ObjectManagementSystem` reads this every frame and reconciles the scene graph. This keeps the ECS hierarchy state consistent with the three.js scene graph.
|
|
108
133
|
:::
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Runtime Entity Management
|
|
2
|
+
|
|
3
|
+
From a `Behaviour` script you have full control over the entity lifecycle: spawning new objects, adding or removing components on existing ones, re-parenting entities in the hierarchy, and deleting with proper resource cleanup.
|
|
4
|
+
|
|
5
|
+
## The ObjectManagementSystem
|
|
6
|
+
|
|
7
|
+
Every world exposes `ObjectManagementSystem` via `this.world.getObjectManagementSystem()`. It is the hub for all runtime entity and component creation:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const oms = this.world.getObjectManagementSystem();
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Always null-check — `oms` returns `null` if the world has not been set up yet.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Spawning new objects
|
|
18
|
+
|
|
19
|
+
Use `requestCreate` to queue a new entity from a built-in kind. The factory runs on the next frame and the optional `onCreated` callback fires with the new `entityId`:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
23
|
+
|
|
24
|
+
export default class Spawner extends Behaviour {
|
|
25
|
+
private spawnedId: number | null = null;
|
|
26
|
+
|
|
27
|
+
protected init() {
|
|
28
|
+
const oms = this.world.getObjectManagementSystem();
|
|
29
|
+
|
|
30
|
+
oms?.requestCreate("cube", {
|
|
31
|
+
name: "RuntimeCube",
|
|
32
|
+
transform: { position: { x: 0, y: 1, z: 0 } },
|
|
33
|
+
}, (entityId) => {
|
|
34
|
+
this.spawnedId = entityId;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dispose(world: any) {
|
|
39
|
+
if (this.spawnedId !== null) {
|
|
40
|
+
this.world.getObjectManagementSystem()?.deleteEntity(this.world, this.spawnedId, true);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Supported kinds: `"cube"`, `"sphere"`, `"plane"`, `"capsule"`, `"cylinder"`, `"empty"`, `"model"`, `"image"`, `"sprite"`, `"video"`, `"audio"`, `"camera"`, `"ambient-light"`, `"directional-light"`, `"point-light"`, `"spot-light"`, `"area-light"`, `"instanced-mesh"`.
|
|
47
|
+
|
|
48
|
+
See the [Spawning reference](./spawning/) for per-kind payload schemas.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Adding components to existing entities
|
|
53
|
+
|
|
54
|
+
### `world.addComponent` — simple data components
|
|
55
|
+
|
|
56
|
+
Use `world.addComponent` to attach data-only components that carry no runtime handles:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
60
|
+
import { Name } from "@relu-interactives/spatial-ecs/components";
|
|
61
|
+
|
|
62
|
+
export default class Tagger extends Behaviour {
|
|
63
|
+
protected init() {
|
|
64
|
+
const entity = this.world.getEntityByName("Target");
|
|
65
|
+
if (!entity) return;
|
|
66
|
+
|
|
67
|
+
this.world.addComponent(entity.entityId, new Name({ value: "Tagged" }));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `oms.createComponentNow` — factory-initialized components
|
|
73
|
+
|
|
74
|
+
For components that need runtime setup (Rapier physics bodies, Web Audio nodes, Three.js material instances, AnimationMixers) use `createComponentNow`. It calls the same registered factory used during scene construction, so all runtime handles are correctly wired up:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
78
|
+
|
|
79
|
+
export default class AddPhysics extends Behaviour {
|
|
80
|
+
protected init() {
|
|
81
|
+
const oms = this.world.getObjectManagementSystem();
|
|
82
|
+
if (!oms) return;
|
|
83
|
+
|
|
84
|
+
oms.createComponentNow("physics", this.world, this.entityId, {
|
|
85
|
+
rigidbody: { type: "dynamic", mass: 1, gravityScale: 1 },
|
|
86
|
+
collider: { shape: "box" },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**When to use which:**
|
|
93
|
+
|
|
94
|
+
| Scenario | Method |
|
|
95
|
+
|----------|--------|
|
|
96
|
+
| Name, EntityType, Transform (data only) | `world.addComponent(id, new ComponentClass(...))` |
|
|
97
|
+
| Physics with Rapier body/collider | `oms.createComponentNow("physics", world, id, config)` |
|
|
98
|
+
| Audio with Web Audio node | `oms.createComponentNow("audio", world, id, path, options, ctx)` |
|
|
99
|
+
| Material with Three.js material instance | `oms.createComponentNow("material", world, id, values)` |
|
|
100
|
+
| Animation with clip catalog | `oms.createComponentNow("animation", world, id)` |
|
|
101
|
+
|
|
102
|
+
**Registered factory kinds:**
|
|
103
|
+
|
|
104
|
+
`"animation"`, `"audio"`, `"camera"`, `"physics"`, `"environment"`, `"entityType"`, `"image"`, `"imageTarget"`, `"imageTargetAnchor"`, `"light"`, `"material"`, `"mesh"`, `"meshGeometry"`, `"model"`, `"name"`, `"object3DRef"`, `"parentId"`, `"postprocessing"`, `"script"`, `"sprite"`, `"transform"`, `"video"`, `"instancedMesh"`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 3. Removing components
|
|
109
|
+
|
|
110
|
+
Remove a component by passing its class to `world.removeComponent`. The associated runtime handles are not disposed — call system-specific cleanup first if needed:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
114
|
+
import { PhysicsComponent } from "@relu-interactives/spatial-ecs/components";
|
|
115
|
+
|
|
116
|
+
export default class Freezer extends Behaviour {
|
|
117
|
+
protected init() {
|
|
118
|
+
// Remove physics from this entity to freeze it in place
|
|
119
|
+
this.world.removeComponent(this.entityId, PhysicsComponent);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 4. Re-parenting at runtime
|
|
127
|
+
|
|
128
|
+
`oms.setParent(world, entityId, parentId, options?)` updates the `ParentId` component and reconciles the Three.js scene graph in the same frame. By default (`preserveWorldTransform: true`) the entity's world-space position and rotation are preserved by adjusting the local transform:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
132
|
+
|
|
133
|
+
export default class Attach extends Behaviour {
|
|
134
|
+
protected init() {
|
|
135
|
+
const oms = this.world.getObjectManagementSystem();
|
|
136
|
+
if (!oms) return;
|
|
137
|
+
|
|
138
|
+
const anchor = this.world.getEntityByName("Anchor");
|
|
139
|
+
if (!anchor) return;
|
|
140
|
+
|
|
141
|
+
// Attach this entity under Anchor, preserving world position
|
|
142
|
+
oms.setParent(this.world, this.entityId, anchor.entityId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Detach from parent (become a direct child of the scene root):
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
oms.setParent(this.world, this.entityId, null);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`setParent` guards against cycles and returns `false` if the operation would create a loop or if either entity does not exist.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 5. Deleting entities and freeing GPU memory
|
|
158
|
+
|
|
159
|
+
`oms.deleteEntity(world, entityId, dispose?)` removes the entity from the ECS world and its `Object3D` from the Three.js scene graph:
|
|
160
|
+
|
|
161
|
+
- Pass `dispose = false` (default) when geometry or materials are shared — GPU resources stay allocated.
|
|
162
|
+
- Pass `dispose = true` for dynamically spawned, one-off objects to immediately free `BufferGeometry` and `Material` GPU memory.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// Safe remove (shared geometry/materials stay on GPU)
|
|
166
|
+
oms.deleteEntity(this.world, entityId);
|
|
167
|
+
|
|
168
|
+
// Full cleanup (frees GPU memory — use for unique spawned objects)
|
|
169
|
+
oms.deleteEntity(this.world, entityId, true);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Always delete spawned entities in `dispose()` to prevent leaks when the script is removed:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
dispose(world: any) {
|
|
176
|
+
if (this.spawnedId !== null) {
|
|
177
|
+
this.world.getObjectManagementSystem()?.deleteEntity(this.world, this.spawnedId, true);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 6. Async loading patterns
|
|
185
|
+
|
|
186
|
+
Models and audio load asynchronously. The entity is created immediately but the asset is not available until the loader fires. Use the `onCreated` callback for setup that doesn't need the asset, and poll component state in `onUpdate` for setup that does:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
190
|
+
import { AnimationComponent } from "@relu-interactives/spatial-ecs/components";
|
|
191
|
+
|
|
192
|
+
export default class ModelSpawner extends Behaviour {
|
|
193
|
+
private modelId: number | null = null;
|
|
194
|
+
private animSetup = false;
|
|
195
|
+
|
|
196
|
+
protected init() {
|
|
197
|
+
const oms = this.world.getObjectManagementSystem();
|
|
198
|
+
|
|
199
|
+
oms?.requestCreate("model", {
|
|
200
|
+
name: "Character",
|
|
201
|
+
path: "localasset://assets/character.glb",
|
|
202
|
+
transform: { position: { x: 0, y: 0, z: 0 } },
|
|
203
|
+
}, (entityId) => {
|
|
204
|
+
this.modelId = entityId;
|
|
205
|
+
|
|
206
|
+
// Safe immediately — entity exists, physics factory is synchronous
|
|
207
|
+
oms.createComponentNow("physics", this.world, entityId, {
|
|
208
|
+
rigidbody: { type: "dynamic", mass: 1 },
|
|
209
|
+
collider: { shape: "capsule" },
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected onUpdate() {
|
|
215
|
+
if (this.modelId === null || this.animSetup) return;
|
|
216
|
+
|
|
217
|
+
// availableClips populates after the GLB finishes loading
|
|
218
|
+
const anim = this.world.getComponent(this.modelId, AnimationComponent);
|
|
219
|
+
if (anim && anim.availableClips.length > 0) {
|
|
220
|
+
anim.addState("Idle", anim.availableClips[0], true);
|
|
221
|
+
anim.switchState("Idle");
|
|
222
|
+
this.animSetup = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
dispose(world: any) {
|
|
227
|
+
if (this.modelId !== null) {
|
|
228
|
+
this.world.getObjectManagementSystem()?.deleteEntity(this.world, this.modelId, true);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 7. Complete worked example
|
|
237
|
+
|
|
238
|
+
Spawn a model, add physics, parent it under an AR anchor, set up animation once clips are ready, and clean everything up on dispose:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { Behaviour } from "@relu-interactives/spatial-ecs";
|
|
242
|
+
import { AnimationComponent } from "@relu-interactives/spatial-ecs/components";
|
|
243
|
+
|
|
244
|
+
export default class ARCharacter extends Behaviour {
|
|
245
|
+
private characterId: number | null = null;
|
|
246
|
+
private animReady = false;
|
|
247
|
+
|
|
248
|
+
protected init() {
|
|
249
|
+
const oms = this.world.getObjectManagementSystem();
|
|
250
|
+
if (!oms) return;
|
|
251
|
+
|
|
252
|
+
const anchor = this.world.getEntityByName("ImageTargetAnchor");
|
|
253
|
+
|
|
254
|
+
oms.requestCreate("model", {
|
|
255
|
+
name: "ARCharacter",
|
|
256
|
+
path: "localasset://assets/character.glb",
|
|
257
|
+
transform: { position: { x: 0, y: 0, z: 0 } },
|
|
258
|
+
}, (entityId) => {
|
|
259
|
+
this.characterId = entityId;
|
|
260
|
+
|
|
261
|
+
// Parent under AR anchor so it tracks with the target
|
|
262
|
+
if (anchor) {
|
|
263
|
+
oms.setParent(this.world, entityId, anchor.entityId);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Add physics (Rapier body wired immediately by the factory)
|
|
267
|
+
oms.createComponentNow("physics", this.world, entityId, {
|
|
268
|
+
rigidbody: { type: "kinematic-position" },
|
|
269
|
+
collider: { shape: "capsule" },
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
protected onUpdate() {
|
|
275
|
+
if (this.characterId === null || this.animReady) return;
|
|
276
|
+
|
|
277
|
+
const anim = this.world.getComponent(this.characterId, AnimationComponent);
|
|
278
|
+
if (anim && anim.availableClips.length > 0) {
|
|
279
|
+
if (anim.availableClips.includes("Idle")) anim.addState("Idle", "Idle", true);
|
|
280
|
+
if (anim.availableClips.includes("Walk")) anim.addState("Walk", "Walk", true);
|
|
281
|
+
anim.crossFadeDuration = 0.3;
|
|
282
|
+
anim.switchState("Idle");
|
|
283
|
+
this.animReady = true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
dispose(world: any) {
|
|
288
|
+
if (this.characterId !== null) {
|
|
289
|
+
this.world.getObjectManagementSystem()?.deleteEntity(this.world, this.characterId, true);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## See also
|
|
298
|
+
|
|
299
|
+
- [Spawning reference](./spawning/) — per-kind payload schemas for `requestCreate`
|
|
300
|
+
- [ObjectManagementSystem](./systems/object-management-system) — system internals and API reference
|
|
301
|
+
- [Accessing Components](./accessing-components) — reading and writing component state
|
|
302
|
+
- [World API](./world-api) — entity queries, input, raycasting, scene access
|