Docs Scripting API

Scripting API

PHP 8.5+ class reference for the Lenga scripting runtime.

Attributes

AddComponentMenu

Applies to: **classes**

Namespace:

namespace Lenga\Engine\Attributes;

Registers a Behaviour subclass in the editor's Add Component menu under the specified path. Without this attribute, the Behaviour class can still be used by code, but it will not appear in the Inspector's component picker.

Parameters

Parameter Type Default Description
$menuName string Menu path shown in the component picker, e.g. "AI/Pathfinding".
$order int\|null null Optional sort order within the menu group.

Example

use Lenga\Engine\Attributes\AddComponentMenu;
use Lenga\Engine\Core\Behaviour;

#[AddComponentMenu('AI/Pathfinding Agent')]
class PathfindingAgent extends Behaviour
{
    public float $speed = 3.5;

    public function update(): void
    {
        // move towards target...
    }
}
Attributes

Header

Applies to: **properties**

Namespace:

namespace Lenga\Engine\Attributes;

Adds a bold label above a serialized property in the Inspector window. Use it to visually group related fields without adding any runtime overhead.

Parameters

Parameter Type Description
$headerText string The label text to display above the field.

Example

use Lenga\Engine\Attributes\Header;
use Lenga\Engine\Core\Behaviour;

class PlayerController extends Behaviour
{
    #[Header('Movement')]
    public float $speed = 5.0;
    public float $jumpHeight = 2.0;

    #[Header('Health')]
    public int $maxHealth = 100;
    public int $currentHealth = 100;
}
Attributes

HideInInspector

Applies to: **properties**

Namespace:

namespace Lenga\Engine\Attributes;

Hides a public property from the Inspector window. Useful when a field must be public for inter-script access but should not be exposed or edited through the editor.

Example

use Lenga\Engine\Attributes\HideInInspector;
use Lenga\Engine\Core\Behaviour;

class Enemy extends Behaviour
{
    // Configurable in the Inspector
    public int $maxHealth = 100;

    // Runtime-only — hidden from the Inspector
    #[HideInInspector]
    public bool $isDead = false;

    #[HideInInspector]
    public int $currentHealth = 0;

    public function start(): void
    {
        $this->currentHealth = $this->maxHealth;
    }
}
Attributes

Icon

Applies to: **classes**

Namespace:

namespace Lenga\Engine\Attributes;

Sets a custom icon for a Behaviour class in the editor. The icon is displayed next to the component's name in the Inspector and the scene hierarchy.

Parameters

Parameter Type Description
$iconPath string Path to the icon file, relative to the project's Assets/ folder.

Example

use Lenga\Engine\Attributes\Icon;
use Lenga\Engine\Core\Behaviour;

#[Icon('Icons/enemy-icon.png')]
class Enemy extends Behaviour
{
    public int $health = 50;

    public function update(): void
    {
        // enemy logic...
    }
}
Attributes

Min

Applies to: **properties** (numeric)

Namespace:

namespace Lenga\Engine\Attributes;

Clamps a serialized numeric property to a minimum value in the Inspector. The editor prevents the value from being set below the specified minimum. Use Range when you need both a minimum and maximum.

Parameters

Parameter Type Description
$minValue float\|int The minimum value the field can hold.

Example

use Lenga\Engine\Attributes\Min;
use Lenga\Engine\Core\Behaviour;

class Enemy extends Behaviour
{
    // Health can never be set below 1 in the Inspector
    #[Min(1)]
    public int $maxHealth = 100;

    // Speed cannot go negative
    #[Min(0.0)]
    public float $movementSpeed = 3.5;
}
Attributes

Range

Applies to: **properties** (numeric)

Namespace:

namespace Lenga\Engine\Attributes;

Constrains a serialized numeric property to a minimum/maximum range in the Inspector. The field is displayed as a slider instead of a plain number input, making it easy to tune values visually.

Parameters

Parameter Type Description
$minValue float\|int The minimum value of the slider.
$maxValue float\|int The maximum value of the slider.

Example

use Lenga\Engine\Attributes\Range;
use Lenga\Engine\Core\Behaviour;

class AudioManager extends Behaviour
{
    #[Range(0.0, 1.0)]
    public float $masterVolume = 0.8;

    #[Range(0, 10)]
    public int $reverb = 3;

    public function update(): void
    {
        // apply masterVolume to audio output...
    }
}
Attributes

RequireComponent

Applies to: **classes** (repeatable)

Namespace:

namespace Lenga\Engine\Attributes;

Declares that this Behaviour depends on one or more other components being present on the same GameObject.

Use this when a Behaviour cannot work correctly unless another component exists beside it. For example, a player movement Behaviour might require a Rigidbody2D, or a pickup Behaviour might require a renderer and collider.

When Lenga attaches the Behaviour, it checks the required components and adds any missing ones before the Behaviour lifecycle methods run. This applies both when the Behaviour is authored in the editor and when the Behaviour is added from code.

Multiple types can be listed in a single attribute or by stacking multiple RequireComponent attributes.

Parameters

Parameter Type Description
...$componentTypes string One or more fully-qualified component class names.

Example

use Lenga\Engine\Attributes\RequireComponent;
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Rigidbody2D;
use Lenga\Engine\Core\BoxCollider2D;

// Ensure a 2D physics body and collider are always present.
#[RequireComponent(Rigidbody2D::class, BoxCollider2D::class)]
class PlayerController extends Behaviour
{
    public float $speed = 5.0;

    public function update(): void
    {
        $body = $this->getComponent(Rigidbody2D::class);
        // Move using the rigidbody...
    }
}
Attributes

SerializeField

Applies to: **properties**

Namespace:

namespace Lenga\Engine\Attributes;

Exposes a private or protected property to the Inspector so it can be set without making the property part of the class's public API. This keeps encapsulation intact while still allowing values to be configured from the editor.

Public properties are serialized automatically. Use SerializeField only on private or protected ones.

Example

use Lenga\Engine\Attributes\SerializeField;
use Lenga\Engine\Core\Behaviour;

class PlayerController extends Behaviour
{
    // Editable in the Inspector, but private to this class
    #[SerializeField]
    private float $speed = 5.0;

    #[SerializeField]
    private int $maxJumps = 2;

    private int $jumpsRemaining = 0;

    public function start(): void
    {
        $this->jumpsRemaining = $this->maxJumps;
    }

    public function update(): void
    {
        // use $this->speed...
    }
}
Attributes

SerializeReference

Applies to: **properties**

Namespace:

namespace Lenga\Engine\Attributes;

Marks a property for managed-reference serialization. Unlike SerializeField, which serializes by value, SerializeReference preserves the concrete type of the object when saving and loading, making it suitable for polymorphic fields (interfaces, abstract classes, or class hierarchies).

Example

use Lenga\Engine\Attributes\SerializeReference;
use Lenga\Engine\Core\Behaviour;

interface IWeapon
{
    public function attack(): void;
}

class Sword implements IWeapon
{
    public float $damage = 25.0;

    public function attack(): void { /* swing */ }
}

class Player extends Behaviour
{
    // The actual concrete type (Sword, Bow, etc.) is preserved on save/load
    #[SerializeReference]
    public ?IWeapon $equippedWeapon = null;

    public function update(): void
    {
        $this->equippedWeapon?->attack();
    }
}
Attributes

Space

Applies to: **properties**

Namespace:

namespace Lenga\Engine\Attributes;

Inserts a vertical gap above a field in the Inspector window, adding breathing room between groups of properties without a visible label. For a labeled separator, use Header instead.

Parameters

Parameter Type Default Description
$height float 8.0 Height of the gap in pixels.

Example

use Lenga\Engine\Attributes\Header;
use Lenga\Engine\Attributes\Space;
use Lenga\Engine\Core\Behaviour;

class PlayerController extends Behaviour
{
    public float $speed = 5.0;
    public float $jumpHeight = 2.0;

    // Extra gap before the next property group
    #[Space(16.0)]
    #[Header('Stamina')]
    public float $maxStamina = 100.0;
    public float $staminaRegenRate = 10.0;
}
Attributes

TextArea

Applies to: **properties** (string)

Namespace:

namespace Lenga\Engine\Attributes;

Renders a multi-line text area in the Inspector for a serialized string property. Useful for longer text values such as dialogue lines, descriptions, or configuration blobs.

Parameters

Parameter Type Default Description
$minLines int 3 Minimum number of visible lines in the editor.
$maxLines int 10 Maximum number of visible lines before scrolling.

Example

use Lenga\Engine\Attributes\TextArea;
use Lenga\Engine\Core\Behaviour;

class DialogueTrigger extends Behaviour
{
    #[TextArea(minLines: 3, maxLines: 8)]
    public string $dialogueText = 'Enter dialogue here...';

    public function onTriggerEnter(): void
    {
        // display $this->dialogueText in the UI...
    }
}
Attributes

Tooltip

Applies to: **properties**

Namespace:

namespace Lenga\Engine\Attributes;

Attaches a descriptive tooltip to a serialized property in the Inspector. When the user hovers over the field label, the tooltip text is shown as a pop-up hint, improving discoverability without cluttering the layout.

Parameters

Parameter Type Description
$tooltipText string The text shown when hovering over the field in the Inspector.

Example

use Lenga\Engine\Attributes\Tooltip;
use Lenga\Engine\Core\Behaviour;

class PlayerController extends Behaviour
{
    #[Tooltip('Movement speed in units per second.')]
    public float $speed = 5.0;

    #[Tooltip('Multiplier applied to speed when sprinting. Values above 2 may feel unresponsive.')]
    public float $sprintMultiplier = 1.5;
}
Core

Application

Engine/application-level utilities.

Namespace:

namespace Lenga\Engine\Core;

class Application

Methods

quit

public static function quit()

Engine/application-level utilities.

pause

public static function pause()

resume

public static function resume()

togglePause

public static function togglePause()

isPaused

public static function isPaused()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Application;

class QuitButton extends Behaviour
{
    public function update(): void
    {
        if (Input::getKeyDown(KeyCode::ESCAPE)) {
            Application::quit();
        }
    }
}
Core

AudioSource

Namespace:

namespace Lenga\Engine\Core;

class AudioSource

AudioSource plays an audio clip from a GameObject.

Use it for background music, UI sounds, pickup sounds, impacts, jump sounds, and other one-shot or looping audio. In the editor, add an Audio Source component, assign an audio clip, then set playback options such as Play On Awake, Loop, Volume, and Pitch.

Properties

clipPath

public string $clipPath

Project-relative path to the audio file this source should play.

Example:

$audio->clipPath = 'Assets/Audio/jump.wav';

playOnAwake

public bool $playOnAwake

When true, the source starts playing automatically when the component starts. When false, the clip only plays when you call play() from code.

loop

public bool $loop

When true, the source restarts after the clip finishes. Use this for background music and ambience.

volume

public float $volume

Playback volume from 0.0 to 1.0.

pitch

public float $pitch

Playback pitch. 1.0 is the normal pitch. Values above 1.0 play higher and faster. Values below 1.0 play lower and slower.

Methods

play

public function play(): bool

Starts the assigned clip. Returns false if the clip cannot be played.

stop

public function stop(): bool

Stops playback.

isPlaying

public function isPlaying(): bool

Returns whether this source is currently playing.

Example: Play a Jump Sound

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\AudioSource;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Core\KeyCode;

class SoundEffectPlayer extends Behaviour
{
    public function start(): void
    {
        $audio = $this->gameObject->getComponent(AudioSource::class);
        $audio->clipPath = 'assets/sounds/jump.wav';
        $audio->volume = 0.8;
    }

    public function update(): void
    {
        if (Input::getKeyDown(KeyCode::SPACE)) {
            $audio = $this->gameObject->getComponent(AudioSource::class);
            if (!$audio->isPlaying()) {
                $audio->play();
            }
        }
    }
}

Notes

  • Play On Awake only controls automatic playback. A script can still call play() explicitly.
  • Active audio sources pause and resume with the engine-wide pause state.
  • Use looping sources for music and ambience. Use explicit play() calls for short gameplay sounds.
Core

Behaviour

Namespace:

namespace Lenga\Engine\Core;

class Behaviour

Behaviour is the base class for component logic that belongs on a GameObject. Use it when code needs scene ownership, lifecycle methods, Inspector fields, or nearby components. Use ordinary PHP classes, enums, and utilities for code that does not need to live on a GameObject.

Event Helpers

createSignal

protected function createSignal(): Signal

Creates an owner-scoped Signal and registers it for cleanup when the behaviour is destroyed.

Use this when a specific instance intentionally exposes a local callback surface. For most cross-system gameplay events, prefer emitEvent(...) and onEvent(...).

emitEvent

protected function emitEvent(string $eventName, mixed $payload = null): void

Emits a global EventBus event.

Use this when listeners should react without holding a reference to the dispatcher.

dispatchEvent

protected function dispatchEvent(string $eventName, mixed $payload = null): void

Alias for emitEvent(...).

onEvent

protected function onEvent(string $eventName, callable $listener, bool $disposeOnDisable = true): SignalSubscription

Subscribes this behaviour to a global EventBus event and automatically tracks the returned subscription.

By default the subscription is disposed when the behaviour disables.

onceEvent

protected function onceEvent(string $eventName, callable $listener, bool $disposeOnDisable = true): SignalSubscription

Like onEvent(...), but the listener is automatically removed after the first matching event.

trackSubscription

protected function trackSubscription(SignalSubscription $subscription, bool $disposeOnDisable = true): SignalSubscription

Tracks an existing subscription handle so the behaviour lifecycle can clean it up.

This is useful when you subscribe directly through EventBus::on(...) or another API that returns a SignalSubscription.

If disposeOnDisable is false, the subscription stays alive across disable and is only released on destroy.

Methods

startCoroutine

public function startCoroutine(\Generator $routine)

stopCoroutine

public function stopCoroutine(Coroutine|\Generator $routine)

stopAllCoroutines

public function stopAllCoroutines()

awake

public function awake()

Called when the Behaviour instance is being loaded.

onEnable

public function onEnable()

Called when the object becomes enabled and active.

start

public function start()

Called before the first frame update, after all Awake calls.

fixedUpdate

public function fixedUpdate()

Called on a fixed timestep, used for physics-like updates.

update

public function update()

Called once per frame.

lateUpdate

public function lateUpdate()

Called once per frame, after all Update calls.

onDisable

public function onDisable()

Called when the object becomes disabled or inactive.

onDestroy

public function onDestroy()

Called when the behaviour is destroyed.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Signal;

final class Door extends Behaviour
{
    public Signal $onOpened;

    public function awake(): void
    {
        $this->onOpened = $this->createSignal();
    }

    public function open(): void
    {
        $this->onOpened->dispatch($this);

        $this->emitEvent('game.door.opened', [
            'source' => $this,
        ]);
    }
}
Core

BoxCollider2D

Namespace:

namespace Lenga\Engine\Core;

class BoxCollider2D

Methods

getContacts

public function getContacts(bool $includeTriggers = true, ?int $layerMask = null)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\BoxCollider2D;
use Lenga\Engine\Core\Collision2D;

class PlayerCollisionHandler extends Behaviour
{
    public function start(): void
    {
        $collider = $this->getComponent(BoxCollider2D::class);
        $collider->size = [1.0, 2.0];
    }

    public function onCollisionEnter(Collision2D $collision): void
    {
        Debug::log('Collided with: ' . $collision->gameObject->name);
    }
}
Core

BoxCollider3D

Namespace:

namespace Lenga\Engine\Core;

class BoxCollider3D

Methods

isTouching

public function isTouching(bool $includeTriggers = true, ?int $layerMask = null)

getContacts

public function getContacts(bool $includeTriggers = true, ?int $layerMask = null)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\BoxCollider3D;
use Lenga\Engine\Core\Collision3D;

