1. The script model
When a file is loaded, Lynx wraps it in an internal function receiving parent. The script is evaluated by QuickJS, and the engine retrieves lifecycle functions by name.
function BeginPlay() {
console.log("Player started", parent.id);
}
function Update(dt) {
parent.position.x += 3 * dt;
}
function EndPlay() {
console.log("Player stopped");
}Important: a script is not a class instantiated with new. The engine creates the behavior instance for each attachment.
2. Lifecycle
| Function | Called | Use |
|---|
BeginPlay() | Game start | Gameplay initialization. |
Update(dt) | Every tick | Movement, logic, input. |
EndPlay() | Stop / removal / destruction | Cleanup and end-of-play logic. |
If a script is added during gameplay, its BeginPlay() runs before its next Update(). The engine also protects iterations when components are added or removed.
3. Multiple scripts and multiple Actors
An Actor can have multiple JS files. The C++ property Actor::scripts stores their paths separated by ;. The same file can be attached to multiple Actors.
parent.addScript("scripts/Health.js");
parent.addScript("scripts/EnemyAI.js");
console.log(parent.hasScript("scripts/Health.js"));
parent.removeScript("scripts/EnemyAI.js");The source of a script is compiled once per path and then reused. Each attachment still keeps its own variable environment and state.
4. Actor
parent is a JavaScript object representing the Actor. The following properties are available:
parent.idActor identifier, read/write
parent.classNameC++ class name, read-only
parent.validtrue while the Actor still exists
parent.entityinternal ECS entity identifier
parent.transformTransform reference
parent.positionposition Vec3 reference
parent.velocitylinear velocity Vec3 reference
parent.angularVelocityangular velocity Vec3 reference
parent.dampingVelocityComponent damping
parent.lifetimelifetime; undefined when absent
parent.tagstag array
parent.scriptsattached script array
5. Transform and Vec3
position, velocity, and angularVelocity reference Actor data. Changing x, y, or z directly changes the Actor.
parent.position.x += 4 * dt;
parent.position.y = 10;
parent.velocity = vec3(2, 0, 0);
parent.angularVelocity.set(0, 0, 1);
parent.transform.position.x = 5;
parent.transform.rotation = vec3(0, 0, 0);
parent.transform.scale = vec3(2, 2, 2);
const p = parent.position.clone();
vec3() accepts up to three numbers. With one number, all three components use that value. An object {x, y, z} can also be used.
const a = vec3(1, 2, 3);
const b = vec3(5);
parent.position = { x: 10, y: 4, z: 0 };
6. Level
The global Level object operates on the current level.
Level.spawn(className, transform?)Creates an Actor. The second argument accepts a transform object or a Vec3 position. Returns the Actor or null.
Level.find(id)Returns the Actor with that identifier or null.
Level.findWithTag(tag)Returns an array of Actors carrying the tag.
Level.all()Returns every Actor in the current level.
Level.count(className)Counts Actors of a class.
Level.destroy(actor)Requests Actor destruction.
const enemy = Level.spawn("Enemy", {
position: {x: 12, y: 2, z: 0},
rotation: {x: 0, y: 0, z: 0},
scale: {x: 1, y: 1, z: 1}
});
const player = Level.find("Player");
const enemies = Level.findWithTag("enemy");
7. Input
The input API works with action and axis names configured by the project. An action is resolved when called, so configuration changes can take effect immediately.
Input.pressed(name)true on the frame the action was pressed
Input.held(name)true while the action is held
Input.released(name)true on the frame the action was released
Input.axis(name)returns a floating-point 1D axis value
function Update(dt) {
const x = Input.axis("move_x");
parent.position.x += x * 8 * dt;
if (Input.pressed("jump"))
parent.velocity.y = 12;
}
8. Engine
Engine.getTimeDilation()Returns the effective global time dilation.
Engine.setTimeDilation(value)Sets the background time dilation.
Engine.setTimeDilation(value, duration)Applies temporary dilation for a duration, useful for hit-stop.
Engine.isPlaying()Reports whether the game is currently in play mode.
if (Engine.isPlaying()) {
Engine.setTimeDilation(0.15, 0.08);
}
9. Reflected properties
Actors expose C++ properties registered through the reflection system. From JavaScript:
if (parent.hasProperty("Health")) {
const hp = parent.getProperty("Health");
parent.setProperty("Health", hp - 10);
}getProperty(name) returns the property value when it exists, otherwise undefined. setProperty() throws a type error when the value does not match the expected type. This lets scripts control reflected C++ properties without knowing their internal member layout.
10. Errors, destruction, and hot reload
A JavaScript error during a function call is caught and logged by the engine with the script path and, when relevant, the function name. It should not bring down the engine process.
After an Actor is destroyed, JS wrappers can become invalid. Use parent.valid when a reference must survive operations that may destroy the Actor.
ScriptComponent::Reload() reloads files from disk. The engine releases old instance functions and recreates the scripts. Previously compiled files are also managed through a path-based cache.
function Update(dt) {
if (!parent.valid)
return;
}
11. Recommended patterns
Simple movement
function Update(dt) {
parent.position.x += Input.axis("move_x") * 5 * dt;
}Timed projectile
function BeginPlay() {
parent.velocity = vec3(12, 0, 0);
parent.lifetime = 2;
}Find an enemy
function Update(dt) {
const enemies = Level.findWithTag("enemy");
if (enemies.length > 0)
parent.call("TakeDamage", 1);
}Communication between scripts
actor.call(name, ...args) calls the requested function in the scripts attached to that Actor. If multiple scripts define the same function, the engine calls them in instance order and the last return value becomes the final result.
12. What JavaScript does not do
- It does not replace the C++ engine or native gameplay classes.
- It does not create an Actor class hierarchy through JavaScript inheritance.
- It does not automatically expose every C++ component: only bridges explicitly registered in the JS API are available.
- Calls use the current level and QuickJS runtime; a script remains behavior attached to an Actor.