The World
Every Behaviour script has a this.world reference to the owning World. It is the runtime gateway for finding entities, querying components, reading input, and driving the scene from your scripts.
TIP
Full TypeScript signatures live in the auto-generated reference: World API.
This page summarises the methods most useful from a Behaviour and shows how to combine them.
Inside a Behaviour
import { Behaviour, TransformComponent } from "@relu-interactives/spatial-ecs";
export default class Spinner extends Behaviour {
onUpdate() {
// `this.world` is refreshed every frame
const transform = this.world.getComponent(this.entity.entityId, TransformComponent);
if (transform) {
transform.rotation.y += this.deltaTime;
}
}
}Method reference
Entity lifecycle
| Method | Purpose |
|---|---|
createEntity() | Allocate a new entity id. |
destroyEntity(entityId) | Remove an entity and all of its components. |
hasEntity(entityId) | true if the entity still exists. |
getEntities() | Array of every active entity id. |
getEntityCount() | Number of active entities. |
Components
| Method | Purpose |
|---|---|
addComponent(entityId, component) | Attach a single component. |
addComponents(entityId, [components]) | Attach a batch of components. |
removeComponent(entityId, ComponentClass) | Detach a component by class. |
getComponent(entityId, ComponentClass) | Read a component or null. |
hasComponent(entityId, ComponentClass) | true if the entity has the component. |
query(...ComponentClasses) | Array of entity ids that have all of the given components. |
Entity lookups
| Method | Purpose |
|---|---|
getAllEntities() | All entities as WorldEntityView objects ({ entityId, name, type, object, components }). |
getEntityById(entityId) | One WorldEntityView or null. |
getEntityByName(name) | First entity whose Name matches, or null. |
getEntitiesByName(name) | All entities sharing a Name. |
getEntitiesByComponent(component) | All entities that own a component of the same class. |
Scripts
| Method | Purpose |
|---|---|
getEntityScript<T>(entityId) | The live Behaviour instance attached to an entity. |
getScript<T>(className) | First behaviour instance of the given class anywhere in the world. |
getScripts<T>(className) | All behaviour instances of the given class. |
Input
| Method | Purpose |
|---|---|
getKey(key) | true while a key is held this frame. |
getKeyDown(key) / getKeyUp(key) | Edge-triggered: true only on the frame the key transitions. |
getMouse(button) / getMouseDown(button) / getMouseUp(button) | Mouse buttons ('left', 'right', etc.). |
getScrollClick() / getScrollClickDown() / getScrollClickUp() | Middle-mouse / scroll-wheel click. |
getMousePosition() | Latest pointer position (canvas-relative). |
getMouseDelta() / consumeMouseDelta() | Movement since last frame; consume zeroes it after read. |
getScrollDelta() / consumeScrollDelta() | Wheel deltas; consume zeroes them. |
lockMousePointer() / unlockMousePointer() / isMousePointerLocked() | Pointer-lock for first-person controls. |
Scene & physics access
| Method | Purpose |
|---|---|
getScene() | The live THREE.Scene. |
getCamera() | The active rendering camera. |
getRenderer() | The THREE.WebGLRenderer. |
getPhysicsWorld() | The Rapier World (or null if physics is disabled). |
Raycasting
| Method | Purpose |
|---|---|
raycast(origin, direction, near?, far?) | Cast a ray through the scene; returns IntersectionResult[] sorted by distance. |
raycastFrom(eid, near?, far?) | Cast a ray from an entity's world position along its forward direction; the source entity is filtered out of results. |
Both methods accept Vec3 (THREE.Vector3 or { x, y, z }) and return IntersectionResult[] — each entry pairs the owning ECS entityId with the underlying threeResult: THREE.Intersection so you can read point, distance, face, uv, etc. directly from three.js.
Systems
| Method | Purpose |
|---|---|
getSystem(SystemClass) | Resolve a registered system instance. |
hasSystem(SystemClass) | true if the system is registered. |
getObjectManagementSystem() | Shorthand for the built-in ObjectManagementSystem. Use this to spawn or destroy entities at runtime (requestCreate, createNow, deleteEntity). |
getEnvironmentComponent() | Returns the singleton Environment component, or undefined if not yet initialized. Mutate values to change skybox, fog, or gravity at runtime. |
getPostprocessingComponent() | Returns the singleton Postprocessing component, or undefined if not yet initialized. Mutate values to toggle effects or adjust parameters at runtime. |
World lifecycle
| Method | Purpose |
|---|---|
pause() | Pause simulation. All system ticks stop — physics, animation, scripts, and rendering freeze on the last frame. |
resume() | Resume simulation after a pause() call. Has no effect if the world is not paused. |
isPaused() | Returns true while the world is paused. |
Examples
Find an entity by name and move it
import { Behaviour, TransformComponent } from "@relu-interactives/spatial-ecs";
export default class TargetMover extends Behaviour {
onUpdate() {
const target = this.world.getEntityByName("Goal");
if (!target) return;
const transform = this.world.getComponent(target.entityId, TransformComponent);
if (!transform) return;
transform.position.x += this.deltaTime * 0.5;
}
}Iterate every entity that owns a component
import { Behaviour, LightComponent } from "@relu-interactives/spatial-ecs";
export default class FlickerAllLights extends Behaviour {
onUpdate() {
const ids = this.world.query(LightComponent);
for (const id of ids) {
const light = this.world.getComponent(id, LightComponent);
if (!light) continue;
light.values.intensity = 1 + Math.sin(performance.now() / 200) * 0.2;
}
}
}Talk to another script on a different entity
import { Behaviour } from "@relu-interactives/spatial-ecs";
import type Health from "./Health";
export default class DamageDealer extends Behaviour {
fire() {
const target = this.world.getEntityByName("Player");
if (!target) return;
const health = this.world.getEntityScript<Health>(target.entityId);
health?.takeDamage(10);
}
}Spawn a new entity at runtime
There are two ways to spawn entities from a script:
- Manually — allocate an id, build your own
THREE.Object3D, and attach components yourself. Use this when you need full control over the mesh/material. - Through the
ObjectManagementSystem— ask the registered factory for a built-in kind ("cube","sphere","model","pointLight", …) to create the entity with all of its baseline components for you. Use this when you want a primitive that behaves exactly like one created from the editor.
Option 1 (Manual: build the Object3D yourself)
import {
Behaviour,
createEntityTypeComponent,
createNameComponent,
createObject3DRefComponent,
createTransformComponent,
} from "@relu-interactives/spatial-ecs";
import * as THREE from "three";
export default class Spawner extends Behaviour {
init() {
const id = this.world.createEntity();
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: "tomato" }),
);
this.world.getScene().add(mesh);
this.world.addComponents(id, [
createNameComponent({ value: "RuntimeCube" }),
createEntityTypeComponent({ value: "cube" }),
createTransformComponent({ position: { x: 0, y: 1, z: 0 } }),
createObject3DRefComponent({ object: mesh }),
]);
}
}Option 2 (Use ObjectManagementSystem)
ObjectManagementSystem is the same system the editor uses to create objects, so the new entity comes pre-wired with the correct components for its kind.
import { Behaviour } from "@relu-interactives/spatial-ecs";
export default class CubeSpawner extends Behaviour {
init() {
const objects = this.world.getObjectManagementSystem();
if (!objects) return;
// Supported kinds:
// "cube", "sphere", "plane", "capsule", "cylinder", "empty",
// "model", "image", "imageTarget", "sprite", "audio", "camera", "video",
// "ambientLight", "directionalLight", "pointLight", "spotLight", "areaLight"
objects.requestCreate(
"cube",
{
name: "RuntimeCube",
transform: {
position: { x: 0, y: 1, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1, z: 1 },
},
},
(entityId) => {
// Optional: runs once the entity has been created.
console.log("spawned cube", entityId);
},
);
}
}For the payload accepted by each kind (audio paths, camera projection, light colors, video chroma-key, image targets, etc.), see the Spawning reference.
Cast a ray and read the hit entity
world.raycast accepts any origin/direction in world space and returns every intersected mesh sorted by distance. Each hit carries the owning entityId so you can drive game logic from the result.
import { Behaviour, NameComponent } from "@relu-interactives/spatial-ecs";
import * as THREE from "three";
export default class ClickToInspect extends Behaviour {
private direction = new THREE.Vector3();
onUpdate() {
if (!this.world.getMouseDown("left")) return;
const camera = this.world.getCamera();
camera.getWorldDirection(this.direction);
const hits = this.world.raycast(camera.position, this.direction);
const first = hits.find((hit) => hit.entityId !== null);
if (!first || first.entityId === null) return;
const name = this.world.getComponent(first.entityId, NameComponent);
console.log(
"clicked",
name?.value ?? first.entityId,
"@",
first.threeResult.distance.toFixed(2),
"m",
);
}
}Cast a ray from an entity's forward direction
world.raycastFrom(eid) uses the entity's world position and +Z direction. The source entity is automatically excluded from the results so you don't hit yourself.
import { Behaviour } from "@relu-interactives/spatial-ecs";
export default class LookDetector extends Behaviour {
onUpdate() {
const hits = this.world.raycastFrom(this.entityId, 0, 25);
const target = hits[0];
if (target && target.entityId !== null) {
const view = this.world.getEntityById(target.entityId);
console.log(
"looking at",
view?.name,
"@",
target.threeResult.distance.toFixed(2),
"m",
);
}
}
}Read keyboard and mouse input
import { Behaviour, TransformComponent, KeyboardKeys } from "@relu-interactives/spatial-ecs";
export default class WASDMove extends Behaviour {
speed = 3;
onUpdate() {
const transform = this.world.getComponent(this.entity.entityId, TransformComponent);
if (!transform) return;
const dt = this.deltaTime;
if (this.world.getKey(KeyboardKeys.W)) transform.position.z -= this.speed * dt;
if (this.world.getKey(KeyboardKeys.S)) transform.position.z += this.speed * dt;
if (this.world.getKey(KeyboardKeys.A)) transform.position.x -= this.speed * dt;
if (this.world.getKey(KeyboardKeys.D)) transform.position.x += this.speed * dt;
if (this.world.getMouseDown("left")) {
console.log("click at", this.world.getMousePosition());
}
}
}Pointer lock for a first-person camera
import { Behaviour } from "@relu-interactives/spatial-ecs";
export default class FpsLook extends Behaviour {
init() {
this.world.lockMousePointer();
}
onUpdate() {
if (!this.world.isMousePointerLocked()) return;
const { x, y } = this.world.consumeMouseDelta();
this.entity.object?.rotateY(-x * 0.002);
this.world.getCamera().rotateX(-y * 0.002);
}
dispose() {
this.world.unlockMousePointer();
}
}Reach into the physics world
import { Behaviour, PhysicsComponent } from "@relu-interactives/spatial-ecs";
export default class Jump extends Behaviour {
onUpdate() {
if (!this.world.getKeyDown("space")) return;
const physics = this.world.getComponent(this.entity.entityId, PhysicsComponent);
physics?.rigidBody?.applyImpulse({ x: 0, y: 5, z: 0 }, true);
}
}Pause and resume the world
Use pause() / resume() to freeze and unfreeze simulation. All system ticks stop when paused — physics, animation, scripts, and the render pass. The last rendered frame stays frozen on the canvas.
import { Behaviour } from "@relu-interactives/spatial-ecs";
export default class PauseOnHit extends Behaviour {
onUpdate() {
// Pause when the spacebar is pressed, resume when pressed again.
if (!this.world.getKeyDown("Space")) return;
if (this.world.isPaused()) {
this.world.resume();
} else {
this.world.pause();
}
}
}TIP
pause() and resume() affect the entire world. If you only want to stop a subset of entities (for example, freeze enemies while a menu is open) use a flag on a custom component instead and guard your onUpdate logic.
See also
- Custom Behaviours —
init,onUpdate,dispose,data, lifecycle. - Behaviour Data Binding — exposing fields to the inspector through
data. - Behaviour Lifecycle & Events — when each hook fires.
- Component Catalog — the components you'll read and mutate from scripts.