class TriggerZone extends Behaviour
{
    public function start(): void
    {
        $collider = $this->getComponent(BoxCollider3D::class);
        $collider->size = [2.0, 2.0, 2.0];
        $collider->isTrigger = true;
    }

    public function onTriggerEnter(Collision3D $collision): void
    {
        Debug::log('Entered trigger zone');
    }
}
Core

Camera

Namespace:

namespace Lenga\Engine\Core;

class Camera

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Camera;

class CameraController extends Behaviour
{
    public function start(): void
    {
        $camera = $this->getComponent(Camera::class);
        $camera->fieldOfView = 60.0;
    }

    public function update(): void
    {
        $camera = $this->getComponent(Camera::class);
        // Use camera to perform raycasts or screen-to-world conversions
    }
}
Core

CapsuleCollider3D

Namespace:

namespace Lenga\Engine\Core;

class CapsuleCollider3D

Methods

isTouching

public function isTouching(bool $includeTriggers = true, ?int $layerMask = null)

getContacts

public function getContacts(bool $includeTriggers = true, ?int $layerMask = null)

moveAndSlide

public function moveAndSlide(Vector3 $delta,
        float $skinWidth = 0.05,
        bool $includeTriggers = false,
        ?int $layerMask = null,)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CapsuleCollider3D;

class CharacterCollider extends Behaviour
{
    public function start(): void
    {
        $collider = $this->getComponent(CapsuleCollider3D::class);
        $collider->radius = 0.5;
        $collider->height = 2.0;
    }

    public function update(): void
    {
        $collider = $this->getComponent(CapsuleCollider3D::class);
        if ($collider->isTouching()) {
            Debug::log('Character is touching something');
        }
    }
}
Core

CharacterController

Namespace:

namespace Lenga\Engine\Core;

class CharacterController

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CharacterController;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Core\Time;

class PlayerMovement extends Behaviour
{
    public float $speed = 5.0;

    public function update(): void
    {
        $controller = $this->getComponent(CharacterController::class);

        $moveDirection = new Vector3(0, 0, 0);
        if (Input::getKey(KeyCode::W)) {
            $moveDirection->z += 1;
        }
        if (Input::getKey(KeyCode::S)) {
            $moveDirection->z -= 1;
        }

        $delta = Vector3::scale($moveDirection, $this->speed * Time::deltaTime());
        $collisionFlags = $controller->move($delta);
    }
}
Core

CircleCollider2D

Namespace:

namespace Lenga\Engine\Core;

class CircleCollider2D

Methods

getContacts

public function getContacts(bool $includeTriggers = true, ?int $layerMask = null)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CircleCollider2D;

class BallCollider extends Behaviour
{
    public function start(): void
    {
        $collider = $this->getComponent(CircleCollider2D::class);
        $collider->radius = 0.5;
    }

    public function update(): void
    {
        $collider = $this->getComponent(CircleCollider2D::class);
        $contacts = $collider->getContacts();
        if (count($contacts) > 0) {
            Debug::log('Ball is in contact with ' . count($contacts) . ' object(s)');
        }
    }
}
Core

Collision2D

Namespace:

namespace Lenga\Engine\Core;

class Collision2D

Methods

fromNativeData

public static function fromNativeData(array $data)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Collision2D;

class DamageOnCollision extends Behaviour
{
    public function onCollisionEnter(Collision2D $collision): void
    {
        // Access collision information
        $otherObject = $collision->gameObject;
        $collider = $collision->collider;
        $contactPoint = $collision->contactPoint;

        Debug::log('Collided with: ' . $otherObject->name);
    }

    public function onCollisionStay(Collision2D $collision): void
    {
        // Continuously called while colliding
    }

    public function onCollisionExit(Collision2D $collision): void
    {
        // Called when collision ends
        Debug::log('Stopped colliding with: ' . $collision->gameObject->name);
    }
}
Core

Collision3D

Namespace:

namespace Lenga\Engine\Core;

class Collision3D

Methods

fromNativeData

public static function fromNativeData(array $data)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Collision3D;

class ExplosiveObject extends Behaviour
{
    public function onCollisionEnter(Collision3D $collision): void
    {
        $otherObject = $collision->gameObject;
        $normalForce = $collision->relativeVelocity;
        $contactPoint = $collision->contactPoint;

        // Only explode if hit with enough force
        if ($normalForce->magnitude() > 10.0) {
            Debug::log('Explosion triggered by: ' . $otherObject->name);
            $this->explode();
        }
    }

    private function explode(): void
    {
        // Explosion logic here
    }
}
Core

Component

Namespace:

namespace Lenga\Engine\Core;

class Component

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Component;
use Lenga\Engine\Core\Rigidbody3D;

class ComponentExample extends Behaviour
{
    public function start(): void
    {
        // Get a component from this GameObject
        $rb = $this->getComponent(Rigidbody3D::class);
        $instanceId = $rb->getInstanceId();

        // Access the GameObject that owns this component
        $gameObject = $this->gameObject;

        Debug::log('Component instance ID: ' . $instanceId);
    }
}
Core

Coroutine

Namespace:

namespace Lenga\Engine\Core;

class Coroutine

Methods

getId

public function getId()

isFinished

public function isFinished()

stop

public function stop()

matchesGenerator

public function matchesGenerator(\Generator $generator)

prime

public function prime()

tick

public function tick()

tickFixedUpdate

public function tickFixedUpdate()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\WaitForSeconds;

class SequenceController extends Behaviour
{
    public function start(): void
    {
        // Start a coroutine that will be managed automatically
        $this->startCoroutine($this->playSequence());
    }

    private function playSequence(): \Generator
    {
        Debug::log('Sequence started');

        yield new WaitForSeconds(1.0);
        Debug::log('1 second has passed');

        yield new WaitForSeconds(2.0);
        Debug::log('3 seconds total have passed');

        // Coroutine automatically finishes when generator ends
    }
}
Core

CubeRenderer

Namespace:

namespace Lenga\Engine\Core;

class CubeRenderer

Methods

setSize

public function setSize(float $width, float $height, float $length)

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CubeRenderer;

class CubeVisual extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(CubeRenderer::class);
        $renderer->setSize(1.0, 1.0, 1.0);
        $renderer->setColor(255, 0, 0, 255); // Red cube
    }
}
Core

CylinderRenderer

Namespace:

namespace Lenga\Engine\Core;

class CylinderRenderer

Methods

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CylinderRenderer;

class CylinderPillar extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(CylinderRenderer::class);
        $renderer->setSize(0.5, 3.0); // radius, height
        $renderer->setColor(100, 100, 100, 255); // Gray cylinder
    }
}
Core

Debug

Namespace:

namespace Lenga\Engine\Core;

class Debug

Methods

log

public static function log(mixed $message)

info

public static function info(mixed $message)

warn

public static function warn(mixed $message)

error

public static function error(mixed $message)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Debug;

class DebugExample extends Behaviour
{
    public function update(): void
    {
        Debug::log('This is a log message');
        Debug::info('This is info');
        Debug::warn('This is a warning');

        if (Input::getKeyDown(KeyCode::SPACE)) {
            Debug::error('Player jumped!');
        }
    }
}
Core

DirectionalLight

Namespace:

namespace Lenga\Engine\Core;

class DirectionalLight

Methods

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\DirectionalLight;

class LightController extends Behaviour
{
    public function start(): void
    {
        $light = $this->getComponent(DirectionalLight::class);
        $light->intensity = 1.0;
        $light->setColor(255, 255, 255, 255); // White light
    }

    public function update(): void
    {
        $light = $this->getComponent(DirectionalLight::class);
        // Rotate the light source
        $this->transform->rotateY(10 * Time::deltaTime());
    }
}
Core

EventBus

Namespace:

namespace Lenga\Engine\Core;

final class EventBus

EventBus is the process-wide decoupled event system.

For gameplay events, start here when listeners should react without holding a reference to the dispatcher. Use Signal only when a specific owner instance intentionally exposes an owner-scoped callback surface.

Inside Behaviours, prefer the Behaviour helpers emitEvent(...), onEvent(...), and onceEvent(...) unless you specifically need the raw static API.

Constants

UI_SELECTION_CHANGED

public const UI_SELECTION_CHANGED = 'ui.selection_changed';

Emitted by the engine when UI selection changes through the built-in navigation system.

The payload is an array with:

  • name: the event name string
  • viaPointer: whether pointer input caused the selection
  • canvas: native lookup data for the canvas
  • previous: native lookup data for the previously selected UI element, or null
  • current: native lookup data for the selected UI element, or null

Methods

on

public static function on(string $eventName, callable $listener): SignalSubscription

Subscribes a listener and returns a SignalSubscription.

The listener receives the payload first and the event name second:

EventBus::on('game.round_started', function (array $payload, string $eventName): void {
    // ...
});

If you are inside a Behaviour, prefer onEvent(...) so lifecycle cleanup is automatic.

addListener

public static function addListener(string $eventName, callable $listener): SignalSubscription

Alias for on(...).

subscribe

public static function subscribe(string $eventName, callable $listener): SignalSubscription

Alias for on(...).

once

public static function once(string $eventName, callable $listener): SignalSubscription

Subscribes a listener that automatically removes itself after the first event.

onceListener

public static function onceListener(string $eventName, callable $listener): SignalSubscription

Alias for once(...).

off

public static function off(string $eventName, SignalSubscription|callable $listener): void

Removes a subscription or callable listener from the event.

removeListener

public static function removeListener(string $eventName, SignalSubscription|callable $listener): void

Alias for off(...).

emit

public static function emit(string $eventName, mixed $payload = null): void

Dispatches a global event with one payload value.

dispatch

public static function dispatch(string $eventName, mixed $payload = null): void

Alias for emit(...).

clear

public static function clear(?string $eventName = null): void

Clears listeners for one event. If no event name is provided, clears all global event listeners.

Use this carefully. Most gameplay code should dispose the specific SignalSubscription it owns instead.

hasListeners

public static function hasListeners(string $eventName): bool

Returns whether the event currently has listeners.

listenerCount

public static function listenerCount(string $eventName): int

Returns the number of listeners for the event.

Example

use Lenga\Engine\Core\Behaviour;

final class Health extends Behaviour
{
    public int $current = 100;
    public string $kind = 'enemy';

    public function applyDamage(int $amount): void
    {
        $this->current -= $amount;

        if ($this->current <= 0) {
            $this->emitEvent('game.health.died', [
                'source' => $this,
                'kind' => $this->kind,
            ]);
        }
    }
}
use Lenga\Engine\Core\Behaviour;

final class GameManager extends Behaviour
{
    public function onEnable(): void
    {
        $this->onEvent('game.health.died', function (?array $payload): void {
            if (($payload['kind'] ?? null) === 'player') {
                $this->beginGameOverFlow();
            }
        });
    }

    private function beginGameOverFlow(): void
    {
    }
}

This pattern lets unrelated systems react to the same event without first finding the dispatcher.

Core

GameObject

Namespace:

namespace Lenga\Engine\Core;

class GameObject

GameObject represents an object in a scene hierarchy. Every GameObject has a Transform, can contain components, and can have child GameObjects.

Parenting affects transforms in the usual hierarchy-friendly way: moving a parent moves its children in world space, but moving a child does not move the parent. Child objects keep their own local position, rotation, and scale relative to the parent.

Methods

setActive

public function setActive(bool $value)

isActiveSelf

public function isActiveSelf()

isActiveInHierarchy

public function isActiveInHierarchy()

compareTag

public function compareTag(string $tag)

getScene

public function getScene()

getChildren

public function getChildren()

setParent

public function setParent(?GameObject $parent, bool $worldPositionStays = true): bool

Moves this GameObject under another GameObject, or back to the scene root when $parent is null. When $worldPositionStays is true, Lenga preserves the object's world-space placement and recalculates its local transform. When it is false, Lenga keeps the current local transform relative to the new parent.

getComponents

public function getComponents(?string $type = null)

getComponentsInChildren

public function getComponentsInChildren(?string $type = null, bool $includeInactive = false)

getComponentInChildren

public function getComponentInChildren(string $type, bool $includeInactive = false)

getComponentsInParent

public function getComponentsInParent(?string $type = null, bool $includeInactive = false)

getComponentInParent

public function getComponentInParent(string $type, bool $includeInactive = false)

addComponent

public function addComponent(string $type)

clone

public function clone(?string $name = null)

find

public static function find(string $name)

findGameObjectsWithTag

public static function findGameObjectsWithTag(string $tag)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\GameObject;

class GameObjectInteraction extends Behaviour
{
    public function start(): void
    {
        // Access the GameObject this Behaviour is attached to
        $gameObject = $this->gameObject;

        // Get information about this GameObject
        Debug::log('Name: ' . $gameObject->name);
        Debug::log('Active: ' . ($gameObject->isActiveInHierarchy() ? 'yes' : 'no'));

        // Find other game objects by tag
        $enemies = GameObject::findGameObjectsWithTag('Enemy');
        foreach ($enemies as $enemy) {
            Debug::log('Found enemy: ' . $enemy->name);
        }
    }
}
Core

Input

Class Input

Namespace:

namespace Lenga\Engine\Core;

class Input

Methods

getAxis

public static function getAxis(string $axis)

Class Input

getAxisRaw

public static function getAxisRaw(string $axis)

Retrieves the raw value of the specified input axis.

getKey

public static function getKey(int|string|KeyCode $key)

Checks if the specified key is currently pressed.

getKeyDown

public static function getKeyDown(int|string|KeyCode $key)

Checks if the specified key was pressed down this frame.

getKeyUp

public static function getKeyUp(int|string|KeyCode $key)

Checks if the specified key was released this frame.

getButton

public static function getButton(string $axis)

Mirrors -style virtual buttons such as Jump.

getButtonDown

public static function getButtonDown(string $axis)

Mirrors -style virtual buttons such as Jump.

getButtonUp

public static function getButtonUp(string $axis)

Mirrors -style virtual buttons such as Jump.

isKeyPressed

public static function isKeyPressed(int|string|KeyCode $key)

Raylib-style alias for IsKeyPressed().

isKeyDown

public static function isKeyDown(int|string|KeyCode $key)

Raylib-style alias for IsKeyDown().

isKeyReleased

public static function isKeyReleased(int|string|KeyCode $key)

Raylib-style alias for IsKeyReleased().

isKeyUp

public static function isKeyUp(int|string|KeyCode $key)

Raylib-style alias for IsKeyUp().

isGamepadAvailable

public static function isGamepadAvailable(int $gamepad)

Mirrors raylib's IsGamepadAvailable().

getGamepadName

public static function getGamepadName(int $gamepad)

Mirrors raylib's GetGamepadName().

isGamepadButtonPressed

public static function isGamepadButtonPressed(int $gamepad, int|GamepadButton $button)

Mirrors raylib's IsGamepadButtonPressed().

isGamepadButtonDown

public static function isGamepadButtonDown(int $gamepad, int|GamepadButton $button)

Mirrors raylib's IsGamepadButtonDown().

isGamepadButtonReleased

public static function isGamepadButtonReleased(int $gamepad, int|GamepadButton $button)

Mirrors raylib's IsGamepadButtonReleased().

isGamepadButtonUp

public static function isGamepadButtonUp(int $gamepad, int|GamepadButton $button)

Mirrors raylib's IsGamepadButtonUp().

getGamepadButtonPressed

public static function getGamepadButtonPressed()

Mirrors raylib's GetGamepadButtonPressed().

getGamepadAxisCount

public static function getGamepadAxisCount(int $gamepad)

Mirrors raylib's GetGamepadAxisCount().

getGamepadAxisMovement

public static function getGamepadAxisMovement(int $gamepad, int|GamepadAxis $axis)

Mirrors raylib's GetGamepadAxisMovement().

setGamepadMappings

public static function setGamepadMappings(string $mappings)

Mirrors raylib's SetGamepadMappings().

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Core\KeyCode;

