Skip to content

Light

Light source attached to an entity. type selects the underlying three.js light class (directional, point, spot, ambient, hemisphere, rect-area). values carries authoring state including color, intensity, and type-specific fields (distance, decay, angle, penumbra, width, height).

Managed by: LightSyncSystem

Editor inspector

Light inspector

Properties

PropertyTypeDescription
lightTHREE.LightLive three.js light placed in the scene. Recreated when the project loads.
typestringThree.js class name of the light (e.g. "DirectionalLight", "PointLight", "SpotLight").
valuesLightValueStateSerialized authoring state. Saved with the project.

API reference

Scripting examples

Component access patterns

Use this.getComponent(X) to access a component on the entity this script is attached to. Use this.world.getEntityByName to find another entity by name, then this.world.getComponent to read its component:

typescript
// Component on the entity this script is on
const light = this.getComponent(LightComponent);

// Component on another entity, found by its scene name
const other = this.world.getEntityByName("SpotLight");
if (other) {
  const light = this.world.getComponent(other.entityId, LightComponent);
}

Pulse intensity

Animate the light's intensity between a min and max value using a sine wave — useful for flickering flames or heartbeat effects.

typescript
import { Behaviour, LightComponent } from "@relu-interactives/spatial-ecs";
import type { FloatInput } from "@relu-interactives/spatial-ecs";

export default class LightPulse extends Behaviour {
  data = {
    speed: 2 as FloatInput,
    minIntensity: 0.5 as FloatInput,
    maxIntensity: 3 as FloatInput,
  };

  private elapsed = 0;

  protected onUpdate() {
    this.elapsed += this.deltaTime;
    const light = this.getComponent(LightComponent);
    if (!light?.light) return;

    const t = (Math.sin(this.elapsed * this.data.speed) + 1) * 0.5;
    light.light.intensity =
      this.data.minIntensity + t * (this.data.maxIntensity - this.data.minIntensity);
  }
}

Timed color shift

Cycle the light's color through the RGB spectrum over a configurable period.

typescript
import { Behaviour, LightComponent } from "@relu-interactives/spatial-ecs";
import type { FloatInput } from "@relu-interactives/spatial-ecs";

export default class LightColorCycle extends Behaviour {
  data = {
    period: 4 as FloatInput,
  };

  private elapsed = 0;

  protected onUpdate() {
    this.elapsed += this.deltaTime;
    const light = this.getComponent(LightComponent);
    if (!light?.light) return;

    const hue = (this.elapsed / this.data.period) % 1;
    (light.light.color as { setHSL(h: number, s: number, l: number): void })
      .setHSL(hue, 1, 0.5);
  }
}

Live light access

Mutate light.light.intensity and light.light.color directly for immediate per-frame changes. For changes that should survive a save/reload cycle, also update light.values.intensity / light.values.color so the authoring state stays in sync.

Via ComponentInput in the inspector

You can also pick this component from the inspector using a ComponentInput field. Assign any entity in the inspector and the field resolves to the live LightComponent instance at runtime.

typescript
import {
  Behaviour,
  LightComponent,
  type ComponentInput,
} from "@relu-interactives/spatial-ecs";

export default class Example extends Behaviour {
  data = {
    targetLight: {
      type: "component",
      value: null,
    } as unknown as ComponentInput,
  };

  protected onUpdate() {
    const light = this.data.targetLight.value as LightComponent | null;
    if (!light) return;
    // Use the live component instance.
  }
}

Via EntityInput in the inspector

Alternatively, pick an entity from the inspector using an EntityInput field and then read the component off that entity via world.getComponent:

typescript
import {
  Behaviour,
  LightComponent,
  type EntityInput,
  type WorldEntityView,
} from "@relu-interactives/spatial-ecs";

export default class Example extends Behaviour {
  data = {
    targetEntity: { type: "entity", value: null } as unknown as EntityInput,
  };

  protected onUpdate() {
    const entity = this.data.targetEntity.value as WorldEntityView | null;
    if (!entity) return;

    const light = this.world.getComponent(
      entity.entityId,
      LightComponent,
    ) as LightComponent | null;
    if (!light) return;
    // Use the component on the picked entity.
  }
}