← Main Docs Lua 5.x Hot-Reload scripts/
Lua 5.x LuaGameModule scripts/launch_up.lua

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.

Game code is isolated. Lua scripts never include Vulkan, GLFW, or engine headers. All engine access goes through the 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.

function init() required

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().

function update(dt) required

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.

dtfloatSeconds elapsed since the last frame. Capped to prevent physics explosion on lag spikes.
function shutdown() required

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.

function get_status_line() → string optional

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:

  1. Calls shutdown() on the current Lua state
  2. Destroys the old lua_State
  3. Opens a fresh state and re-binds the full engine API
  4. Runs the script file
  5. Calls init() again
Lua variables do not survive hot-reload. Any local Lua state (variables, tables, closures) is destroyed when the 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
Namespace your keys. Prefix all variable keys with your game name (e.g. "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.

engine.get_camera_position() → x, y, z get

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
engine.set_camera_position(x, y, z) set

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)
engine.get_camera_forward() → x, y, z get

Returns the normalised camera look direction. Useful for shooting, raycasting, or directional movement logic.

engine.get_camera_right() → x, y, z get

Returns the normalised camera right vector (perpendicular to forward in the horizontal plane). Use with forward for strafe-relative logic.

05. Physics

engine.get_physics_state() → table get

Returns a table describing the player's current physics state.

is_groundedboolTrue if the player is standing on solid geometry this frame.
is_jumpingboolTrue during the upward arc of a jump.
vertical_velocityfloatCurrent vertical speed in m/s. Negative = falling.
engine.set_vertical_velocity(v) set

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

vfloatVelocity in m/s. Positive = upward.

06. Input

engine.get_input() → table get

Returns a snapshot of the current frame's input state. Read this once at the top of update().

key_downtableIndexed by GLFW key code. true if the key is currently held. Use for continuous actions.
key_just_pressedtabletrue only on the single frame the key went down. Use for discrete actions (restart, next level).
mouse_delta_xfloatMouse horizontal movement this frame in pixels.
mouse_delta_yfloatMouse vertical movement this frame in pixels.
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
key_just_pressed is edge-triggered. It is true for exactly one frame. Always read input once at the top of update() — never inside a loop.

07. Time

engine.get_time() → double get

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)
engine.get_delta_time() → float get

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().

ID 0 means not found. engine.find_object() returns 0 when no object with that name exists. Always check before using an ID.

Transform

engine.find_object(name) → id query

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
engine.object_exists(id) → bool query

Returns true if id is non-zero and present in the current scene. Use as a guard before transform calls.

engine.get_object_position(id) → x, y, z get

Returns the world-space position of the object's transform origin as three floats.

engine.set_object_position(id, x, y, z) set

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.

engine.get_object_rotation(id) → x, y, z get

Returns Euler rotation in degrees (YXZ order, matching the Ra Engine editor).

engine.set_object_rotation(id, x, y, z) set

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
engine.get_object_scale(id) → sx, sy, sz get

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
engine.set_object_scale(id, s)  or  engine.set_object_scale(id, sx, sy, sz) set

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

engine.set_object_emissive(id, r, g, b [, intensity]) set

Sets the emissive colour and intensity at runtime. Drives RTX GI — nearby geometry is lit by the emissive surface. intensity defaults to 1.0.

r, g, bfloatEmissive colour in [0, 1].
intensityfloatMultiplier. 0.0 = off, 1.0 = normal, >1.0 = boosted. Values above ~6.0 saturate nearby surfaces.
-- 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)
engine.set_object_visible(id, visible) set

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
Note: Physics collision is currently unaffected by visibility — hidden objects still block the player. This is a known engine limitation.

Collision & Triggers

engine.get_object_aabb(id) → min_x, min_y, min_z, max_x, max_y, max_z get

Returns six floats describing the world-space AABB of the object. Returns a degenerate zero-size AABB if the ID is invalid.

engine.get_player_aabb() → min_x, min_y, min_z, max_x, max_y, max_z get

Returns the player's world-space AABB (a capsule approximation centred on camera position).

engine.player_overlaps_object(id) → bool query

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

engine.find_objects_by_prefix(prefix) → table of IDs query

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
engine.get_all_object_names() → table of strings query

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

engine.load_scene(path) action

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.

pathstringPath relative to the working directory, e.g. "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
Score persists across load_scene. Any values written with 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.

engine.set_checkpoint(x, y, z) set

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.

engine.get_checkpoint() → x, y, z get

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.

engine.set_var(key, value) set

Stores a float value under an arbitrary string key.

keystringArbitrary identifier. Prefix with your game name.
valuefloatValue to store. Encode booleans as 0/1, level indices as floats.
engine.get_var(key, default) → float get

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

engine.print_status(text) action

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.

engine.set_hud_data(score, height) set

Pushes score and height to the in-viewport HUD overlay (top-left bitmap font readout). Call every frame while playing.

scorefloatCurrent score in points.
heightfloatHeight gained from spawn in metres (not absolute Y).
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_SPACE32
KEY_W87
KEY_A65
KEY_S83
KEY_D68
KEY_R82
KEY_N78
KEY_UP265
KEY_DOWN264
KEY_LEFT263
KEY_RIGHT262
KEY_ESCAPE256
KEY_ENTER257
KEY_LEFT_SHIFT340
Need a key not listed? The 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.

15. 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)

Quick Reference

FunctionReturnsDescription
engine.get_camera_position()x, y, zPlayer world position
engine.set_camera_position(x,y,z)Teleport player
engine.get_camera_forward()x, y, zNormalised look direction
engine.get_camera_right()x, y, zNormalised right vector
engine.get_physics_state()tableis_grounded, is_jumping, vertical_velocity
engine.set_vertical_velocity(v)Jump / cancel falling
engine.get_input()tablekey_down, key_just_pressed, mouse_delta
engine.get_time()doubleWall-clock seconds (for animations)
engine.get_delta_time()floatFrame delta seconds
engine.find_object(name)idID by GLTF node name; 0 if missing
engine.object_exists(id)boolValidate an object ID
engine.get_object_position(id)x, y, zObject world position
engine.set_object_position(id,x,y,z)Move object (triggers BLAS rebuild)
engine.get_object_rotation(id)x, y, zEuler degrees YXZ
engine.set_object_rotation(id,x,y,z)Set rotation
engine.get_object_scale(id)sx, sy, szCurrent 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 floatsWorld AABB min/max
engine.get_player_aabb()6 floatsPlayer capsule AABB
engine.player_overlaps_object(id)boolTrigger zone test
engine.find_objects_by_prefix(pfx)table of IDsBulk lookup by name prefix
engine.get_all_object_names()table of stringsAll scene node names
engine.load_scene(path)Deferred scene transition
engine.set_checkpoint(x,y,z)Set respawn position
engine.get_checkpoint()x, y, zGet respawn position
engine.set_var(key, value)Persist float (survives hot-reload)
engine.get_var(key, default)floatRead persisted float
engine.print_status(text)Terminal status line (captured by F1 console)
engine.set_hud_data(score, height)Update in-viewport HUD