class PlayerController extends Behaviour
{
    public function update(): void
    {
        // Check for key presses
        if (Input::getKeyDown(KeyCode::W)) {
            Debug::log('W key pressed');
        }

        // Check if key is held down
        if (Input::getKey(KeyCode::SPACE)) {
            // Jump logic
        }

        // Use input axes for smooth movement
        $horizontalInput = Input::getAxis('Horizontal');
        $verticalInput = Input::getAxis('Vertical');

        // Check gamepad input
        if (Input::isGamepadAvailable(0)) {
            $gamepadAxis = Input::getGamepadAxisMovement(0, 0);
        }
    }
}
Core

KinematicMove3DResult

Namespace:

namespace Lenga\Engine\Core;

class KinematicMove3DResult

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CharacterController;
use Lenga\Engine\Core\Vector3;

class CharacterMovement extends Behaviour
{
    public function update(): void
    {
        $controller = $this->getComponent(CharacterController::class);

        // Move the character
        $moveDirection = new Vector3(1, 0, 0);
        $result = $controller->move($moveDirection);

        // Check the move result
        if ($result->collided) {
            Debug::log('Character hit something');

            if ($result->hit !== null) {
                $hitObject = $result->hit->gameObject;
                $hitDistance = $result->hit->distance;
                Debug::log('Hit object: ' . $hitObject->name);
            }
        }

        if ($result->usedSlide) {
            Debug::log('Character slid along a surface');
        }
    }
}
Core

MathUtil

A collection of common math functions.

Namespace:

namespace Lenga\Engine\Core;

class MathUtil

Methods

abs

public static function abs(float $f)

Returns the absolute value of f.

ceil

public static function ceil(float $f)

Returns the smallest integer greater than or equal to f.

ceilToInt

public static function ceilToInt(float $f)

Returns the smallest integer greater than or equal to f (as int).

floor

public static function floor(float $f)

Returns the largest integer smaller than or equal to f.

floorToInt

public static function floorToInt(float $f)

Returns the largest integer smaller than or equal to f (as int).

round

public static function round(float $f)

Returns f rounded to the nearest integer.

roundToInt

public static function roundToInt(float $f)

Returns f rounded to the nearest integer (as int).

sign

public static function sign(float $f)

Returns the mathematical sign of f: -1, 0 or 1.

sqrt

public static function sqrt(float $f)

Returns the square root of f.

pow

public static function pow(float $f, float $p)

Returns f raised to the power p.

exp

public static function exp(float $power)

Returns e raised to the power power.

log

public static function log(float $f, float $base = M_E)

Returns the logarithm of f in a specified base.

log10

public static function log10(float $f)

Returns the base-10 logarithm of f.

acos

public static function acos(float $f)

Returns the arc-cosine of f (radians).

asin

public static function asin(float $f)

Returns the arc-sine of f (radians).

atan

public static function atan(float $f)

Returns the arc-tangent of f (radians).

atan2

public static function atan2(float $y, float $x)

Returns the angle in radians whose tangent is y/x.

cos

public static function cos(float $f)

Returns the cosine of angle f (radians).

sin

public static function sin(float $f)

Returns the sine of angle f (radians).

tan

public static function tan(float $f)

Returns the tangent of angle f (radians).

clamp

public static function clamp(float $value, float $min, float $max)

Clamps value between min and max and returns the clamped value.

clamp01

public static function clamp01(float $value)

Clamps value between 0 and 1.

min

public static function min(float $a, float ...$rest)

Returns the minimum of two or more values.

max

public static function max(float $a, float ...$rest)

Returns the maximum of two or more values.

repeat

public static function repeat(float $t, float $length)

Loops the value t so that it is never larger than length and never

pingPong

public static function pingPong(float $t, float $length)

PingPongs the value t between 0 and length.

wrap

public static function wrap(float $value, float $min, float $max)

Wraps a value into the [min, max) range.

lerp

public static function lerp(float $a, float $b, float $t)

Linearly interpolates between a and b by t (clamped to [0, 1]).

lerpUnclamped

public static function lerpUnclamped(float $a, float $b, float $t)

Linearly interpolates between a and b by t with no clamping.

lerpAngle

public static function lerpAngle(float $a, float $b, float $t)

Same as {@see lerp()} but interpolates correctly when values wrap

inverseLerp

public static function inverseLerp(float $a, float $b, float $t)

Determines the interpolation factor of value between a and b

smoothStep

public static function smoothStep(float $from, float $to, float $t)

Interpolates between from and to with smoothing at the limits.

smoothDamp

public static function smoothDamp(float $current,
        float $target,
        float &$currentVelocity,
        float $smoothTime,
        float $deltaTime,
        float $maxSpeed = self::INFINITY,)

Gradually moves the current value towards a target value, over a

smoothDampAngle

public static function smoothDampAngle(float $current,
        float $target,
        float &$currentVelocity,
        float $smoothTime,
        float $deltaTime,
        float $maxSpeed = self::INFINITY,)

Gradually changes an angle given in degrees towards a desired goal

moveTowards

public static function moveTowards(float $current, float $target, float $maxDelta)

Moves a value current towards target.

moveTowardsAngle

public static function moveTowardsAngle(float $current, float $target, float $maxDelta)

Same as {@see moveTowards()} but handles angles wrapping around

approximately

public static function approximately(float $a, float $b)

Compares two floating-point values and returns true if they are

deltaAngle

public static function deltaAngle(float $current, float $target)

Calculates the shortest difference between two angles in degrees.

closestPowerOfTwo

public static function closestPowerOfTwo(int $value)

Returns the closest power of two to the given value.

isPowerOfTwo

public static function isPowerOfTwo(int $value)

Returns true if the value is a power of two.

nextPowerOfTwo

public static function nextPowerOfTwo(int $value)

Returns the next power of two that is equal to or greater than the

perlinNoise

public static function perlinNoise(float $x, float $y)

Generates 2D Perlin noise for the given coordinates.

perlinNoise1D

public static function perlinNoise1D(float $x)

Generates 1D pseudo-random pattern of float values across a 2D

floatToHalf

public static function floatToHalf(float $f)

Encode a 32-bit float into its IEEE 754 half-precision (16-bit)

halfToFloat

public static function halfToFloat(int $half)

Convert a half-precision float (16-bit int) to a 32-bit float.

gammaToLinearSpace

public static function gammaToLinearSpace(float $value)

Converts a value from gamma (sRGB) to linear colour space.

linearToGammaSpace

public static function linearToGammaSpace(float $value)

Converts a value from linear to gamma (sRGB) colour space.

correlatedColorTemperatureToRGB

public static function correlatedColorTemperatureToRGB(float $kelvin)

Converts a colour temperature in Kelvin to an approximate RGB

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\MathUtil;

class MovementController extends Behaviour
{
    public function update(): void
    {
        // Clamp a value between min and max
        $speed = MathUtil::clamp($desiredSpeed, 0.0, 10.0);

        // Linear interpolation for smooth transitions
        $currentAlpha = MathUtil::lerp($currentAlpha, $targetAlpha, 0.1);

        // Wrap angle values
        $angle = MathUtil::wrap($angle, 0.0, 360.0);

        // Smooth movement towards a target
        $velocity = 0.0;
        $currentValue = MathUtil::smoothDamp(
            $currentValue,
            $targetValue,
            $velocity,
            0.3,
            Time::deltaTime()
        );
    }
}
Core

MeshRenderer

Namespace:

namespace Lenga\Engine\Core;

class MeshRenderer

Methods

loadMesh

public function loadMesh(string $meshPath)

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\MeshRenderer;

class CustomMeshDisplay extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(MeshRenderer::class);
        $renderer->loadMesh('assets/models/character.obj');
        $renderer->setColor(200, 200, 200, 255);
    }
}
Core

ModelRenderer

Namespace:

namespace Lenga\Engine\Core;

class ModelRenderer

Methods

loadModel

public function loadModel(string $modelPath)

loadAnimations

public function loadAnimations(string $animationPath)

getAnimationCount

public function getAnimationCount()

getAnimationNames

public function getAnimationNames()

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\ModelRenderer;

class AnimatedCharacter extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(ModelRenderer::class);
        $renderer->loadModel('assets/models/player.gltf');
        $renderer->loadAnimations('assets/animations/player.anim');

        $animCount = $renderer->getAnimationCount();
        $animNames = $renderer->getAnimationNames();
        Debug::log('Available animations: ' . implode(', ', $animNames));
    }
}
Core

NativeComponent

Namespace:

namespace Lenga\Engine\Core;

class NativeComponent

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\NativeComponent;

class CustomComponentUser extends Behaviour
{
    public function start(): void
    {
        // Get a native component attached to this GameObject
        // This is typically done through getComponent() with the appropriate class name
        $nativeComp = $this->getComponent(NativeComponent::class);

        if ($nativeComp !== null) {
            Debug::log('Found native component');
        }
    }
}
Core

NativeEngine

Namespace:

namespace Lenga\Engine\Core;

class NativeEngine

Example

use Lenga\Engine\Core\Behaviour;

class EngineAccess extends Behaviour
{
    public function update(): void
    {
        // The NativeEngine is an internal class used by the Lenga Engine framework
        // to interact with the native C++ engine layer. It is not typically
        // instantiated or used directly in game scripts.
        //
        // Instead, use the provided public APIs like Application, Physics3D,
        // Time, Input, Debug, and other core classes to interact with the engine.

        Debug::log('Access the engine through public APIs');
    }
}
Core

ParticleSystem

Namespace:

namespace Lenga\Engine\Core;

final class ParticleSystem extends Component

ParticleSystem controls a native 2D particle emitter attached to a GameObject.

Use it when gameplay code needs to start, stop, clear, burst, or retarget a particle effect that was authored in the Inspector.

Getting the Component

The usual workflow is:

  1. Create or select a GameObject in the editor.
  2. Add Particle System from Add Component -> 2D -> Rendering.
  3. Add a Behaviour class to the same GameObject.
  4. In that script, call $this->getComponent(ParticleSystem::class).
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\ParticleSystem;

final class LandingDust extends Behaviour
{
    private ?ParticleSystem $dust = null;

    public function start(): void
    {
        $this->dust = $this->getComponent(ParticleSystem::class);
    }

    public function onLanded(): void
    {
        $this->dust?->emit(16);
        $this->dust?->play();
    }
}

Properties

isPlaying

public bool $isPlaying { get; }

Returns whether the emitter is currently playing.

aliveParticleCount

public int $aliveParticleCount { get; }

Returns how many particles are currently alive.

This is useful for debugging or for waiting until a one-shot effect has finished.

sortingLayer

public string $sortingLayer { get; set; }

Controls the 2D sorting layer used by the particles.

orderInLayer

public int $orderInLayer { get; set; }

Controls the draw order inside the selected sorting layer.

texturePath

public string $texturePath { get; }

Returns the currently assigned particle texture path.

If this is empty, the system draws simple circle particles.

Methods

play

public function play(): void

Starts the particle emitter.

stop

public function stop(bool $clear = false): void

Stops the particle emitter.

Pass true when you also want to remove existing particles immediately.

$particles->stop(true);

clear

public function clear(): void

Removes all currently alive particles.

emit

public function emit(int $count): void

Creates a burst of particles immediately.

$particles->emit(24);

loadTexture

public function loadTexture(string $texturePath): bool

Loads a new particle texture from a project-relative path.

Returns true when the texture was loaded successfully.

if (!$particles->loadTexture('Assets/Sprites/Effects/spark.png')) {
    Debug::warn('Could not load spark particle texture.');
}

getState

public function getState(): array

Returns the current native particle-system state as an array.

The returned array can include:

  • maxParticles
  • emissionRate
  • lifetime
  • startSpeed
  • startSize
  • gravity
  • emissionAngle
  • spreadAngle
  • looping
  • playOnAwake
  • isPlaying
  • aliveParticleCount
  • sortingLayer
  • orderInLayer
  • texturePath

Use getState() for diagnostics or UI display. Prefer the named properties and methods for normal gameplay code.

Runtime Workflow

The PHP surface exposes runtime control for play, stop, clear, burst emission, texture changes, and sorting.

Authoring settings such as emission rate, lifetime, speed, size, color, gravity, angle, and spread are edited in the Inspector. Use the Inspector to shape the emitter and use PHP to control playback, bursts, texture changes, and sorting during gameplay.

For the beginner workflow, see Add 2D Particle Effects.

Core

Physics2D

Namespace:

namespace Lenga\Engine\Core;

class Physics2D

Methods

layerMask

public static function layerMask(int ...$layers)

raycast

