Skip to content

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 ​

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";
import { TransformComponent } from "@relu-interactives/spatial-ecs/components";

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 ​

MethodPurpose
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 ​

MethodPurpose
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 ​

MethodPurpose
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.
getEntitiesChildren(entityId)All child entities of the given entity (entities whose ParentId component points to this entity).
getEntitiesParent(entityId)The parent entity of the given entity, or null if the entity has no parent or is at the world root.

Scripts ​

MethodPurpose
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 ​

MethodPurpose
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 ​

MethodPurpose
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 ​

MethodPurpose
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 ​

MethodPurpose
getSystem(instance)Resolve the REGISTERED system matching the argument's class — pass a throwaway instance (world.getSystem(new XRHitTestSystem())), never the class itself.
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 ​

MethodPurpose
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 ​

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";
import { TransformComponent } from "@relu-interactives/spatial-ecs/components";

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 ​

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";
import { LightComponent } from "@relu-interactives/spatial-ecs/components";

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;
    }
  }
}

Use getEntitiesParent and getEntitiesChildren to traverse parent-child relationships in the scene graph. This is useful for group operations, finding related entities, or building custom hierarchy traversal logic.

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";

export default class HierarchyWalker extends Behaviour {
  init() {
    // Get the parent of this entity
    const parent = this.world.getEntitiesParent(this.entityId);
    if (parent) {
      console.log(`Parent: ${parent.name} (ID: ${parent.entityId})`);
    }

    // Get all children of this entity
    const children = this.world.getEntitiesChildren(this.entityId);
    console.log(`Children count: ${children.length}`);
    for (const child of children) {
      console.log(`  - ${child.name} (ID: ${child.entityId})`);
    }

    // Recursively walk up to the root
    let current = this.world.getEntityById(this.entityId);
    while (current) {
      console.log(`Ancestor: ${current.name}`);
      current = this.world.getEntitiesParent(current.entityId);
    }
  }
}

Talk to another script on a different entity ​

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";
// Your own Behaviour, in a sibling file.
declare class Health extends Behaviour {
  takeDamage(amount: number): void;
}

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:

  1. Manually — allocate an id, build your own THREE.Object3D, and attach components yourself. Use this when you need full control over the mesh/material.
  2. 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) ​

ts
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);

    // Each factory takes (world, entityId, ...constructorArgs) and attaches
    // the component itself — there is nothing to pass to addComponents.
    createNameComponent(this.world, id, "RuntimeCube");
    createEntityTypeComponent(this.world, id, "cube");
    createTransformComponent(this.world, id, { position: { x: 0, y: 1, z: 0 } });
    createObject3DRefComponent(this.world, id, 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.

ts
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.

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";
import { Name } from "@relu-interactives/spatial-ecs/components";
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, Name);
    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.

ts
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 ​

ts
import { Behaviour, KeyboardKeys } from "@relu-interactives/spatial-ecs";
import { TransformComponent } from "@relu-interactives/spatial-ecs/components";

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 ​

ts
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 ​

ts
import { Behaviour, KeyboardKeys } from "@relu-interactives/spatial-ecs";
import { PhysicsComponent } from "@relu-interactives/spatial-ecs/components";

export default class Jump extends Behaviour {
  onUpdate() {
    if (!this.world.getKeyDown(KeyboardKeys.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.

ts
import { Behaviour, KeyboardKeys } 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(KeyboardKeys.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 in a custom script (Behaviour) instead and guard your onUpdate logic.

See also ​