Lua 5.4 is the most direct Rustic scripting option: the runtime is bundled, each
script is an isolated module, and Script API mutations are applied immediately.
Create a .lua file through the Explorer's scripting commands; do not create an
Asset ID or edit a scene file manually.
- Global Startup Script: one game-lifetime instance, started when Play starts.
- Scene Startup Script: one scene-lifetime instance, started after globals when
its
scene/{name}.sceneloads. - Object Component Script: one instance per attachment, created and removed with its object. Select the object and use + Add Component, or drag the script from Explorer onto the viewport.
Moving or renaming the .lua file in the editor is safe because the attachment holds
its persistent Asset ID. Do not edit config/scripts.ron.
Return one behavior table. Every member is optional; Rustic looks up a callback before calling it. Returning no value is also accepted and creates a no-op behavior.
local speed = 4.0 -- private state for this behavior instance
return {
Start = function()
rustic.log("info", "started " .. rustic.entity_id())
end,
OnEnable = function() end,
FixedUpdate = function(dt)
local key = rustic.key("KeyW")
if key.held then
local x, y, z = rustic.get_translation()
rustic.set_translation(x, y, z + speed * dt)
end
end,
Update = function(dt) end,
OnDisable = function() end,
OnDestroy = function() end,
}Canonical public names are Start, FixedUpdate, Update, OnEnable,
OnDisable, and OnDestroy. The legacy spellings on_start, fixed_update,
update, on_enable, on_disable, and on_destroy still work. The legacy internal
hooks on_create and on_stop are also recognized. If both canonical and legacy
names are present, the canonical callback wins. Use FixedUpdate(dt) for physics and
deterministic movement; use Update(dt) for frame-rate work.
OnStart is not a Lua callback name. A script with OnStart can load successfully
while its startup function never runs; rename it to Start.
The engine-wide callback model also defines OnCollisionEnter,
OnCollisionStay, and OnCollisionExit. The current embedded Lua adapter does not
yet bind those three names, so do not rely on them in Lua code in this release.
The read API returns engine-owned snapshots; writes validate against the host:
local id = rustic.entity_id() -- stable entity ID string
local frame_dt = rustic.delta_time()
local fixed_dt = rustic.fixed_delta_time()
local x, y, z = rustic.get_translation()
rustic.set_translation(x, y + 1, z)
local health = rustic.get_property("health") -- nil if absent
rustic.set_property("health", 90) -- declared property only
local name = rustic.GetAttribute("Name")
rustic.EditAttribute("Anchored", true)
print("entity", id)
warn("entity needs attention", id)
rustic.set_enabled(false)print(...) writes an info entry and warn(...) writes a warn entry to the
Rustic console. Multiple arguments are converted with Lua's tostring and separated
by tabs. rustic.log(level, message) remains available when an explicit level is
needed.
get_attribute/GetAttribute and edit_attribute/EditAttribute are aliases.
Built-in attributes are Name, Position, Size, Color, CanTouch,
CanCollide, Anchored, and Parent. Attribute and property writes must match the
existing value's type. Attribute setters accept scalar values, exactly three finite
numbers in a table for Position, Size, and RGB Color, and a stable entity ID
string or nil for Parent. Public-property setters retain their existing scalar
write rules. To edit another object, use
rustic.game.Demo.Room.Player:EditAttribute("Position", {1, 2, 3}); see
Edit scene objects for setup and supported types.
Invalid values fail and disable the offending script.
local table_id = Game.scene.Find("Room.Table")
local all_in_room = Game.scene.List("Room")
local direct_root_child = Game.scene.Camera
local new_id = instance.add("Cube")
local model_id = instance.add("assets/models/chair.glb", table_id)
local copy_id = instance.clone("Room.Table")
Game.setCurrentCamera("Room.Camera")Paths may be dotted or slash-separated. Find returns an ID or nil; List
returns stable IDs. instance.add accepts an entity ID/path, an Explorer model path,
or Part, Cube, Sphere, Cylinder, Plane, Rectangle2D, or Circle2D.
clone copies an existing scene object. The optional parent must be a stable entity
ID string. Both calls return the new ID immediately. Camera selection accepts a path,
ID, or the compatibility form { "Game.scene.Room.Camera" }.
The editor forwards held keys to scripts while its Play viewport is hovered or
focused. Click the game view once if another panel has focus. The forwarded names
are KeyW, KeyA, KeyS, KeyD, ArrowUp, ArrowDown, ArrowLeft,
ArrowRight, ShiftLeft, and ShiftRight. Either physical Shift key sets both
Shift names because the editor currently receives a combined Shift modifier.
For these names, rustic.key(name).held is true while the key is held and false
after release or when the Play viewport loses keyboard focus. axis is 1 when
held and 0 otherwise.
The rustic.key(name) result also has pressed and released fields, but the
current Play bridge does not forward press/release edges. Those fields remain
false. rustic.key_events() returns an empty list, and
rustic.any_key_pressed() remains false. Named rustic.input("action") actions
remain inactive. Other names, including Space, Escape, Digit1, and function
keys, are not forwarded yet. Keyboard forwarding currently applies to the editor's
embedded Play viewport; a separate New Window or Standalone runtime
window does not send its keyboard events to scripts. Use .held with the names
listed above for gameplay movement in this build.
- In the editor, create a Lua 5.4
.luaObject Component Script..luauuses a different adapter. If the file already exists, select the player object and attach it with + Add Component in the Inspector. - Replace the script contents with the example below. Keep
return { ... }and useStart, notOnStart. - Start Play, hover or click the game viewport, then hold WASD or an arrow key. Hold Shift to sprint. The Console should show both startup messages.
local WALK_SPEED = 4.0
local SPRINT_SPEED = 7.0
local PLAYER_HEIGHT = 1.0
local function held(primary, alternate)
return rustic.key(primary).held or rustic.key(alternate).held
end
return {
Start = function()
print("Character ready - use WASD or arrow keys to move")
warn("Movement controller is running")
end,
FixedUpdate = function(dt)
local horizontal = 0
local vertical = 0
if held("KeyA", "ArrowLeft") then horizontal = horizontal - 1 end
if held("KeyD", "ArrowRight") then horizontal = horizontal + 1 end
if held("KeyW", "ArrowUp") then vertical = vertical - 1 end
if held("KeyS", "ArrowDown") then vertical = vertical + 1 end
if horizontal == 0 and vertical == 0 then return end
local length = math.sqrt(horizontal * horizontal + vertical * vertical)
local speed = WALK_SPEED
if held("ShiftLeft", "ShiftRight") then speed = SPRINT_SPEED end
local x, _, z = rustic.get_translation()
rustic.set_translation(
x + horizontal / length * speed * dt,
PLAYER_HEIGHT,
z + vertical / length * speed * dt
)
end,
}This moves the object carrying the component, not a player object found by
name. Diagonal motion is normalized so it has the same speed as straight motion.
PLAYER_HEIGHT is applied when movement starts; change it to match your scene.
Make sure the active game camera can see the object and has enough room to show
the translation.
If the startup messages are missing, check that the file is .lua, the script is
attached and enabled, the callback is named Start, and Play actually started.
If messages appear but movement does not, use the editor's embedded Play viewport,
hover or click it, and try KeyW first. Check the Console for a script error: a
failing callback is disabled until the script is fixed and reloaded or Play restarts.
Lua cannot directly call another behavior's VM object. API 1.1 adds shared
Events.on, once, emit, and object signals across every supported script type. See
Shared gameplay actions for payloads and lifetime rules.
Lua cannot access io, os, package, debug, dofile, loadfile, require,
or collectgarbage. It has no filesystem, network, process, registry, or editor
access. An exception or exhausted instruction budget disables only that behavior.
Use the Console diagnostic to open the source location, fix it, then reload scripts.
See Edit scene objects for named-scene hierarchy calls, supported attributes, copyable examples, and native SDK calls to edit another object. Use your language's native call syntax and its current runtime limitations.
Lua owner and path-based EditAttribute calls accept three-number tables for
Position, Size, and RGB Color; Parent accepts a stable entity ID string or nil.
Vectors require exactly three finite components. These conversions apply to built-in
attributes; script public-property setters retain their existing value rules.
API 1.1 exposes shared-core easing, tweens, movement, skeletal/keyframe/procedural animation, timelines, timers, paths, cameras, physics, effects, audio and signals. See Shared gameplay actions for attachment, native call conventions, duration/speed options, callbacks, scene-clock controls and backend limits.