public static function raycast(Vector3 $origin,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

raycastAll

public static function raycastAll(Vector3 $origin,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

circleCast

public static function circleCast(Vector3 $origin,
        float $radius,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

circleCastAll

public static function circleCastAll(Vector3 $origin,
        float $radius,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

boxCast

public static function boxCast(Vector3 $origin,
        Vector3 $size,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

boxCastAll

public static function boxCastAll(Vector3 $origin,
        Vector3 $size,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

overlapPoint

public static function overlapPoint(Vector3 $point,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

overlapCircleAll

public static function overlapCircleAll(Vector3 $center,
        float $radius,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

overlapBoxAll

public static function overlapBoxAll(Vector3 $center,
        Vector3 $size,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Physics2D;
use Lenga\Engine\Core\Vector2;

class PhysicsQueries extends Behaviour
{
    public function update(): void
    {
        // Raycast in 2D
        $origin = new Vector2(0, 0);
        $direction = new Vector2(1, 0);
        $hit = Physics2D::raycast($origin, $direction, 100.0);

        if ($hit !== null) {
            Debug::log('Hit: ' . $hit->gameObject->name);
        }

        // Overlap circle
        $circle = Physics2D::overlapCircle(new Vector2(0, 0), 5.0);
        Debug::log('Objects in circle: ' . count($circle));

        // Overlap box
        $box = Physics2D::overlapBox(new Vector2(0, 0), new Vector2(2, 2));
        foreach ($box as $collider) {
            Debug::log('Overlapping: ' . $collider->gameObject->name);
        }

        // Set gravity
        Physics2D::setGravity(new Vector2(0, -9.8));
    }
}
Core

Physics3D

Namespace:

namespace Lenga\Engine\Core;

class Physics3D

Methods

moveAndSlideSphere

public static function moveAndSlideSphere(Vector3 $position,
        float $radius,
        Vector3 $delta,
        ?GameObject $ignoreGameObject = null,
        ?Vector3 $offset = null,
        float $skinWidth = 0.05,
        bool $includeTriggers = false,
        ?int $layerMask = null,)

@deprecated Use CharacterController::move() for the current kinematic movement workflow.

moveAndSlideCapsule

public static function moveAndSlideCapsule(Vector3 $position,
        float $radius,
        float $height,
        Vector3 $delta,
        ?GameObject $ignoreGameObject = null,
        ?Vector3 $offset = null,
        float $skinWidth = 0.05,
        bool $includeTriggers = false,
        ?int $layerMask = null,)

@deprecated Use CharacterController::move() for the current kinematic movement workflow.

layerMask

public static function layerMask(int ...$layers)

raycast

public static function raycast(Vector3 $origin,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

raycastAll

public static function raycastAll(Vector3 $origin,
        Vector3 $direction,
        float $distance,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

overlapSphereAll

public static function overlapSphereAll(Vector3 $center,
        float $radius,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

overlapBoxAll

public static function overlapBoxAll(Vector3 $center,
        Vector3 $size,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

overlapCapsuleAll

public static function overlapCapsuleAll(Vector3 $center,
        float $radius,
        float $height,
        bool $includeTriggers = true,
        ?int $layerMask = null,)

@return list

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Debug;
use Lenga\Engine\Core\Physics3D;
use Lenga\Engine\Core\Vector3;

class PhysicsQueries3D extends Behaviour
{
    public function update(): void
    {
        // Raycast in 3D
        $origin = $this->transform->position;
        $direction = $this->transform->forward;
        $hit = Physics3D::raycast($origin, $direction, 100.0);

        if ($hit !== null) {
            Debug::log('Raycast hit: ' . $hit->gameObject->name);
            Debug::log('Distance: ' . $hit->distance);
        }

        // Overlap sphere
        $sphereColliders = Physics3D::overlapSphereAll(
            new Vector3(0, 1, 0),
            5.0
        );
        Debug::log('Objects in sphere: ' . count($sphereColliders));

        // Overlap box
        $boxColliders = Physics3D::overlapBoxAll(
            new Vector3(0, 0, 0),
            new Vector3(2, 2, 2)
        );
        Debug::log('Objects in box: ' . count($boxColliders));
    }
}
Core

PhysicsMaterial2D

Namespace:

namespace Lenga\Engine\Core;

class PhysicsMaterial2D

Methods

fromArray

public static function fromArray(array $state, string $assetPath = '')

@param array{

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Rigidbody2D;

class BouncyBall extends Behaviour
{
    public function start(): void
    {
        $rb = $this->getComponent(Rigidbody2D::class);

        // Create and configure physics material
        $material = new PhysicsMaterial2D();
        $material->density = 1.0;
        $material->friction = 0.4;
        $material->restitution = 0.8; // Bouncy!

        // Apply material to collider
        $rb->material = $material;
    }
}
Core

PlaneRenderer

Namespace:

namespace Lenga\Engine\Core;

class PlaneRenderer

Methods

setSize

public function setSize(float $width, float $length)

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\PlaneRenderer;

class GroundPlane extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(PlaneRenderer::class);
        $renderer->setSize(10.0, 10.0);
        $renderer->setColor(100, 150, 100, 255); // Green ground
    }
}
Core

PointLight

Namespace:

namespace Lenga\Engine\Core;

class PointLight

Methods

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\PointLight;

class TorchLight extends Behaviour
{
    public function start(): void
    {
        $light = $this->getComponent(PointLight::class);
        $light->intensity = 1.5;
        $light->range = 10.0;
        $light->setColor(255, 200, 100, 255); // Warm orange light
    }

    public function update(): void
    {
        $light = $this->getComponent(PointLight::class);
        // Flicker effect
        $light->intensity = 1.5 + MathUtil::sin(Time::time() * 5) * 0.3;
    }
}
Core

Prefab

Namespace:

namespace Lenga\Engine\Core;

class Prefab

Methods

instantiate

public static function instantiate(string $assetPath, ?string $name = null)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Prefab;

class SpawnManager extends Behaviour
{
    public function start(): void
    {
        // Instantiate a prefab at this GameObject's position
        $enemyInstance = Prefab::instantiate('assets/prefabs/enemy.prefab', 'Enemy_1');
        $enemyInstance->transform->position = $this->gameObject->transform->position;
    }

    public function update(): void
    {
        if (Input::getKeyDown(KeyCode::SPACE)) {
            // Spawn a bullet from a prefab
            $bullet = Prefab::instantiate('assets/prefabs/bullet.prefab');
            $bullet->transform->position = $this->transform->position;
        }
    }
}
Core

Preferences

`Preferences` is the PHP API for small engine-managed local settings.

Namespace:

namespace Lenga\Engine\Core;

final class Preferences

Use Preferences for values such as volume, language, fullscreen, mouse sensitivity, and tiny non-critical flags. Do not use it for full save-game data or sensitive information.

Preferences are loaded from an engine-managed per-user location. Script code reads and writes values through the Preferences API instead of choosing an arbitrary file path.

Methods

getInt

public static function getInt(string $key, int $defaultValue = 0): int

Returns the saved integer value for $key.

If the key does not exist, Lenga returns $defaultValue.

setInt

public static function setInt(string $key, int $value): void

Sets an integer preference in memory.

Call save() when you want to write the change to disk.

getFloat

public static function getFloat(string $key, float $defaultValue = 0.0): float

Returns the saved float value for $key, or $defaultValue when the key does not exist.

setFloat

public static function setFloat(string $key, float $value): void

Sets a float preference in memory.

getString

public static function getString(string $key, string $defaultValue = ''): string

Returns the saved string value for $key, or $defaultValue when the key does not exist.

setString

public static function setString(string $key, string $value): void

Sets a string preference in memory.

getBool

public static function getBool(string $key, bool $defaultValue = false): bool

Returns the saved boolean value for $key, or $defaultValue when the key does not exist.

setBool

public static function setBool(string $key, bool $value): void

Sets a boolean preference in memory.

hasKey

public static function hasKey(string $key): bool

Returns true when a preference exists for $key.

deleteKey

public static function deleteKey(string $key): void

Removes one preference from memory.

Call save() to persist the removal.

deleteAll

public static function deleteAll(): void

Removes all preferences from memory.

Call save() to persist the reset.

save

public static function save(): bool

Writes the current in-memory preferences to disk.

Returns true when the save succeeds.

Example

The code below belongs in a Behaviour class attached to a normal GameObject.

<?php

namespace Game\Scripts;

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Preferences;

final class SettingsController extends Behaviour
{
    public function start(): void
    {
        $volume = Preferences::getFloat('audio.masterVolume', 0.8);

        // Apply $volume to your audio settings here.
    }

    public function setMasterVolume(float $volume): void
    {
        Preferences::setFloat('audio.masterVolume', $volume);
        Preferences::save();
    }
}

Usage Notes

Preferences is for small local settings and simple scalar state.

It is not a save-game system, a local database, or a secure secret store.

Core

Core Project Structure

Lenga expects a project folder to contain these key pieces:

MyGame/
  Assets/
  ProjectSettings/
    lenga.json
  bootstrap.php
  composer.json

If one of the important files is missing, the editor shows a validation error instead of opening the project in a broken state.

composer.json should declare the project's PHP autoload rules and require lenga/engine, which provides the PHP gameplay API used by project scripts.

Core

Quaternion

Namespace:

namespace Lenga\Engine\Core;

class Quaternion

Quaternion is Lenga's 3D rotation value type.

Use it when you want to:

  • store a 3D rotation safely
  • combine rotations without depending on Euler-angle addition
  • rotate a direction vector such as Vector3::back()
  • work with Transform::$rotation and Transform::$localRotation

Common Methods

identity

public static function identity(): self

Returns the identity rotation.

fromEulerAngles

public static function fromEulerAngles(Vector3 $degrees): self

Creates a quaternion from Euler angles in degrees.

toEulerAngles

public function toEulerAngles(): Vector3

Converts the quaternion back to Euler angles in degrees.

multiply

public function multiply(Quaternion $other): self

Combines this rotation with another rotation.

inverse

public function inverse(): self

Returns the inverse rotation.

rotateVector

public function rotateVector(Vector3 $vector): Vector3

Rotates a direction or offset vector by this quaternion.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Quaternion;
use Lenga\Engine\Core\Vector3;

class TurretAim extends Behaviour
{
    public function update(): void
    {
        $yaw = Quaternion::fromEulerAngles(new Vector3(0.0, 90.0, 0.0));
        $pitch = Quaternion::fromEulerAngles(new Vector3(-15.0, 0.0, 0.0));

        $this->transform->localRotation = $yaw->multiply($pitch);

        $forward = $this->transform->rotation->rotateVector(Vector3::back());
    }
}

Usage Notes

  • Use Transform::$rotation for world rotation.
  • Use Transform::$localRotation for rotation relative to the parent.
  • Use fromEulerAngles(...) when you want to start from readable degree values.
  • Use toEulerAngles() when you need to present a quaternion as Euler angles.
Core

RaycastHit2D

Namespace:

namespace Lenga\Engine\Core;

class RaycastHit2D

Methods

fromNativeData

public static function fromNativeData(array $data)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Physics2D;
use Lenga\Engine\Core\Vector3;

class RaycastExample extends Behaviour
{
    public function update(): void
    {
        $origin = $this->transform->position;
        $direction = new Vector3(1, 0, 0);

        // Cast a ray and get hit information
        $hit = Physics2D::raycast($origin, $direction, 100.0);

        if ($hit !== null) {
            // Access hit information
            $hitObject = $hit->gameObject;
            $hitDistance = $hit->distance;
            $hitPoint = $hit->point;
            $hitNormal = $hit->normal;

            Debug::log('Hit object: ' . $hitObject->name);
            Debug::log('Distance: ' . $hitDistance);
        }
    }
}
Core

RaycastHit3D

Namespace:

namespace Lenga\Engine\Core;

class RaycastHit3D

Methods

fromNativeData

public static function fromNativeData(array $data)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Physics3D;
use Lenga\Engine\Core\Vector3;

class WeaponRaycast extends Behaviour
{
    public function update(): void
    {
        if (Input::getMouseButtonDown(0)) {
            // Cast a ray from camera forward
            $camera = $this->getComponent(Camera::class);
            $origin = $camera->transform->position;
            $direction = $camera->transform->forward;

            // Raycast from camera
            $hit = Physics3D::raycast($origin, $direction, 1000.0);

            if ($hit !== null) {
                $targetObject = $hit->gameObject;
                $impactPoint = $hit->point;

                Debug::log('Shot ' . $targetObject->name);
                // Apply damage, create impact effect, etc.
            }
        }
    }
}
Core

RectangleRenderer

Namespace:

namespace Lenga\Engine\Core;

class RectangleRenderer

Methods

setSize

public function setSize(float $width, float $height)

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\RectangleRenderer;

class UIPanel extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(RectangleRenderer::class);
        $renderer->setSize(2.0, 3.0);
        $renderer->setColor(50, 50, 200, 200); // Semi-transparent blue
    }
}
Core

Rigidbody2D

2D rigid body physics component bound to the native engine runtime.

Namespace:

namespace Lenga\Engine\Core;

class Rigidbody2D

Methods

isTouching

public function isTouching(bool $includeTriggers = true, ?int $layerMask = null)

Rigid body mode used by the physics solver (for example: Dynamic, Kinematic, Static).

isGrounded

public function isGrounded(float $minSupportDot = 0.5,
        bool $includeTriggers = false,
        ?int $layerMask = null,)

Returns true when this body is resting on a supporting surface.

getContacts

public function getContacts(bool $includeTriggers = true, ?int $layerMask = null)

Returns all current contacts that match the query.

addForce

public function addForce(Vector2 $force, ForceMode2D $mode = ForceMode2D::Force)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Rigidbody2D;
use Lenga\Engine\Core\Vector2;
use Lenga\Engine\Core\ForceMode2D;

class PlayerMovement2D extends Behaviour
{
    public float $jumpForce = 10.0;

    public function update(): void
    {
        $rb = $this->getComponent(Rigidbody2D::class);

        // Apply horizontal movement
        $moveInput = Input::getAxis('Horizontal');
        $rb->velocity = new Vector2($moveInput * 5.0, $rb->velocity->y);

        // Jump when grounded
        if (Input::getKeyDown(KeyCode::SPACE) && $rb->isGrounded()) {
            $rb->addForce(new Vector2(0, $this->jumpForce), ForceMode2D::Impulse);
        }
    }
}
Core

Signal

Namespace:

namespace Lenga\Engine\Core;

class Signal

Signal is an owner-scoped callback surface for one specific behaviour or system instance.

Subscribers still need a reference to the owner in order to subscribe. If you want decoupled gameplay communication where listeners react without knowing the dispatcher ahead of time, use EventBus instead.

If one known object can just call one known collaborator directly, a public method is often simpler than a Signal. Signal is most useful when an owner wants to expose a one-to-many callback point with add(...), once(...), and disposal semantics.

Methods

add

public function add(callable $listener)

addListener

public function addListener(callable $listener)

once

public function once(callable $listener)

onceListener

public function onceListener(callable $listener)

remove

public function remove(SignalSubscription|callable $listener)

removeListener

public function removeListener(SignalSubscription|callable $listener)

clear

public function clear()

hasListeners

public function hasListeners()

count

public function count()

dispatch

public function dispatch(mixed ...$arguments)

invoke

public function invoke(mixed ...$arguments)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Signal;
use Lenga\Engine\Core\SignalSubscription;

final class Door extends Behaviour
{
    public Signal $onOpened;

    public function awake(): void
    {
        $this->onOpened = $this->createSignal();
    }

    public function open(): void
    {
        $this->onOpened->dispatch($this);
    }
}

final class DoorIndicator extends Behaviour
{
    public ?Door $door = null;
    private ?SignalSubscription $subscription = null;

    public function onEnable(): void
    {
        $this->subscription = $this->door?->onOpened->add(function (Door $door): void {
            $this->flashIndicator();
        });
    }

    public function onDisable(): void
    {
        $this->subscription?->dispose();
        $this->subscription = null;
    }

    private function flashIndicator(): void
    {
    }
}

This pattern fits when the listener already knows which owner instance it wants to observe.

Core

SignalSubscription

Namespace:

namespace Lenga\Engine\Core;

class SignalSubscription

Methods

dispose

public function dispose()

isDisposed

public function isDisposed()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Signal;
use Lenga\Engine\Core\SignalSubscription;

final class Door extends Behaviour
{
    public Signal $onOpened;

    public function awake(): void
    {
        $this->onOpened = $this->createSignal();
    }

    public function open(): void
    {
        $this->onOpened->dispatch($this);
    }
}

final class DoorIndicator extends Behaviour
{
    public ?Door $door = null;
    private ?SignalSubscription $subscription = null;

    public function onEnable(): void
    {
        $this->subscription = $this->door?->onOpened->add(function (Door $door): void {
            $this->flashIndicator();
        });
    }

    public function onDisable(): void
    {
        if ($this->subscription !== null && !$this->subscription->isDisposed()) {
            $this->subscription->dispose();
        }

        $this->subscription = null;
    }

    private function flashIndicator(): void
    {
    }
}
Core

SphereCollider3D

Namespace:

namespace Lenga\Engine\Core;

class SphereCollider3D

Methods

isTouching

public function isTouching(bool $includeTriggers = true, ?int $layerMask = null)

getContacts

public function getContacts(bool $includeTriggers = true, ?int $layerMask = null)

moveAndSlide

public function moveAndSlide(Vector3 $delta,
        float $skinWidth = 0.05,
        bool $includeTriggers = false,
        ?int $layerMask = null,)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\SphereCollider3D;

class BallPhysics extends Behaviour
{
    public function start(): void
    {
        $collider = $this->getComponent(SphereCollider3D::class);
        $collider->radius = 0.5;
        $collider->center = [0, 0, 0];
    }

    public function update(): void
    {
        $collider = $this->getComponent(SphereCollider3D::class);

        // Check if touching any objects
        if ($collider->isTouching()) {
            $contacts = $collider->getContacts();
            foreach ($contacts as $contact) {
                Debug::log('In contact with: ' . $contact->gameObject->name);
            }
        }
    }
}
Core

SphereRenderer

Namespace:

namespace Lenga\Engine\Core;

class SphereRenderer

Methods

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\SphereRenderer;

class SphereVisual extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(SphereRenderer::class);
        $renderer->setRadius(0.5);
        $renderer->setColor(255, 0, 0, 255); // Red sphere
    }
}
Core

SpriteAnimation

Namespace:

namespace Lenga\Engine\Core;

final class SpriteAnimation extends Component

SpriteAnimation plays a single clip or drives a sprite animation controller on a GameObject.

Use it when gameplay code needs to:

  • play or stop a clip directly
  • switch the active controller state
  • set animator parameters from gameplay code
  • fire one-shot animation triggers

Getting the Component

The usual workflow is:

  1. Add Sprite Renderer to a GameObject.
  2. Add Sprite Animation to the same GameObject.
  3. Assign either a clip or a controller in the Inspector.
  4. From PHP, call $this->getComponent(SpriteAnimation::class).
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\SpriteAnimation;

final class PlayerAnimator extends Behaviour
{
    private ?SpriteAnimation $animator = null;

    public function start(): void
    {
        $this->animator = $this->getComponent(SpriteAnimation::class);
    }
}

Properties

clipPath

public string $clipPath

Project-relative path to the clip that should play when you are not using a controller.

controllerPath

public string $controllerPath

Project-relative path to the animation controller asset.

state

public string $state

Gets or sets the currently selected state name.

This is most useful when you want to force a state directly instead of waiting for controller conditions.

playOnAwake

public bool $playOnAwake

Controls whether the animation starts automatically when the component starts.

speed

public float $speed

Playback speed multiplier for the current clip or controller output.

Methods

isPlaying

public function isPlaying(): bool

Returns whether the animation is currently playing.

play

public function play(): bool

Starts playback.

stop

public function stop(): bool

Stops playback.

getBool

public function getBool(string $parameterName): bool

Returns the current value of a bool controller parameter.

setBool

public function setBool(string $parameterName, bool $value): bool

Sets a bool controller parameter.

getInt

public function getInt(string $parameterName): int

Returns the current value of an int controller parameter.

setInt

public function setInt(string $parameterName, int $value): bool

Sets an int controller parameter.

getFloat

public function getFloat(string $parameterName): float

Returns the current value of a float controller parameter.

setFloat

public function setFloat(string $parameterName, float $value): bool

Sets a float controller parameter.

setTrigger

public function setTrigger(string $parameterName): bool

Fires a trigger parameter for one-shot transitions such as attacks, hits, and jumps.

resetTrigger

public function resetTrigger(string $parameterName): bool

Clears a trigger parameter.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Core\SpriteAnimation;

class CharacterAnimator extends Behaviour
{
    private ?SpriteAnimation $animator = null;

    public function start(): void
    {
        $this->animator = $this->getComponent(SpriteAnimation::class);
    }

    public function update(): void
    {
        if ($this->animator === null) {
            return;
        }

        $move = Input::getAxis('Horizontal');
        $speed = abs($move) * 8.0;

        $this->animator->setBool('isWalking', $speed > 0.05);
        $this->animator->setBool('isRunning', $speed > 6.0);
        $this->animator->setFloat('speed', $speed);

        if (Input::getButtonDown('Attack')) {
            $this->animator->setInt('comboStep', $this->animator->getInt('comboStep') + 1);
            $this->animator->setTrigger('lightAttack');
        }
    }
}

Parameter Notes

  • Parameter names must match the controller exactly.
  • Use bools for persistent conditions such as isGrounded.
  • Use ints for discrete steps such as comboStep.
  • Use floats for threshold- or comparison-based motion such as speed.
  • Use triggers for one-shot transitions such as lightAttack.
Core

SpriteRenderer

Namespace:

namespace Lenga\Engine\Core;

class SpriteRenderer

Methods

setSize

public function setSize(float $width, float $height)

getColor

public function getColor()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\SpriteRenderer;

class PlayerSprite extends Behaviour
{
    public function start(): void
    {
        $renderer = $this->getComponent(SpriteRenderer::class);
        $renderer->loadSprite('assets/sprites/player.png');
        $renderer->setSize(1.0, 1.0);
        $renderer->setColor(255, 255, 255, 255);
    }

    public function update(): void
    {
        $renderer = $this->getComponent(SpriteRenderer::class);
        // Fade effect
        $alpha = (int)(255 * (0.5 + 0.5 * MathUtil::sin(Time::time() * 2)));
        $color = $renderer->getColor();
        $renderer->setColor($color['r'], $color['g'], $color['b'], $alpha);
    }
}
Core

Time

C++ can update these internal values via the embed SAPI / extension.

Namespace:

namespace Lenga\Engine\Core;

class Time

Methods

time

public static function time()

C++ can update these internal values via the embed SAPI / extension.

unscaledTime

public static function unscaledTime()

Returns the real time elapsed since application start, unaffected by pause.

deltaTime

public static function deltaTime()

Returns the time elapsed since the last frame.

unscaledDeltaTime

public static function unscaledDeltaTime()

Returns the real frame delta time, unaffected by pause.

fixedDeltaTime

public static function fixedDeltaTime()

Returns the fixed delta time value.

fixedStepCount

public static function fixedStepCount()

isPaused

public static function isPaused()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Time;

class MovementController extends Behaviour
{
    public float $speed = 5.0;

    public function update(): void
    {
        // Frame-rate-independent movement
        $moveDistance = $this->speed * Time::deltaTime();
        $this->transform->translate($moveDistance, 0, 0);

        // Get time since game started
        $elapsedTime = Time::time();
        Debug::log('Time elapsed: ' . $elapsedTime);
    }

    public function fixedUpdate(): void
    {
        // Physics updates use fixed delta time
        $fixedDelta = Time::fixedDeltaTime();
        // Apply physics calculations with consistent timestep
    }
}
Core

Transform

Namespace:

namespace Lenga\Engine\Core;

class Transform

Transform stores an object's position, rotation, and scale.

When an object has a parent:

  • localPosition is the offset from that parent
  • localRotation is the rotation relative to that parent
  • localScale is the scale relative to that parent

When you want the final world-space values, use:

  • position
  • rotation
  • lossyScale

Rotation in Lenga

Lenga supports two ways to work with 3D rotation:

  • Euler-angle properties such as $localEulerAngles and $eulerAngles
  • quaternion properties such as $localRotation and $rotation

The Inspector is still mainly Euler-facing in this release, but runtime 3D transform composition uses quaternion-backed rotation math. That means you can author readable Euler values in the editor and still use safer quaternion rotation workflows in code.

Common Properties

Property Type What it means
$localPosition Vector3 Position relative to the parent.
$position Vector3 World-space position.
$localEulerAngles Vector3 Local rotation in degrees.
$eulerAngles Vector3 World rotation in degrees.
$localRotation Quaternion Local rotation relative to the parent.
$rotation Quaternion World rotation.
$localScale Vector3 Scale relative to the parent.
$lossyScale Vector3 Final scale after parent scaling is applied.
$right Vector3 World-space right direction.
$up Vector3 World-space up direction.
$forward Vector3 World-space forward direction.

Methods

getChildren

public function getChildren(): array

Returns the direct child transforms.

childCount

public function childCount(): int

Returns the number of direct children.

getChild

public function getChild(int $index): ?Transform

Returns the child at the given index, or null.

find

public function find(string $path): ?Transform

Finds a child by a slash-separated path such as "Weapon/Muzzle".

isChildOf

public function isChildOf(Transform $parent): bool

detachChildren

public function detachChildren(): void

setParent

public function setParent(?Transform $newParent, bool $worldPositionStays = true): void

Changes the parent transform.

When worldPositionStays is true, Lenga keeps the object's world-space position, rotation, and scale, then recalculates the local values under the new parent.

When worldPositionStays is false, Lenga keeps the current local values and lets the final world transform change relative to the new parent.

translate

public function translate(Vector3 $offset, bool $relativeToSelf = true): void

Moves the transform by the given offset.

  • When relativeToSelf is true, the offset is applied in the object's own rotated space.
  • When relativeToSelf is false, the offset is applied in world space.

translate2D

public function translate2D(float $dx, float $dy, bool $relativeToSelf = true): void

rotate

public function rotate(Vector3|float $xOrEuler, ?float $y = null, ?float $z = null, bool $relativeToSelf = true): void

Applies a rotation delta.

  • When relativeToSelf is true, the delta is applied in local space.
  • When relativeToSelf is false, the delta is applied in world space.

For more deliberate 3D rotation control, set $localRotation or $rotation directly with Quaternion.

rotate2D

public function rotate2D(float $degrees, bool $relativeToSelf = true): void

lookAt

public function lookAt(Vector3 $worldPosition): void

Rotates the object so it faces the given world-space point.

getNativeId

public function getNativeId(): ?int

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Quaternion;
use Lenga\Engine\Core\Time;
use Lenga\Engine\Core\Vector3;

class ObjectController extends Behaviour
{
    public function update(): void
    {
        $transform = $this->transform;

        $worldPosition = $transform->position;
        $worldRotation = $transform->rotation;
        $worldEuler = $transform->eulerAngles;

        $transform->position = new Vector3(
            $worldPosition->x + (1.0 * Time::deltaTime()),
            $worldPosition->y,
            $worldPosition->z,
        );

        $transform->rotate(0.0, 90.0 * Time::deltaTime(), 0.0);

        $transform->localRotation = Quaternion::fromEulerAngles(
            new Vector3(0.0, 45.0, 0.0)
        );

        $transform->translate(
            new Vector3(0.0, 0.0, -1.0 * Time::deltaTime())
        );

        $forward = $transform->forward;
        $right = $transform->right;
    }
}

Usage Notes

  • Use Euler angles when you want readable degree values.
  • Use quaternions when you want safer 3D rotation composition in code.
  • The scene format and Inspector still use Euler-facing values for authoring, but runtime transform composition is quaternion-backed for 3D workflows.
Core

Vector2

2D vector value type mirroring 's .Vector2.

Namespace:

namespace Lenga\Engine\Core;

class Vector2

Methods

down

public static function down()

Unit vector pointing down (0, -1).

up

public static function up()

Unit vector pointing up (0, 1).

left

public static function left()

Unit vector pointing left (-1, 0).

right

public static function right()

Unit vector pointing right (1, 0).

one

public static function one()

Vector with all components set to 1.

zero

public static function zero()

Vector with all components set to 0.

negativeInfinity

public static function negativeInfinity()

Shorthand for Vector2(-INF, -INF).

positiveInfinity

public static function positiveInfinity()

Shorthand for Vector2(INF, INF).

offsetExists

public function offsetExists(mixed $offset)

Checks whether an array offset maps to a valid vector component.

offsetGet

public function offsetGet(mixed $offset)

Gets a vector component by index: 0|'x' => x, 1|'y' => y.

offsetSet

public function offsetSet(mixed $offset, mixed $value)

Sets a vector component by index: 0|'x' => x, 1|'y' => y.

offsetUnset

public function offsetUnset(mixed $offset)

Unsets a vector component by index.

add

public function add(Vector2 $other)

Adds another vector to this vector in place.

subtract

public function subtract(Vector2 $other)

Subtracts another vector from this vector in place.

multiply

public function multiply(Vector2 $other)

Multiplies this vector component-wise by another vector in place.

divide

public function divide(Vector2 $other)

Divides this vector component-wise by another vector in place.

scale

public function scale(float $scalar)

Scales this vector by a scalar in place.

normalize

public function normalize()

Normalizes this vector to have a magnitude of 1 in place.

set

public function set(float $x, float $y)

Sets the x and y components of this vector.

equals

public function equals(Vector2 $other)

Returns true if this vector is exactly equal to the other vector.

__toString

public function __toString()

Returns a formatted string representation of this vector.

toVector3

public function toVector3(float $z = 0.0)

Converts this Vector2 to a Vector3 with an optional z component.

fromVector4

public static function fromVector4(Vector4 $v)

Converts a Vector4 to a Vector2 by dropping the z and w components.

fromVector3

public static function fromVector3(Vector3 $v)

Converts a Vector3 to a Vector2 by dropping the z component.

toVector4

public function toVector4(float $z = 0.0, float $w = 0.0)

Converts this Vector2 to a Vector4 with optional z and w components.

sum

public static function sum(Vector2 $a, Vector2 $b)

Returns the sum of two vectors.

difference

public static function difference(Vector2 $a, Vector2 $b)

Returns the difference of two vectors.

scaleNew

public static function scaleNew(Vector2 $v, float $scalar)

Returns a new vector scaled by a scalar.

product

public static function product(Vector2 $a, Vector2 $b)

Multiplies two vectors component-wise and returns the result.

dot

public static function dot(Vector2 $a, Vector2 $b)

Returns the dot product of two vectors.

angle

public static function angle(Vector2 $from, Vector2 $to)

Returns the unsigned angle in degrees between two vectors.

signedAngle

public static function signedAngle(Vector2 $from, Vector2 $to)

Returns the signed angle in degrees between two vectors.

distance

public static function distance(Vector2 $a, Vector2 $b)

Returns the distance between two points.

lerp

public static function lerp(Vector2 $a, Vector2 $b, float $t)

Linearly interpolates between two vectors by t, clamped to [0, 1].

lerpUnclamped

public static function lerpUnclamped(Vector2 $a, Vector2 $b, float $t)

Linearly interpolates between two vectors by t, without clamping.

max

public static function max(Vector2 $a, Vector2 $b)

Returns a vector made from the largest components of two vectors.

min

public static function min(Vector2 $a, Vector2 $b)

Returns a vector made from the smallest components of two vectors.

clampMagnitude

public static function clampMagnitude(Vector2 $vector, float $maxLength)

Returns a copy of $vector with its magnitude clamped to $maxLength.

moveTowards

public static function moveTowards(Vector2 $current, Vector2 $target, float $maxDelta)

Moves $current towards $target by at most $maxDelta.

perpendicular

public static function perpendicular(Vector2 $inDirection)

Returns the 2D vector perpendicular to the given direction,

reflect

public static function reflect(Vector2 $inDirection, Vector2 $inNormal)

Reflects a vector off the surface defined by a normal.

smoothDamp

public static function smoothDamp(Vector2 $current,
        Vector2 $target,
        Vector2 &$currentVelocity,
        float $smoothTime,
        float $maxSpeed = PHP_FLOAT_MAX,
        float $deltaTime = 0.0,)

Gradually changes a vector towards a desired goal over time using a spring-damper.

fromVector3

public static function fromVector3(Vector3 $v)

Converts a Vector3 to a Vector2 by dropping the z component.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Vector2;
use Lenga\Engine\Core\MathUtil;

class VelocityController extends Behaviour
{
    public function update(): void
    {
        // Create a vector
        $velocity = new Vector2(5.0, 3.0);

        // Get magnitude and normalize
        $speed = $velocity->magnitude();
        $direction = $velocity->normalized();

        // Vector operations
        $acceleration = new Vector2(1.0, 2.0);
        $newVelocity = Vector2::add($velocity, $acceleration);

        // Dot product and angle
        $dot = Vector2::dot($velocity, $acceleration);
        $distance = Vector2::distance($velocity, $acceleration);

        // Lerp between two vectors
        $start = new Vector2(0, 0);
        $end = new Vector2(10, 10);
        $midpoint = Vector2::lerp($start, $end, 0.5);
    }
}
Core

Vector3

3D vector value type with mutating and pure helper operations.

Namespace:

namespace Lenga\Engine\Core;

class Vector3

Methods

zero

public static function zero()

Vector with all components set to 0.

one

public static function one()

Vector with all components set to 1.

up

public static function up()

Unit vector pointing up (0, 1, 0).

down

public static function down()

Unit vector pointing down (0, -1, 0).

left

public static function left()

Unit vector pointing left (-1, 0, 0).

right

public static function right()

Unit vector pointing right (1, 0, 0).

forward

public static function forward()

Unit vector pointing forward (0, 0, 1).

back

public static function back()

Unit vector pointing back (0, 0, -1).

negativeInfinity

public static function negativeInfinity()

Shorthand for Vector3(-INF, -INF, -INF).

positiveInfinity

public static function positiveInfinity()

Shorthand for Vector3(INF, INF, INF).

add

public function add(Vector3 $other)

Adds another vector to this vector in place.

subtract

public function subtract(Vector3 $other)

Subtracts another vector from this vector in place.

multiply

public function multiply(Vector3 $other)

Multiplies this vector component-wise by another vector in place.

divide

public function divide(Vector3 $other)

Divides this vector component-wise by another vector in place.

scale

public function scale(float $scalar)

Scales this vector by a scalar in place.

normalize

public function normalize()

Normalizes the vector to have a magnitude of 1 in place.

set

public function set(float $x, float $y, float $z)

Sets the x, y, and z components of this vector.

clone

public function clone()

Creates and returns a clone of this vector.

equals

public function equals(Vector3 $other)

Checks if this vector is exactly equal to another vector.

__toString

public function __toString()

Returns a formatted string representation of this vector.

toVector4

public function toVector4(float $w = 0.0)

Converts this Vector3 to a Vector4 with an optional w component.

toVector2

public function toVector2()

Converts this Vector3 to a Vector2 by dropping the z component.

toArray

public function toArray()

@return array{x: float, y: float, z: float}

fromArray

public static function fromArray(array $data)

@param array{x?: float, y?: float, z?: float} $data

fromVector4

public static function fromVector4(Vector4 $vector)

Builds a Vector3 from a Vector4 by dropping the w component.

fromVector2

public static function fromVector2(Vector2 $vector, float $z = 0.0)

Builds a Vector3 from a Vector2 with an optional z component.

sum

public static function sum(Vector3 $a, Vector3 $b)

Returns the sum of two vectors.

difference

public static function difference(Vector3 $a, Vector3 $b)

Returns the difference of two vectors.

product

public static function product(Vector3 $a, Vector3 $b)

Multiplies two vectors component-wise and returns the result.

quotient

public static function quotient(Vector3 $a, Vector3 $b)

Returns the quotient of two vectors.

scaleNew

public static function scaleNew(Vector3 $vector, float $scalar)

Returns a new vector scaled by a scalar.

dot

public static function dot(Vector3 $a, Vector3 $b)

Returns the dot product of two vectors.

cross

public static function cross(Vector3 $a, Vector3 $b)

Returns the cross product of two vectors.

angle

public static function angle(Vector3 $from, Vector3 $to)

Returns the unsigned angle in degrees between two vectors.

signedAngle

public static function signedAngle(Vector3 $from, Vector3 $to, Vector3 $axis)

Returns the signed angle in degrees between two vectors around an axis.

distance

public static function distance(Vector3 $a, Vector3 $b)

Returns the distance between two points.

lerp

public static function lerp(Vector3 $a, Vector3 $b, float $t)

Linearly interpolates between two vectors by t, clamped to [0, 1],

lerpUnclamped

public static function lerpUnclamped(Vector3 $a, Vector3 $b, float $t)

@var array{x: float, y: float, z: float}|false $result */

slerp

public static function slerp(Vector3 $a, Vector3 $b, float $t)

Spherically interpolates between two vectors, treating them as directions.

slerpUnclamped

public static function slerpUnclamped(Vector3 $a, Vector3 $b, float $t)

@var array{x: float, y: float, z: float}|false $result */

max

public static function max(Vector3 $a, Vector3 $b)

Returns a vector made from the largest components of two vectors.

min

public static function min(Vector3 $a, Vector3 $b)

Returns a vector made from the smallest components of two vectors.

clampMagnitude

public static function clampMagnitude(Vector3 $vector, float $maxLength)

Returns a copy of vector with its magnitude clamped to maxLength.

moveTowards

public static function moveTowards(Vector3 $current, Vector3 $target, float $maxDelta)

Moves current towards target by at most maxDelta.

project

public static function project(Vector3 $vector, Vector3 $onNormal)

Projects a vector onto another vector.

projectOnPlane

public static function projectOnPlane(Vector3 $vector, Vector3 $planeNormal)

Projects a vector onto a plane defined by a normal.

reflect

public static function reflect(Vector3 $inDirection, Vector3 $inNormal)

Reflects a vector off the plane defined by a normal vector.

rotateTowards

public static function rotateTowards(Vector3 $current,
        Vector3 $target,
        float $maxRadiansDelta,
        float $maxMagnitudeDelta,)

Rotates current towards target by an angular step, while also moving the

smoothDamp

public static function smoothDamp(Vector3 $current,
        Vector3 $target,
        Vector3 &$currentVelocity,
        float $smoothTime,
        float $maxSpeed = PHP_FLOAT_MAX,
        ?float $deltaTime = null,)

Gradually changes a vector towards a target over time using a spring-like damper.

orthoNormalize

public static function orthoNormalize(Vector3 &$normal, Vector3 &$tangent)

@var array{

offsetExists

public function offsetExists(mixed $offset)

Checks whether an array offset maps to a valid vector component.

offsetGet

public function offsetGet(mixed $offset)

Gets a vector component by index: 0|'x' => x, 1|'y' => y, 2|'z' => z.

offsetSet

public function offsetSet(mixed $offset, mixed $value)

Sets a vector component by index: 0|'x' => x, 1|'y' => y, 2|'z' => z.

offsetUnset

public function offsetUnset(mixed $offset)

Unsets a vector component by index.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Core\Time;

class Movement3D extends Behaviour
{
    public function update(): void
    {
        // Create vectors for positions and directions
        $position = $this->transform->position;
        $target = new Vector3(10, 0, 10);

        // Calculate direction and distance
        $direction = Vector3::subtract($target, $position)->normalized();
        $distance = Vector3::distance($position, $target);

        // Move towards target
        $moveAmount = 5.0 * Time::deltaTime();
        $newPosition = Vector3::add($position, Vector3::scale($direction, $moveAmount));
        $this->transform->position = $newPosition;

        // Perform vector operations
        $velocity = new Vector3(1, 0, 0);
        $gravity = new Vector3(0, -9.8, 0);
        $netForce = Vector3::add($velocity, Vector3::scale($gravity, Time::deltaTime()));

        // Cross product and rotation
        $forward = $this->transform->forward;
        $up = new Vector3(0, 1, 0);
        $right = Vector3::cross($forward, $up);
    }
}
Core

Vector4

4D vector value type with native bridge support for interpolation, projection, clamped length, and movement helpers.

Namespace: Lenga\Engine\Core

Key Members

  • x, y, z, w: scalar components.
  • sqrMagnitude, magnitude, normalized: common length helpers.
  • toVector2(), toVector3(): dimensional down-conversion helpers.
  • fromVector2(...), fromVector3(...): dimensional up-conversion helpers.

Common Methods

  • public static function zero()
  • public static function one()
  • public function add(Vector4 $other)
  • public function subtract(Vector4 $other)
  • public function multiply(Vector4 $other)
  • public function divide(Vector4 $other)
  • public function scale(float $scalar)
  • public function normalize()
  • public function set(float $x, float $y, float $z, float $w)
  • public static function dot(Vector4 $a, Vector4 $b)
  • public static function distance(Vector4 $a, Vector4 $b)
  • public static function lerp(Vector4 $a, Vector4 $b, float $t)
  • public static function lerpUnclamped(Vector4 $a, Vector4 $b, float $t)
  • public static function max(Vector4 $a, Vector4 $b)
  • public static function min(Vector4 $a, Vector4 $b)
  • public static function clampMagnitude(Vector4 $vector, float $maxLength)
  • public static function moveTowards(Vector4 $current, Vector4 $target, float $maxDelta)
  • public static function project(Vector4 $vector, Vector4 $onNormal)

Use Vector4 when you need four-channel math, homogeneous coordinates, packed data, or cross-conversion with Vector2 and Vector3 without losing the familiar scripting API shape.

Core

WaitForFixedUpdate

Namespace:

namespace Lenga\Engine\Core;

class WaitForFixedUpdate

Methods

resumePhase

public function resumePhase()

keepWaiting

public function keepWaiting()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\WaitForFixedUpdate;

class PhysicsSynchronizedAction extends Behaviour
{
    public function start(): void
    {
        $this->startCoroutine($this->physicsSync());
    }

    private function physicsSync(): \Generator
    {
        Debug::log('Waiting for physics frame...');

        // Wait until the next fixed update
        yield new WaitForFixedUpdate();

        Debug::log('One physics frame has completed');
        // Can now safely access physics simulation results
    }
}
Core

WaitForSeconds

Namespace:

namespace Lenga\Engine\Core;

class WaitForSeconds

Methods

getDuration

public function getDuration()

keepWaiting

public function keepWaiting()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\WaitForSeconds;

class DelayedAction extends Behaviour
{
    public function start(): void
    {
        $this->startCoroutine($this->delayedExplosion());
    }

    private function delayedExplosion(): \Generator
    {
        Debug::log('Explosion will happen in 3 seconds...');

        // Wait 3 real-time seconds
        yield new WaitForSeconds(3.0);

        Debug::log('Explosion!');
        $this->explode();
    }

    private function explode(): void
    {
        // Explosion logic here
    }
}
Core

WaitForSecondsRealtime

Namespace:

namespace Lenga\Engine\Core;

class WaitForSecondsRealtime

Methods

getDuration

public function getDuration()

keepWaiting

public function keepWaiting()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\WaitForSecondsRealtime;

class PauseInsensitiveTimer extends Behaviour
{
    public function start(): void
    {
        $this->startCoroutine($this->timedAction());
    }

    private function timedAction(): \Generator
    {
        Debug::log('Action starts now');

        // Wait 2 real seconds, even if game is paused
        yield new WaitForSecondsRealtime(2.0);

        Debug::log('2 real seconds have passed, regardless of pause state');
    }
}
Core

WaitUntil

Namespace:

namespace Lenga\Engine\Core;

class WaitUntil

Methods

keepWaiting

public function keepWaiting()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\WaitUntil;

class ConditionWaiter extends Behaviour
{
    private bool $conditionMet = false;

    public function start(): void
    {
        $this->startCoroutine($this->waitForCondition());
    }

    public function update(): void
    {
        // Some condition check
        if (Input::getKeyDown(KeyCode::SPACE)) {
            $this->conditionMet = true;
        }
    }

    private function waitForCondition(): \Generator
    {
        Debug::log('Waiting for condition...');

        // Wait until condition is true
        yield new WaitUntil(function() {
            return $this->conditionMet;
        });

        Debug::log('Condition was met!');
    }
}
Core

WaitWhile

Namespace:

namespace Lenga\Engine\Core;

class WaitWhile

Methods

keepWaiting

public function keepWaiting()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\WaitWhile;

class WaitForStop extends Behaviour
{
    private bool $isMoving = false;

    public function start(): void
    {
        $this->startCoroutine($this->waitUntilStopped());
    }

    public function update(): void
    {
        // Control movement
        if (Input::getKey(KeyCode::W)) {
            $this->isMoving = true;
        } else {
            $this->isMoving = false;
        }
    }

    private function waitUntilStopped(): \Generator
    {
        Debug::log('Waiting while moving...');

        // Wait while condition is true (wait until moving stops)
        yield new WaitWhile(function() {
            return $this->isMoving;
        });

        Debug::log('Object has stopped moving!');
    }
}
Core

YieldInstruction

Namespace:

namespace Lenga\Engine\Core;

class YieldInstruction

Methods

resumePhase

public function resumePhase()

keepWaiting

public function keepWaiting()

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\YieldInstruction;
use Lenga\Engine\Core\WaitForSeconds;

class CustomYieldExample extends Behaviour
{
    public function start(): void
    {
        $this->startCoroutine($this->myCoroutine());
    }

    private function myCoroutine(): \Generator
    {
        // YieldInstruction is the base class for all yield instructions
        // You typically use concrete subclasses like:

        // Wait for a fixed time
        yield new WaitForSeconds(1.0);

        Debug::log('After waiting 1 second');

        // You can also create custom yield instructions by extending YieldInstruction
    }
}
Enumerations

CanvasRenderMode

Namespace:

namespace Lenga\Engine\Enumerations;

Controls how a Canvas is rendered relative to the screen and camera.

Values

Case Value Description
ScreenSpaceOverlay 'ScreenSpaceOverlay' The canvas is rendered directly on top of the screen, independent of any camera. Always appears on top of all scene content.
ScreenSpaceCamera 'ScreenSpaceCamera' The canvas is rendered in screen space but is associated with a camera, allowing it to participate in the camera's field of view.
WorldSpace 'WorldSpace' The canvas exists as a flat object in world space and can be occluded by other geometry.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Enumerations\CanvasRenderMode;
use Lenga\Engine\UI\Canvas;

class HUDManager extends Behaviour
{
    public function start(): void
    {
        $canvas = $this->getComponent(Canvas::class);

        if ($canvas->getRenderMode() === CanvasRenderMode::ScreenSpaceOverlay) {
            // HUD is always on top — safe to show health/score UI
        }
    }
}
Enumerations

CollisionFlags

Namespace:

namespace Lenga\Engine\Enumerations;

Describes which sides of a CharacterController were touching a collider after a move() call. The values are bit flags and can be combined.

Values

Case Value Description
None 0 No collision occurred.
Sides 1 The character is touching a collider on its sides (horizontal collision).
Above 2 The character's top is touching a collider (e.g. a ceiling).
Below 4 The character's bottom is touching a collider (e.g. the ground).

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CharacterController;
use Lenga\Engine\Enumerations\CollisionFlags;

class PlatformerCharacter extends Behaviour
{
    private bool $isGrounded = false;

    public function update(): void
    {
        $cc = $this->getComponent(CharacterController::class);
        $flags = $cc->move([0, -9.8 * Time::deltaTime(), 0]);

        // Check if the character landed on the ground
        $this->isGrounded = (bool)($flags->value & CollisionFlags::Below->value);
    }
}
Enumerations

ForceMode

Namespace:

namespace Lenga\Engine\Enumerations;

Defines how a physics force is applied to a 3D Rigidbody3D. Pass a ForceMode case as the second argument to addForce() and related methods.

Values

Case Value Description
Force 0 Add a continuous force over time, scaled by the body's mass. Use for realistic pushes like wind or a rocket engine.
Acceleration 1 Add a continuous acceleration over time, ignoring mass. All objects accelerate at the same rate regardless of weight.
Impulse 2 Apply an instant force in a single frame, scaled by mass. Use for jumps, explosions, and one-shot hits.
VelocityChange 3 Apply an instant velocity change in a single frame, ignoring mass. All objects receive the same velocity delta.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Rigidbody3D;
use Lenga\Engine\Enumerations\ForceMode;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Enumerations\KeyCode;

class Jumper extends Behaviour
{
    public float $jumpForce = 8.0;

    public function update(): void
    {
        if (Input::getKeyDown(KeyCode::SPACE)) {
            $rb = $this->getComponent(Rigidbody3D::class);
            // Apply an instant upward impulse when the player presses Space
            $rb->addForce([0, $this->jumpForce, 0], ForceMode::Impulse);
        }
    }
}
Enumerations

ForceMode2D

Namespace:

namespace Lenga\Engine\Enumerations;

Defines how a physics force is applied to a 2D Rigidbody2D. Pass a ForceMode2D case as the second argument to addForce() and related methods.

Values

Case Value Description
Force 0 Add a continuous force over time, scaled by the body's mass.
Impulse 1 Apply an instant force in a single frame, scaled by mass. Use for jumps and one-shot hits.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Rigidbody2D;
use Lenga\Engine\Enumerations\ForceMode2D;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Enumerations\KeyCode;

class PlatformerJump extends Behaviour
{
    public float $jumpForce = 12.0;

    public function update(): void
    {
        if (Input::getKeyDown(KeyCode::SPACE)) {
            $rb = $this->getComponent(Rigidbody2D::class);
            $rb->addForce([0, $this->jumpForce], ForceMode2D::Impulse);
        }
    }
}
Enumerations

GamepadAxis

Namespace:

namespace Lenga\Engine\Enumerations;

Identifies a physical axis on a gamepad controller. Use with Input::getAxis() to read analogue stick and trigger values.

Values

Case Value Description
LEFT_X 0 Left analogue stick, horizontal axis.
LEFT_Y 1 Left analogue stick, vertical axis.
RIGHT_X 2 Right analogue stick, horizontal axis.
RIGHT_Y 3 Right analogue stick, vertical axis.
LEFT_TRIGGER 4 Left trigger (LT / L2). Range 0–1.
RIGHT_TRIGGER 5 Right trigger (RT / R2). Range 0–1.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Enumerations\GamepadAxis;

class GamepadMovement extends Behaviour
{
    public float $speed = 5.0;

    public function update(): void
    {
        $horizontal = Input::getAxis(GamepadAxis::LEFT_X->value);
        $vertical   = Input::getAxis(GamepadAxis::LEFT_Y->value);

        $this->transform->translate([
            $horizontal * $this->speed * Time::deltaTime(),
            0,
            $vertical   * $this->speed * Time::deltaTime(),
        ]);
    }
}
Enumerations

GamepadButton

Namespace:

namespace Lenga\Engine\Enumerations;

Identifies a physical button on a gamepad controller. Use with Input::getButton() and Input::getButtonDown().

Values

Case Value Description
UNKNOWN 0 Unknown or unmapped button.
LEFT_FACE_UP 1 D-pad up / left face up (e.g. Y on Xbox, △ on PlayStation).
LEFT_FACE_RIGHT 2 D-pad right / left face right (e.g. B, ○).
LEFT_FACE_DOWN 3 D-pad down / left face down (e.g. A, ✕).
LEFT_FACE_LEFT 4 D-pad left / left face left (e.g. X, □).
RIGHT_FACE_UP 5 Right face up button.
RIGHT_FACE_RIGHT 6 Right face right button.
RIGHT_FACE_DOWN 7 Right face down button.
RIGHT_FACE_LEFT 8 Right face left button.
LEFT_TRIGGER_1 9 Left bumper (LB / L1).
LEFT_TRIGGER_2 10 Left trigger (LT / L2).
RIGHT_TRIGGER_1 11 Right bumper (RB / R1).
RIGHT_TRIGGER_2 12 Right trigger (RT / R2).
MIDDLE_LEFT 13 Select / Back / Share button.
MIDDLE 14 Guide / Home / PS button.
MIDDLE_RIGHT 15 Start / Options / Menu button.
LEFT_THUMB 16 Left analogue stick click (L3).
RIGHT_THUMB 17 Right analogue stick click (R3).

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Enumerations\GamepadButton;

class GamepadController extends Behaviour
{
    public float $jumpForce = 8.0;

    public function update(): void
    {
        // Jump on the face-down button (A / ✕)
        if (Input::getButtonDown(GamepadButton::LEFT_FACE_DOWN->name)) {
            $this->jump();
        }
    }

    private function jump(): void { /* ... */ }
}
Enumerations

KeyCode

Namespace:

namespace Lenga\Engine\Enumerations;

Identifies a physical keyboard key. Pass a KeyCode case to Input::getKey(), Input::getKeyDown(), and Input::getKeyUp() for type-safe key checks.

Common values

Case String value Description
BACKSPACE 'Backspace' Backspace key.
TAB 'Tab' Tab key.
ENTER 'Enter' Enter / Return key.
SHIFT 'Shift' Left Shift key.
CONTROL 'Control' Left Control key.
ALT 'Alt' Left Alt key.
ESCAPE 'Escape' Escape key.
SPACE 'Space' Space bar.
LEFT_ARROW 'LeftArrow' Left arrow key.
RIGHT_ARROW 'RightArrow' Right arrow key.
UP_ARROW 'UpArrow' Up arrow key.
DOWN_ARROW 'DownArrow' Down arrow key.
AZ 'A''Z' Letter keys.
ZERONINE '0''9' Number row keys.
F1F12 'F1''F12' Function keys.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Enumerations\KeyCode;

class PlayerController extends Behaviour
{
    public float $speed = 5.0;

    public function update(): void
    {
        $moveX = 0.0;
        $moveZ = 0.0;

        if (Input::getKey(KeyCode::A) || Input::getKey(KeyCode::LEFT_ARROW)) {
            $moveX = -1.0;
        } elseif (Input::getKey(KeyCode::D) || Input::getKey(KeyCode::RIGHT_ARROW)) {
            $moveX = 1.0;
        }

        if (Input::getKey(KeyCode::W) || Input::getKey(KeyCode::UP_ARROW)) {
            $moveZ = 1.0;
        } elseif (Input::getKey(KeyCode::S) || Input::getKey(KeyCode::DOWN_ARROW)) {
            $moveZ = -1.0;
        }

        $this->transform->translate([
            $moveX * $this->speed * Time::deltaTime(),
            0,
            $moveZ * $this->speed * Time::deltaTime(),
        ]);
    }
}

Methods

toRaylibKeyCode

public function toRaylibKeyCode(): int

Returns the underlying Raylib key code integer for this KeyCode case. Intended for engine-internal use.

resolve

public static function resolve(self|string|int $key): ?int

Converts a KeyCode case, a string key name, or a raw integer to its Raylib key code. Returns null if the key cannot be resolved.

Interfaces

ComponentInterface

Namespace:

namespace Lenga\Engine\Interfaces;

Base marker interface for all Lenga components. RendererInterface and TransformInterface extend this interface. Engine systems that operate on any component can type-hint against ComponentInterface to accept the full range of component types.

You rarely need to implement this interface directly — Behaviour already satisfies the contract. It is primarily useful when writing engine utilities or editor tools that accept arbitrary component references.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Interfaces\ComponentInterface;

/**
 * Utility that logs information about any component attached to a game object.
 */
function debugComponent(ComponentInterface $component): void
{
    echo get_class($component) . PHP_EOL;
}

class DebugHelper extends Behaviour
{
    public function start(): void
    {
        // Pass any component to the utility
        debugComponent($this->getComponent(Rigidbody3D::class));
    }
}
Interfaces

GameObjectInterface

Namespace:

namespace Lenga\Engine\Interfaces;

Defines the standard contract for a Lenga game object. All scene objects implement this interface, which provides a consistent API for activation, hierarchy traversal, and component management. Type-hinting against GameObjectInterface lets you write code that works with any game object without depending on the concrete GameObject class.

Methods

setActive / isActiveSelf / isActiveInHierarchy

public function setActive(bool $value): void
public function isActiveSelf(): bool
public function isActiveInHierarchy(): bool

Controls and queries the active state of the object. isActiveSelf reflects the local flag; isActiveInHierarchy accounts for parent state.

getScene / getParent / getChildren / childCount

public function getScene(): mixed
public function getParent(): ?static
public function getChildren(): array
public function childCount(): int
public function setParent(?self $parent, bool $worldPositionStays = true): void

Hierarchy traversal and parenting methods.

hasComponent / getComponent / addComponent / destroy

public function hasComponent(string $type): bool
public function getComponent(string $type): mixed
public function addComponent(string $type): mixed
public function destroy(): void

Component management methods, mirroring the GameObject API.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Interfaces\GameObjectInterface;

/**
 * Recursively activates an object and all of its children.
 */
function activateTree(GameObjectInterface $obj): void
{
    $obj->setActive(true);
    foreach ($obj->getChildren() as $child) {
        activateTree($child);
    }
}

class SceneBootstrapper extends Behaviour
{
    public function start(): void
    {
        activateTree($this->gameObject);
    }
}
Interfaces

RendererInterface

Namespace:

namespace Lenga\Engine\Interfaces;

interface RendererInterface extends ComponentInterface

Marker interface shared by all renderer components (MeshRenderer, SpriteRenderer, ModelRenderer, etc.). Extending ComponentInterface, it allows engine systems and scripts to operate on any renderer without coupling to a specific implementation.

Type-hint against RendererInterface when you need to accept any renderer component generically.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Interfaces\RendererInterface;

/**
 * Toggles visibility for any renderer component.
 */
function setVisible(RendererInterface $renderer, bool $visible): void
{
    $renderer->setEnabled($visible);
}

class FlashEffect extends Behaviour
{
    public function flash(): void
    {
        $renderer = $this->getComponent(RendererInterface::class);
        if ($renderer !== null) {
            setVisible($renderer, false);
        }
    }
}
Interfaces

SceneInterface

Namespace:

namespace Lenga\Engine\Interfaces;

Marker interface for Lenga scene objects. All scenes implement this interface, allowing engine systems and scripts to reference scenes generically without coupling to the concrete Scene class.

In most scripts you will work with SceneManager to load and query scenes rather than implementing this interface yourself.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Interfaces\SceneInterface;
use Lenga\Engine\SceneManagement\SceneManager;

class SceneDebugger extends Behaviour
{
    public function start(): void
    {
        $scene = SceneManager::getActiveScene();

        if ($scene instanceof SceneInterface) {
            // work with the scene generically
        }
    }
}
Interfaces

TransformInterface

Namespace:

namespace Lenga\Engine\Interfaces;

interface TransformInterface extends ComponentInterface

Marker interface shared by all transform components. Extending ComponentInterface, it lets engine systems work with any transform implementation without coupling to the concrete Transform class.

In scripts, you typically access the transform through $this->transform rather than type-hinting against this interface directly. Use TransformInterface when writing utilities or editor extensions that must accept any transform type.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Interfaces\TransformInterface;

/**
 * Resets position, rotation, and scale to identity for any transform.
 */
function resetTransform(TransformInterface $t): void
{
    $t->setPosition([0, 0, 0]);
    $t->setRotation([0, 0, 0]);
    $t->setLocalScale([1, 1, 1]);
}

class LevelResetHelper extends Behaviour
{
    public function resetPlayer(): void
    {
        resetTransform($this->transform);
    }
}
Interfaces

Vector3Interface

Namespace:

namespace Lenga\Engine\Interfaces;

Defines the contract for a three-component vector with typed x, y, and z properties. The concrete Vector3 class implements this interface. Use Vector3Interface when writing utilities that operate on any 3D vector without depending on the specific implementation.

Properties

Property Type Description
$x float X component.
$y float Y component.
$z float Z component.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Interfaces\Vector3Interface;

/**
 * Returns the squared magnitude of any Vector3Interface, useful for
 * distance comparisons without the cost of a square root.
 */
function sqrMagnitude(Vector3Interface $v): float
{
    return $v->x ** 2 + $v->y ** 2 + $v->z ** 2;
}

class ProximityTrigger extends Behaviour
{
    public float $triggerRadius = 5.0;

    public function update(): void
    {
        $delta = $this->transform->getPosition();
        if (sqrMagnitude($delta) < $this->triggerRadius ** 2) {
            // within range
        }
    }
}

Welcome to the Lenga Engine Reference!

This section of the documentation provides a comprehensive reference for all the classes, methods, properties, and functions available in the Lenga Engine. Whether you're a beginner or an experienced developer, this API reference will help you understand how to use the various components of the engine effectively.

APIs are organized into namespaces and categories for easy navigation. Each entry includes detailed information about its purpose, parameters, return values, and usage examples.

SceneManagement

BackdropLayer

Namespace:

namespace Lenga\Engine\SceneManagement;

class BackdropLayer

BackdropLayer gives scripts live access to a scene backdrop layer.

Use it when you want to:

  • scroll a repeating background over time
  • slow the backdrop when the player reverses
  • speed it up during boost or warp effects
  • tune repeat, parallax, or offset at runtime

Properties

index

public int $index

The zero-based layer index in the active scene.

imagePath

public string $imagePath

The image assigned to this backdrop layer.

space

public string $space

The layer space, such as Screen or World.

depthPreset

public string $depthPreset

The current depth preset label for the layer.

offset

public Vector2 $offset

Reads or writes the layer offset.

Increasing offset.x makes a screen-space backdrop move left across the screen, which is useful for side-scrolling starfields and endless-runner backgrounds.

repeat

public Vector2 $repeat

Reads or writes the repeat flags as a vector.

In practice, use 1 to enable wrapping on an axis and 0 to disable it.

parallax

public Vector2 $parallax

Reads or writes the layer parallax strength.

Methods

fromNativeData

public static function fromNativeData(array $data): self

translateOffset

public function translateOffset(Vector2 $delta): bool

Adds a delta to the current offset.

This is the easiest way to animate a repeating backdrop each frame.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Time;
use Lenga\Engine\Core\Vector2;
use Lenga\Engine\SceneManagement\SceneManager;

final class StarfieldController extends Behaviour
{
    public float $scrollSpeed = 40.0;

    public function update(): void
    {
        $scene = SceneManager::getActiveScene();
        $layer = $scene?->getBackdropLayer(0);
        if ($layer === null) {
            return;
        }

        $layer->repeat = new Vector2(1.0, 0.0);
        $layer->translateOffset(new Vector2(
            $this->scrollSpeed * Time::deltaTime(),
            0.0,
        ));
    }
}
SceneManagement

Scene

Namespace:

namespace Lenga\Engine\SceneManagement;

class Scene

Methods

fromNativeData

public static function fromNativeData(array $data)

@param array{name?: string} $data

getActive

public static function getActive()

getCanvases

public function getCanvases()

@var array{name?: string}|false $data */

getBackdropLayers

public function getBackdropLayers(): array

Returns the scene backdrop layers as BackdropLayer wrappers.

getBackdropLayer

public function getBackdropLayer(int $index): ?BackdropLayer

Returns one backdrop layer by zero-based index.

getBackdropLayerCount

public function getBackdropLayerCount(): int

findCanvas

public function findCanvas(string $name)

instantiateGameObject

public function instantiateGameObject(GameObject $original, ?string $name = null)

instantiatePrefab

public function instantiatePrefab(string $assetPath, ?string $name = null)

findGameObject

public function findGameObject(string $name)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\SceneManagement\SceneManager;

class SceneInfo extends Behaviour
{
    public function update(): void
    {
        $scene = SceneManager::getActiveScene();
        if ($scene !== null) {
            $layer = $scene->getBackdropLayer(0);
            if ($layer !== null) {
                $layer->translateOffset(new \Lenga\Engine\Core\Vector2(24.0 * \Lenga\Engine\Core\Time::deltaTime(), 0.0));
            }
        }
    }
}

See Also

SceneManagement

SceneManager

`SceneManager` is the PHP API you use when a script needs to work with the current scene or move the player to another scene.

The most common workflow is:

  1. Create the scenes in the editor.
  2. Open Build Settings and add the scenes you want the game to load.
  3. Make sure the scene is enabled in the Build Settings scene list.
  4. Put your PHP code in a Behaviour attached to a normal GameObject.
  5. Call SceneManager::loadScene('SceneName') from that behaviour.

The load request is applied by the engine at a safe point in the frame, after script update work has finished. That keeps the current scene from being destroyed in the middle of a callback.

Namespace:

namespace Lenga\Engine\SceneManagement;

Import:

use Lenga\Engine\SceneManagement\SceneManager;

getActiveScene

Returns the scene that is running, or null if there is no active scene.

public static function getActiveScene(): ?Scene

Use this when you need to find objects, canvases, or other scene-owned content from a script.

$scene = SceneManager::getActiveScene();

if ($scene === null) {
    Debug::warn('There is no active scene yet.');
    return;
}

loadScene

Requests a scene transition.

public static function loadScene(string $sceneNameOrPath): void

Use the scene name when possible:

SceneManager::loadScene('Level1');

For that to work, Level1 should be present and enabled in Build Settings. Lenga can also accept a project-relative scene path when you need to be explicit:

SceneManager::loadScene('Assets/Scenes/Level1.scene.json');

If the scene cannot be found, is disabled, or fails validation, loadScene throws a RuntimeException.

tryLoadScene

Requests a scene transition and returns whether the request was accepted.

public static function tryLoadScene(string $sceneNameOrPath): bool

Use this when a missing scene should be handled gracefully instead of throwing:

if (!SceneManager::tryLoadScene('Level1')) {
    Debug::warn('Could not load Level1. Check Build Settings.');
}

loadSceneByPath

Requests a scene transition by path.

public static function loadSceneByPath(string $scenePath): void

This is a readability wrapper around loadScene. It is useful when you want the code to make it obvious that the value is a path:

SceneManager::loadSceneByPath('Assets/Scenes/MainMenu.scene.json');

instantiatePrefab

Creates an instance of a prefab in the active scene.

public static function instantiatePrefab(string $assetPath, ?string $name = null): GameObject
$coin = SceneManager::instantiatePrefab('Assets/Prefabs/Coin.prefab.json', 'Coin Pickup');

assetPath should be a project-relative path.

createScene

public static function createScene(string $name): Scene

Runtime scene creation is not supported yet. Use the editor to create scenes, then load them by name or project-relative path.

setActiveScene

public static function setActiveScene(Scene $scene): void

Directly swapping the active scene object is not supported yet. Use loadScene or tryLoadScene for runtime scene transitions.

Example

This example belongs in a Behaviour class attached to a normal GameObject, such as a MainMenuController object in your menu scene.

<?php

declare(strict_types=1);

namespace MyGame\Scripts;

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Debug;
use Lenga\Engine\SceneManagement\SceneManager;

final class MainMenuController extends Behaviour
{
    public function startGame(): void
    {
        SceneManager::loadScene('Level1');
    }

    public function tryOpenBonusLevel(): void
    {
        if (!SceneManager::tryLoadScene('BonusLevel')) {
            Debug::warn('BonusLevel could not be loaded.');
        }
    }
}

You can call these methods from your own button-handling code. The important part is that the script lives on a GameObject in the running scene, because behaviours are how Lenga runs gameplay PHP code.

UI

Button

Namespace:

namespace Lenga\Engine\UI;

final class Button extends UIElement

A clickable UI element. Button extends UIElement and adds text, colour, optional state images, and interaction-state properties. Obtain a reference via Canvas::getElement() or by looking up the element name on a Canvas component.

Interaction state properties

These read-only properties expose the button's current interaction state. Query them inside update().

Property Type Description
$hovered bool true every frame the cursor is over the button.
$pressed bool true every frame the mouse button is held down while the cursor is over the button. Use with care — this fires continuously for every frame the button is held.
$pressedThisFrame bool true only on the first frame the mouse button goes down over the button (edge-triggered).
$releasedThisFrame bool true only on the first frame the mouse button is released over the button (edge-triggered).
$clicked bool true only on the first frame after a full press-and-release cycle completes while the cursor is over the button. Prefer this for menu actions and one-shot triggers.

Tip — Use $clicked for most menu interactions. It fires exactly once per click, equivalent to a traditional button click event. Use $pressed only when you genuinely need continuous feedback (e.g., a hold-to-charge mechanic).

Appearance properties

Property Type Description
$text string The label displayed on the button. Readable and writable.
$fontSize float Font size in points. Readable and writable. Default 24.0.
$fontPath string Path to a custom font file. Readable and writable.
$interactable bool When false, the button is displayed with its disabled colour and ignores all input. Readable and writable.

State image methods

Each button state can use a texture image instead of relying only on colour. Paths are project-relative asset paths such as Assets/Sprites/UI/ButtonIdle.png.

Method Description
setBackgroundImage(path) / getBackgroundImage() Default image used when the button is idle.
setHoverImage(path) / getHoverImage() Image used while the cursor is over the button. If empty, the button falls back to the default image or hover colour.
setPressedImage(path) / getPressedImage() Image used while the mouse button is held down over the button. If empty, the button falls back to the default image or pressed colour.
setDisabledImage(path) / getDisabledImage() Image used when $interactable is false. If empty, the button falls back to the default image or disabled colour.

If a state image is assigned, that image represents the state directly. If a state image is not assigned but the default image is assigned, Lenga uses the default image and applies the state colour as feedback. If no image is available, Lenga draws the button with the state colour.

Colour methods

Each colour is returned as an array{r:int, g:int, b:int, a:int} and changed via the corresponding setter.

Method Description
getTextColor() / setTextColor(r, g, b, a) Foreground (label) colour. Default: white.
getBackgroundColor() / setBackgroundColor(r, g, b, a) Normal state background.
getHoverColor() / setHoverColor(r, g, b, a) Background while the cursor is over the button.
getPressedColor() / setPressedColor(r, g, b, a) Background while the mouse button is held down.
getDisabledColor() / setDisabledColor(r, g, b, a) Background when $interactable is false.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\UI\Button;
use Lenga\Engine\UI\Canvas;
use Lenga\Engine\SceneManagement\SceneManager;

class MainMenu extends Behaviour
{
    private ?Button $playButton = null;
    private ?Button $quitButton = null;

    public function start(): void
    {
        $canvas = $this->getComponent(Canvas::class);

        $this->playButton = $canvas->getElement('PlayButton');
        $this->quitButton = $canvas->getElement('QuitButton');

        // Style the play button
        $this->playButton->setBackgroundColor(34, 139, 34, 255);
        $this->playButton->setHoverColor(50, 180, 50, 255);
        $this->playButton->setBackgroundImage('Assets/Sprites/UI/PlayButton.png');
        $this->playButton->setHoverImage('Assets/Sprites/UI/PlayButtonHover.png');
        $this->playButton->setPressedImage('Assets/Sprites/UI/PlayButtonPressed.png');
    }

    public function update(): void
    {
        // clicked fires exactly once per full press-and-release cycle
        if ($this->playButton?->clicked) {
            SceneManager::loadScene('Game');
        }

        if ($this->quitButton?->clicked) {
            Application::quit();
        }
    }
}
UI

Canvas

Namespace:

namespace Lenga\Engine\UI;

class Canvas

Methods

getId

public function getId()

createText

public function createText(string $name, ?UIElement $parent = null)

getRootElements

public function getRootElements()

@var array{id?: int, name?: string, type?: string, canvasId?: int|null}|false $data */

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\UI\Canvas;

class UISetup extends Behaviour
{
    public function start(): void
    {
        // Get the Canvas component
        $canvas = $this->getComponent(Canvas::class);

        if ($canvas !== null) {
            // Configure canvas properties
            $canvas->renderMode = 'ScreenSpaceOverlay';
            $canvas->scaleMode = 'ScaleWithScreenSize';

            Debug::log('Canvas initialized');
        }
    }
}
UI

Image

Namespace:

namespace Lenga\Engine\UI;

class Image

Methods

getColor

public function getColor()

@return array{r:int, g:int, b:int, a:int}

setColor

public function setColor(int $red, int $green, int $blue, int $alpha = 255)

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\UI\Image;
use Lenga\Engine\Core\Time;

class UIImageController extends Behaviour
{
    public function start(): void
    {
        // Get the Image component
        $image = $this->getComponent(Image::class);

        if ($image !== null) {
            // Set the sprite to display
            $image->sprite = 'assets/ui/icon.png';
            $image->color = [255, 255, 255, 255]; // White, fully opaque
        }
    }

    public function update(): void
    {
        $image = $this->getComponent(Image::class);
        // Fade in/out effect
        $alpha = (int)(255 * (0.5 + 0.5 * MathUtil::sin(Time::time() * 2)));
        $image->color = [255, 255, 255, $alpha];
    }
}
UI

RectTransform

`RectTransform` controls where a UI element sits inside its parent rectangle.

Every UIElement has a rectTransform property. You normally use it to set:

  • where the element is anchored
  • which point inside the element is used as its pivot
  • the element's position relative to its anchor
  • the element's size, scale, and rotation

Namespace:

namespace Lenga\Engine\UI;

final class RectTransform

Basic Usage

This example places a button near the top-right corner of its parent. The code belongs in a Behaviour class attached to a GameObject in the scene.

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Vector2;
use Lenga\Engine\SceneManagement\Scene;

final class HudController extends Behaviour
{
    public function start(): void
    {
        $canvas = Scene::getActive()?->findCanvas('Canvas');
        $button = $canvas?->findButtonByName('Pause Button');

        if ($button === null) {
            return;
        }

        $button->rectTransform->anchorMin = new Vector2(1.0, 0.0);
        $button->rectTransform->anchorMax = new Vector2(1.0, 0.0);
        $button->rectTransform->pivot = new Vector2(1.0, 0.0);
        $button->rectTransform->anchoredPosition = new Vector2(-24.0, 24.0);
        $button->rectTransform->sizeDelta = new Vector2(160.0, 44.0);
    }
}

Coordinate Values

anchorMin, anchorMax, and pivot use normalized coordinates:

  • x = 0.0 means left
  • x = 0.5 means center
  • x = 1.0 means right
  • y = 0.0 means top
  • y = 0.5 means middle
  • y = 1.0 means bottom

For example, (1.0, 0.0) means top right.

Properties

Property Type Description
$anchorMin Vector2 The lower normalized anchor value. When it matches $anchorMax, the element is anchored to a single point.
$anchorMax Vector2 The upper normalized anchor value. Use a different value from $anchorMin when the element should stretch with its parent.
$pivot Vector2 The point inside the element that is positioned, rotated, and scaled.
$anchoredPosition Vector2 The offset from the anchor reference point to the element's pivot.
$sizeDelta Vector2 The element's size when it is anchored to a single point, or the size adjustment when it is stretched.
$scale Vector2 The local UI scale.
$rotation float The element rotation in degrees.

Usage Notes

Use anchoredPosition, not offsetMin or offsetMax, for the standard Lenga UI workflow.

The Inspector includes a 3x3 anchor preset grid for common anchor points such as top-left, middle-center, and bottom-right. Those presets update the anchors and pivot together while preserving the element's current visual position.

For a beginner-friendly walkthrough, see Lay Out UI with Anchors, Buttons, and Parenting.

UI

Text

Namespace:

namespace Lenga\Engine\UI;

final class Text extends UIElement

Text is the UI element you use for labels, score counters, instructions, menu titles, and other screen-space text. It inherits the common layout and hierarchy features from UIElement, including rectTransform, visible, name, and setParent(...).

Where Text Code Lives

Most Text code lives inside a Behaviour class attached to a scene GameObject.

For example, you might create a HUDController object in your scene, attach a HUDController Behaviour to it, then let that Behaviour find a canvas and update the Score Label text element.

The Text element does not need to live on the same GameObject as the Behaviour. The common workflow is:

  1. Put your UI elements under a Canvas.
  2. Attach a controller Behaviour to a normal scene GameObject, such as GameManager or HUDController.
  3. In the Behaviour, ask the active scene for the canvas.
  4. Find the text element by name.
  5. Update the text from start() or update().
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\SceneManagement\Scene;
use Lenga\Engine\UI\Text;

final class HUDController extends Behaviour
{
    private ?Text $scoreLabel = null;

    public function start(): void
    {
        $canvas = Scene::getActive()?->findCanvas('Canvas');
        $this->scoreLabel = $canvas?->findTextByName('Score Label');

        if ($this->scoreLabel !== null) {
            $this->scoreLabel->text = 'Score: 0';
        }
    }
}

If your code already has a direct Text reference from another API, you can use that reference directly. You only need Scene::getActive()?->findCanvas(...) when you are starting from the scene and need to look up a UI element by name.

Properties

Property Type Description
$text string The string displayed by the element.
$fontSize float Font size in points.
$fontPath string Project-relative path to the font asset. Leave empty to use Lenga's bundled default UI font.
$useSdf bool Enables the SDF rendering path for this text element. Use this with a generated .sdf-font.json font asset.
$sdfOutlineWidth float Width of the SDF outline in pixels. This is separate from the standard outline distance used by raster text rendering.
$sdfSoftness float Edge softness for SDF text in pixels. Higher values make the text edge or outline softer.

Colour Methods

Text colour is read and written through methods rather than a direct property.

public function getColor(): array
public function setColor(int $red, int $green, int $blue, int $alpha = 255): void

getColor() returns:

array{r:int, g:int, b:int, a:int}

Example:

$label->setColor(255, 255, 255, 255);

Outline State for Raster Text

These methods expose the outline settings used when a Text element renders with a normal .ttf or .otf font:

public function getOutlineColor(): array
public function getOutlineDistance(): array

These outline settings are useful when you are using a normal .ttf or .otf font directly. For the crispest scalable outlines, generate an SDF font asset and use $sdfOutlineWidth instead.

SDF Font Workflow

For beginner-friendly editor steps, see Generate SDF Fonts for Crisp UI Text.

The short version is:

  1. Import a .ttf or .otf font into your project.
  2. Right-click the font in the Assets panel.
  3. Choose Generate SDF Font.
  4. Assign the generated .sdf-font.json file to the Font field on a Text element.
  5. Tune SDF Outline and SDF Softness on that Text element.

You can also set those values from PHP:

$title->fontPath = 'Assets/Fonts/MenuTitle.sdf-font.json';
$title->useSdf = true;
$title->sdfOutlineWidth = 3.0;
$title->sdfSoftness = 0.75;

If fontPath points to a normal .ttf or .otf file and useSdf is false, Lenga uses the standard raster font rendering path.

Complete Example

This example belongs inside a Behaviour attached to a normal scene GameObject, such as HUDController.

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Input;
use Lenga\Engine\Core\KeyCode;
use Lenga\Engine\SceneManagement\Scene;
use Lenga\Engine\UI\Text;

final class ScoreDisplay extends Behaviour
{
    private int $score = 0;
    private ?Text $scoreLabel = null;

    public function start(): void
    {
        $canvas = Scene::getActive()?->findCanvas('Canvas');
        $this->scoreLabel = $canvas?->findTextByName('Score Label');

        if ($this->scoreLabel !== null) {
            $this->scoreLabel->text = 'Score: 0';
            $this->scoreLabel->fontSize = 36.0;
            $this->scoreLabel->fontPath = 'Assets/Fonts/HUD.sdf-font.json';
            $this->scoreLabel->useSdf = true;
            $this->scoreLabel->sdfOutlineWidth = 2.0;
            $this->scoreLabel->sdfSoftness = 0.5;
            $this->scoreLabel->setColor(255, 255, 255, 255);
        }
    }

    public function update(): void
    {
        if (Input::getKeyDown(KeyCode::SPACE)) {
            $this->score += 10;
            $this->scoreLabel?->text = 'Score: ' . $this->score;
        }
    }
}
UI

UIElement

Namespace:

namespace Lenga\Engine\UI;

abstract class UIElement

Base class for all UI elements — Button, Text, and Image all extend UIElement. It provides the shared API for visibility, hierarchy, layout, and child-element creation.

Obtain an element reference via Canvas::getElement(). You do not instantiate UIElement directly; the canvas creates and manages element instances.

Properties

Property Type Access Description
$name string read The name of the element as set in the scene.
$enabled bool read/write Whether the element processes input and updates.
$visible bool read/write Whether the element is drawn on screen.
$activeInHierarchy bool read true when the element and all of its ancestors are active.
$sortOrder int read/write Draw order relative to sibling elements. Higher values render on top.
$rectTransform RectTransform read Layout data (anchors, position, size, pivot).

Methods

getId

public function getId(): int

Returns the native element identifier. Used internally by engine bindings.

getCanvas

public function getCanvas(): ?Canvas

Returns the Canvas this element belongs to.

getParent / setParent

public function getParent(): ?UIElement
public function setParent(?UIElement $parent): bool

Gets or re-parents the element in the UI hierarchy.

getChildren / findDescendantByName

public function getChildren(): list<UIElement>
public function findDescendantByName(string $name): ?UIElement

Traverses child elements. findDescendantByName searches the entire subtree recursively.

createText / createImage / createButton

public function createText(string $name): Text
public function createImage(string $name): Image
public function createButton(string $name): Button

Creates a new child element of the given type, parented to this element. Throws RuntimeException if the element is not attached to a canvas.

Example

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\UI\Canvas;
use Lenga\Engine\UI\Button;

class InventoryPanel extends Behaviour
{
    private ?Canvas $canvas = null;

    public function start(): void
    {
        $this->canvas = $this->getComponent(Canvas::class);
        $panel = $this->canvas->getElement('InventoryPanel');

        // Toggle panel visibility
        $panel->visible = true;

        // Dynamically create a close button as a child of the panel
        $closeBtn = $panel->createButton('CloseButton');
        $closeBtn->text   = 'Close';
        $closeBtn->visible = true;
    }

    public function update(): void
    {
        $closeBtn = $this->canvas?->getElement('CloseButton');
        if ($closeBtn instanceof Button && $closeBtn->clicked) {
            $this->canvas->getElement('InventoryPanel')->visible = false;
        }
    }
}