Lua API
Ra Engine's game logic layer. Drop a .lua script in scripts/, point the engine at it, and the full engine API is available as the global table engine. Changes to the script hot-reload live — no recompile needed.
engine global. This lets you iterate on game logic independently of the graphics pipeline.
01. Script Contract
Every Ra Engine Lua script must define these global functions. The engine calls them automatically.
Called once when the script first loads, and again on every hot-reload and after every engine.load_scene(). Use this to look up object IDs with engine.find_object() and engine.find_objects_by_prefix(), reset animation state, and read any persisted state from engine.get_var().
Do not cache object IDs across hot-reloads in module-level Lua variables — the scene may have changed. Always re-discover them inside init().
Called every frame before rendering. dt is the frame delta time in seconds (capped; safe for physics and animation). All game logic, input polling, scoring, object movement, and scale/emissive animations go here.
Called on engine exit (before Vulkan teardown). Save any game state that must survive process restart using engine.set_var(). This is your last chance to write persistent data.
Return a short string shown in the engine's terminal status area (bottom line). Called every frame. Return "" to show nothing. Keep it under ~80 characters for clean display.
02. Hot-Reload
Every frame, the engine stat()s the script file. If the modification time has changed, it automatically:
- Calls
shutdown()on the current Lua state - Destroys the old
lua_State - Opens a fresh state and re-binds the full engine API
- Runs the script file
- Calls
init()again
lua_State is torn down. Only values written to engine.set_var() survive — they live in C++ and are unaffected by Lua restarts.
If the reloaded script has a syntax or runtime error, the engine keeps the previous working state and suppresses further reload attempts until the file is saved again.
03. Persisting State
Use engine.set_var() / engine.get_var() for any value that must survive hot-reloads, scene transitions, or be accessible between update() and shutdown().
function init()
-- Read back values saved from a previous session or hot-reload
score = engine.get_var("launch_up.score", 0.0)
peak_height = engine.get_var("launch_up.peak_height", 0.0)
current_level = math.floor(engine.get_var("launch_up.level", 1.0) + 0.5)
end
function shutdown()
engine.set_var("launch_up.score", score)
engine.set_var("launch_up.peak_height", peak_height)
engine.set_var("launch_up.level", current_level)
end
"launch_up.score") to avoid collisions if multiple scripts are used across levels.
04. Camera
The camera is the player in first-person mode. Moving the camera moves the player. Physics and collision are resolved relative to the camera position.
Returns the camera's world-space position as three separate floats. Y is vertical — the primary axis for height-based scoring.
local _, cy, _ = engine.get_camera_position()
if cy > peak_height then
peak_height = cy
score = (peak_height - spawn_y) * 100.0
end
Teleports the camera (and player) to the given world-space position. Used for respawning. Always follow with engine.set_vertical_velocity(0.0) to clear falling momentum.
local cx, cy, cz = engine.get_checkpoint()
engine.set_camera_position(cx, cy, cz)
engine.set_vertical_velocity(0.0)
Returns the normalised camera look direction. Useful for shooting, raycasting, or directional movement logic.
Returns the normalised camera right vector (perpendicular to forward in the horizontal plane). Use with forward for strafe-relative logic.
05. Physics
Returns a table describing the player's current physics state.
Directly sets the player's vertical velocity. Use to trigger a jump or cancel falling momentum on respawn. Engine gravity is −18.0 m/s².
06. Input
Returns a snapshot of the current frame's input state. Read this once at the top of update().
true if the key is currently held. Use for continuous actions.true only on the single frame the key went down. Use for discrete actions (restart, next level).local inp = engine.get_input()
-- One-shot: restart or advance level
if inp.key_just_pressed[KEY_R] then
-- restart current level
elseif inp.key_just_pressed[KEY_N] then
engine.load_scene(LEVELS[current_level + 1])
end
true for exactly one frame. Always read input once at the top of update() — never inside a loop.
07. Time
Returns wall-clock seconds as a double. Use for continuous animations: emissive pulses, scale breathe effects, rotation offsets.
local t = engine.get_time()
-- Hazard breathe: scale oscillates between 0.92 and 1.08
local breathe = 1.0 + 0.08 * math.sin(t * 2.5)
for _, hid in ipairs(hazard_ids) do
engine.set_object_scale(hid, breathe)
end
-- Emissive pulse
local p = (math.sin(t * 3.0) + 1.0) * 0.5
engine.set_object_emissive(glow_id, 0.0, 1.0, 0.9, 2.0 + p * 3.0)
Returns the same dt passed to update(dt). Convenient for helper functions that don't receive dt as a parameter.
08. Scene Objects
Scene objects are identified by a uint32 ID assigned at load time. IDs must be re-acquired after any scene load or hot-reload — always call engine.find_object() and engine.find_objects_by_prefix() inside init().
engine.find_object() returns 0 when no object with that name exists. Always check before using an ID.
Transform
Looks up a scene object by its exact GLTF node name. Returns the integer ID, or 0 if not found.
function init()
goal_id = engine.find_object("goal_pad")
if goal_id == 0 then
engine.print_status("WARNING: goal_pad not found")
end
end
Returns true if id is non-zero and present in the current scene. Use as a guard before transform calls.
Returns the world-space position of the object's transform origin as three floats.
Moves the object to the given world-space position. Internally rebuilds the per-object BLAS and TLAS. Safe to call every frame for moving platforms and hazards.
Returns Euler rotation in degrees (YXZ order, matching the Ra Engine editor).
Sets Euler rotation in degrees (YXZ order). The primary tool for spinning hazard platforms.
-- Rotate all hazard spinners at 45°/sec on Y axis
spinner_angle = (spinner_angle + 45.0 * dt) % 360.0
for _, hid in ipairs(hazard_ids) do
engine.set_object_rotation(hid, 0.0, spinner_angle, 0.0)
end
Returns the current scale as three floats. Default is 1, 1, 1. Minimum per-axis value enforced by the engine: 0.01. Use the current scale as the basis for lerp animations to avoid drift.
-- Ease goal pad scale toward 1.0 on reveal (pop-in)
local sx, _, _ = engine.get_object_scale(goal_id)
if sx < 0.99 then
engine.set_object_scale(goal_id, sx + (1.0 - sx) * 0.15)
end
Sets object scale. Pass one value for uniform scale; pass three for non-uniform. Minimum component: 0.01.
-- Goal pad throbs between 1.0 and 1.15 on the finish screen
local sc = 1.0 + 0.15 * (math.sin(goal_scale_t * 0.8 * 2.0 * math.pi) + 1.0) * 0.5
engine.set_object_scale(goal_id, sc)
-- Checkpoint scale pop: arc up to 1.3 and back over 0.5s
local progress = 1.0 - (timer / 0.5)
local pop_sc = 1.0 + 0.3 * math.sin(progress * math.pi)
engine.set_object_scale(glow_id, pop_sc)
-- Non-uniform: wide flat platform
engine.set_object_scale(platform_id, 3.0, 0.3, 3.0)
Visual
Sets the emissive colour and intensity at runtime. Drives RTX GI — nearby geometry is lit by the emissive surface. intensity defaults to 1.0.
-- Checkpoint orange flash (4Hz, fading out over 1.2s)
local p = (math.sin(t * 4.0 * 2.0 * math.pi) + 1.0) * 0.5
engine.set_object_emissive(glow_id, 1.0, 0.5, 0.0, p * 6.0)
-- Goal pad glow (slow 1.5Hz cyan pulse)
engine.set_object_emissive(goal_glow_id, 0.0, 1.0, 0.9, 2.0 + p * 3.0)
-- Turn off
engine.set_object_emissive(glow_id, 0.0, 0.0, 0.0, 0.0)
Shows or hides an object in the ray-traced scene. Hidden objects are collapsed to a degenerate position in the BVH. Use for goal pad reveals, collectibles, and cinematic triggers. Combine with set_object_scale(id, 0.1) before revealing for a pop-in effect.
-- Reveal goal pad with a pop-in: start tiny, lerp to full size
engine.set_object_visible(goal_id, true)
engine.set_object_scale(goal_id, 0.1) -- init() resets to 1.0 on reload
Collision & Triggers
Returns six floats describing the world-space AABB of the object. Returns a degenerate zero-size AABB if the ID is invalid.
Returns the player's world-space AABB (a capsule approximation centred on camera position).
Convenience AABB overlap test between the player and a scene object. The primary method for checkpoint triggers, goal detection, and collectibles.
if goal_id and engine.player_overlaps_object(goal_id) then
state = "finished"
goal_scale_t = 0.0
engine.print_status("★ LEVEL COMPLETE!")
end
09. Scene Queries
Returns a 1-indexed Lua table of all object IDs whose GLTF node name starts with prefix. Returns an empty table if none match. The preferred way to manage groups of similarly-named objects (hazard spinners, collectibles, checkpoints).
function init()
-- Discover all hazard spinners: platform_hazard_01, _02, _03 ...
hazard_ids = engine.find_objects_by_prefix("platform_hazard_")
-- Reset each one to neutral scale
for _, hid in ipairs(hazard_ids) do
engine.set_object_scale(hid, 1.0)
end
engine.print_status("Hazards: " .. #hazard_ids)
end
Returns a 1-indexed Lua table of every GLTF node name in the current scene. Useful for debugging scene layout in init().
local names = engine.get_all_object_names()
engine.print_status(
"Level " .. current_level .. " | Objects: " .. #names ..
" | Hazards: " .. #hazard_ids
)
10. Scene Loading
Requests a deferred scene transition. The new GLTF loads at the start of the next frame — the current update() finishes cleanly first. After load, the engine calls init() again so the script re-discovers objects in the new scene.
"scenes/launch_up_level2.gltf".local LEVELS = {
"scenes/launch_up_tutorial1.gltf",
"scenes/launch_up_level1.gltf",
"scenes/launch_up_level2.gltf",
"scenes/launch_up_level3.gltf",
"scenes/launch_up_level4.gltf",
"scenes/launch_up_level5.gltf",
"scenes/launch_up_level6.gltf",
"scenes/launch_up_level7.gltf",
}
-- On level complete (KEY_N handler):
local next = current_level + 1
if next <= #LEVELS then
engine.set_var("launch_up.level", next)
engine.set_var("launch_up.peak_height", spawn_pos.y)
engine.load_scene(LEVELS[next])
-- init() is called automatically after the scene loads
end
engine.set_var() before the call are available when init() runs in the new scene.
11. Checkpoints
The engine stores one world-space respawn position. Update it as the player passes height tier thresholds.
Sets the respawn position. Call when the player reaches a new height tier or crosses a trigger zone. Typically also triggers a flash animation on the checkpoint glow object.
Returns the current respawn position. Use in the dead-state handler alongside set_camera_position().
local function do_respawn()
local cx, cy, cz = engine.get_checkpoint()
engine.set_camera_position(cx, cy, cz)
engine.set_vertical_velocity(0.0)
state = "exploring"
end
12. Game Variables
A float key-value store that lives in C++ and survives hot-reloads, scene transitions, and shutdown() / restart cycles within a session.
Stores a float value under an arbitrary string key.
Retrieves a stored value. Returns default if the key has never been set. Always provide a sensible default — the key won't exist on first run.
13. HUD & Status
Prints a line to the engine's terminal status area. Transient — overwritten on the next call. Use for event notifications (checkpoint reached, level complete, debug info). Also captured by the F1 Console View.
Pushes score and height to the in-viewport HUD overlay (top-left bitmap font readout). Call every frame while playing.
local height_gained = peak_height - spawn_pos.y
engine.set_hud_data(score, height_gained)
14. Key Constants
These globals are automatically defined in every Lua script. They map to GLFW key codes and are used with engine.get_input().
key_down and key_just_pressed tables are indexed by raw GLFW key code integers. Any GLFW_KEY_* value works — see the GLFW documentation for the full list.
19a. Full Example — launch_up.lua Patterns
Annotated excerpts from the real scripts/launch_up.lua showing the most important patterns.
init() — bulk object discovery + scale reset
function init()
local _, y, _ = engine.get_camera_position()
spawn_pos.y = y
-- Restore persisted cross-session state
score = engine.get_var("launch_up.score", 0.0)
peak_height = engine.get_var("launch_up.peak_height", spawn_pos.y)
current_level = math.floor(engine.get_var("launch_up.level", 1.0) + 0.5)
-- Re-discover named objects (IDs change on every scene load)
goal_id = engine.find_object("goal_pad")
goal_glow_id = engine.find_object("goal_pad_glow")
-- Bulk-discover all hazard spinners by naming convention
hazard_ids = engine.find_objects_by_prefix("platform_hazard_")
-- Reset scale state so hot-reload doesn't leave objects at wrong size
goal_scale_t = 0.0
cp_scale_timers = {}
if goal_id then
engine.set_object_visible(goal_id, false)
engine.set_object_scale(goal_id, 1.0)
end
for _, hid in ipairs(hazard_ids) do
engine.set_object_scale(hid, 1.0)
end
engine.print_status(
"Level " .. current_level .. "/" .. #LEVELS ..
" | Hazards: " .. #hazard_ids ..
" | Score: " .. string.format("%.0f", score)
)
end
update() — hazard rotation + breathe scale
local SPINNER_SPEED = 45.0 -- degrees/second
local function update_spinner(dt)
if #hazard_ids == 0 then return end
spinner_angle = (spinner_angle + SPINNER_SPEED * dt) % 360.0
local t = engine.get_time()
local breathe = 1.0 + 0.08 * math.sin(t * 2.5)
for _, hid in ipairs(hazard_ids) do
engine.set_object_rotation(hid, 0.0, spinner_angle, 0.0)
engine.set_object_scale(hid, breathe)
end
end
Goal pad — pop-in reveal + finish throb
-- On reveal: make it visible at tiny scale, lerp to 1.0 each frame
engine.set_object_visible(goal_id, true)
engine.set_object_scale(goal_id, 0.1)
-- Each frame while revealed:
local sx, _, _ = engine.get_object_scale(goal_id)
if sx < 0.99 then
engine.set_object_scale(goal_id, sx + (1.0 - sx) * 0.15)
end
-- On finish screen: throb between 1.0 and 1.15
local sc = 1.0 + 0.15 * (math.sin(goal_scale_t * 0.8 * 2 * math.pi) + 1.0) * 0.5
engine.set_object_scale(goal_id, sc)
Checkpoint — emissive flash + scale pop
-- On checkpoint hit:
engine.set_checkpoint(cp.pos[1], cp.pos[2], cp.pos[3])
cp_scale_timers[i] = 0.5 -- 0.5s pop
-- Each frame during flash:
local p = (math.sin(t * 4.0 * 2 * math.pi) + 1.0) * 0.5
engine.set_object_emissive(glow_id, 1.0, 0.5, 0.0, p * 6.0)
local progress = 1.0 - (timer / 0.5)
local pop_sc = 1.0 + 0.3 * math.sin(progress * math.pi)
engine.set_object_scale(glow_id, pop_sc)
15. Ray Casting
Cast a ray from any world-space origin in any direction and receive the closest geometry hit. Useful for hitscan weapons, line-of-sight checks, and proximity triggers.
engine.cast_ray(ox, oy, oz, dx, dy, dz, maxDist, excludeId) → hit, id, px, py, pz, nx, ny, nz, dist
Returns 9 values
Casts a ray from (ox,oy,oz) in direction (dx,dy,dz) (need not be normalised). Returns up to 9 values: hit (bool), id (object id, 0 if miss), hit point px,py,pz, surface normal nx,ny,nz, and dist (float, distance from origin). Pass excludeId to skip an object — typically the shooter's own id to avoid self-intersection. Pass 0 for no exclusion.
local hit, id, px, py, pz, nx, ny, nz, dist =
engine.cast_ray(cx, cy, cz, fx, fy, fz, 200.0, 0)
if hit then
print("Hit object " .. id .. " at dist " .. dist)
end
engine.get_object_rest_center(id) → x, y, z
Returns 3 floats
Returns the load-time centroid of an object — the mean of originalVertices over the object's vertex range. Immutable for the lifetime of the scene. This is the exact pivot used by applyAllObjectTransforms(), so a script that subtracts it before calling set_object_position lands the object exactly where intended and stays consistent across hot-reloads.
local cx, cy, cz = engine.get_object_rest_center(id)
-- place object so its centroid sits at target pos
engine.set_object_position(id, tx - cx, ty - cy, tz - cz)
16. Tagged Mesh Objects
Any SceneObject can carry a gameplay tag and a float property bag, authored via extras["ra_tag"] and extras["ra_props"] in the GLTF, or set at runtime. Tags identify roles — "target", "pickup", "door". Props hold per-instance state — health, points, cooldown timers.
engine.get_object_tag(id) → stringReturns the tag string for a mesh object. Empty string if untagged.
engine.set_object_tag(id, tag)Sets the tag on a mesh object at runtime.
engine.find_objects_by_tag(tag) → table of ids
Returns table
Finds all scene objects whose tag matches exactly. Returns an array of IDs (1-indexed, empty if none).
local targets = engine.find_objects_by_tag("target")
for _, id in ipairs(targets) do
local hp = engine.get_object_prop(id, "health", 100.0)
print("Target " .. id .. " has " .. hp .. " HP")
end
engine.get_object_prop(id, key, default) → floatReads a named float property from an object's props map. Returns default if the key does not exist.
engine.set_object_prop(id, key, value)Writes a named float property. The props map grows dynamically — any string key is valid.
17. Standalone Game Objects
Mesh-less entities: spawn points, waypoints, patrol nodes, invisible triggers. Loaded from GLTF nodes that carry extras["ra_type"]="game_object". IDs live in a separate space from SceneObject IDs — do not mix them.
find_object/find_objects_by_tag; standalone entities use find_game_object/find_game_objects_by_tag.
engine.find_game_object(name) → idFinds a standalone game object by GLTF node name. Returns 0 if not found.
engine.find_game_objects_by_tag(tag) → table of idsReturns IDs of all standalone game objects (active and inactive) with the given tag.
engine.get_game_object_position(id) → x, y, zWorld-space position of a standalone game object.
engine.set_game_object_position(id, x, y, z)Moves a standalone game object. Affects only the CPU-side position — game objects have no geometry or BLAS.
engine.get_game_object_tag(id) → stringReturns the tag of a standalone game object.
engine.is_game_object_active(id) → boolReturns true if the game object is active. Inactive objects can be treated as disabled/consumed.
engine.set_game_object_active(id, bool)Activates or deactivates a standalone game object.
engine.get_game_object_prop(id, key, default) → floatReads a named float property from a standalone game object.
engine.set_game_object_prop(id, key, value)Writes a named float property to a standalone game object.
18. Ambient Sky Color
Set the background and GI ambient contribution color from Lua. Takes effect immediately on the next frame (UBO upload). Two colors are blended in the miss shader: top (sky zenith) and horizon (ground/sides).
engine.set_ambient_color(topR, topG, topB, horizonR, horizonG, horizonB)
All values are linear RGB, 0–1. Written to the six ambientSky* UBO tail fields and read by miss.rmiss. No TLAS rebuild needed.
-- Deep purple night sky
engine.set_ambient_color(0.02, 0.00, 0.08, 0.08, 0.04, 0.12)
-- Warm orange sunset horizon
engine.set_ambient_color(0.3, 0.5, 0.9, 0.9, 0.4, 0.1)
19. Quick Reference — All 48 Functions
| Function | Returns | Description |
|---|---|---|
| engine.get_camera_position() | x, y, z | Player world position |
| engine.set_camera_position(x,y,z) | — | Teleport player |
| engine.get_camera_forward() | x, y, z | Normalised look direction |
| engine.get_camera_right() | x, y, z | Normalised right vector |
| engine.get_physics_state() | table | is_grounded, is_jumping, vertical_velocity |
| engine.set_vertical_velocity(v) | — | Jump / cancel falling |
| engine.get_input() | table | key_down, key_just_pressed, mouse_delta |
| engine.get_time() | double | Wall-clock seconds (for animations) |
| engine.get_delta_time() | float | Frame delta seconds |
| engine.find_object(name) | id | ID by GLTF node name; 0 if missing |
| engine.object_exists(id) | bool | Validate an object ID |
| engine.get_object_position(id) | x, y, z | Object world position |
| engine.set_object_position(id,x,y,z) | — | Move object (triggers BLAS rebuild) |
| engine.get_object_rotation(id) | x, y, z | Euler degrees YXZ |
| engine.set_object_rotation(id,x,y,z) | — | Set rotation |
| engine.get_object_scale(id) | sx, sy, sz | Current scale (use for lerp base) |
| engine.set_object_scale(id, s) | — | Uniform scale |
| engine.set_object_scale(id,sx,sy,sz) | — | Non-uniform scale |
| engine.set_object_emissive(id,r,g,b,i?) | — | Emissive colour + intensity (drives RTX GI) |
| engine.set_object_visible(id, bool) | — | Show / hide in BVH |
| engine.get_object_aabb(id) | 6 floats | World AABB min/max |
| engine.get_player_aabb() | 6 floats | Player capsule AABB |
| engine.player_overlaps_object(id) | bool | Trigger zone test |
| engine.find_objects_by_prefix(pfx) | table of IDs | Bulk lookup by name prefix |
| engine.get_all_object_names() | table of strings | All scene node names |
| engine.load_scene(path) | — | Deferred scene transition |
| engine.set_checkpoint(x,y,z) | — | Set respawn position |
| engine.get_checkpoint() | x, y, z | Get respawn position |
| engine.set_var(key, value) | — | Persist float (survives hot-reload) |
| engine.get_var(key, default) | float | Read persisted float |
| engine.print_status(text) | — | Terminal status line (captured by F1 console) |
| engine.set_hud_data(score, height) | — | Update in-viewport HUD |
| Ray Casting & Object Queries | ||
| engine.cast_ray(ox,oy,oz, dx,dy,dz, maxDist, excludeId) | hit, id, px,py,pz, nx,ny,nz, dist | Hitscan / line-of-sight |
| engine.get_object_rest_center(id) | x, y, z | Immutable load-time centroid (pivot for transforms) |
| Tagged Mesh Objects | ||
| engine.get_object_tag(id) | string | Tag of a mesh object |
| engine.set_object_tag(id, tag) | — | Set tag at runtime |
| engine.find_objects_by_tag(tag) | table of ids | All mesh objects with matching tag |
| engine.get_object_prop(id, key, default) | float | Per-instance float property |
| engine.set_object_prop(id, key, value) | — | Write per-instance float property |
| Standalone Game Objects | ||
| engine.find_game_object(name) | id | Standalone entity by name |
| engine.find_game_objects_by_tag(tag) | table of ids | All standalone entities with tag |
| engine.get_game_object_position(id) | x, y, z | Entity world position |
| engine.set_game_object_position(id,x,y,z) | — | Move entity |
| engine.get_game_object_tag(id) | string | Entity tag |
| engine.is_game_object_active(id) | bool | Active state |
| engine.set_game_object_active(id, bool) | — | Activate / deactivate entity |
| engine.get_game_object_prop(id, key, default) | float | Entity float property |
| engine.set_game_object_prop(id, key, value) | — | Write entity float property |
| Ambient Sky | ||
| engine.set_ambient_color(tR,tG,tB, hR,hG,hB) | — | Sky top + horizon color (linear RGB 0–1) |
Total: 48 functions