Lua Scripting
Attach a Lua Script component to an entity and point it at a .lua file to give that entity
behaviour. A script can define any of these lifecycle functions - all are optional:
function OnCreate()
-- Runs once, when the entity is created / the scene starts
end
function OnUpdate(deltaTime)
-- Runs once per rendered frame
end
function OnFixedUpdate()
-- Runs on a fixed timestep - use this for physics-affecting logic
end
function OnDestroy()
-- Runs once, just before the entity is destroyed
end
Every script has two globals available without needing to look anything up:
CurrentEntity- the entity this script is attached toCurrentScene- the currently loaded Scene, for finding and spawning entities, raycasts and mouse picking
Behaviour tree custom tasks
A behaviour tree's Custom Task node also runs a Lua script, with its own set of functions
(OnStateEntry, OnStateUpdate, OnStateExit) and a Blackboard global. See
Behaviour Trees.
Reading and writing components
Every component type X gets CurrentEntity:AddX(), CurrentEntity:GetX(),
CurrentEntity:HasX(), CurrentEntity:GetOrAddX() and CurrentEntity:RemoveX() - see the
Entity page for the full list of components. For example, moving an entity
based on input:
function OnUpdate(deltaTime)
local transform = CurrentEntity:GetTransformComponent()
if not transform then return end
local speed = 3.0
if Input.IsKeyPressed('D') then
transform.Position = transform.Position + Vec3.new(speed * deltaTime, 0, 0)
end
if Input.IsKeyPressed('A') then
transform.Position = transform.Position - Vec3.new(speed * deltaTime, 0, 0)
end
end
Logging
function OnCreate()
Log.Info("Entity created: " .. CurrentEntity:GetName())
end
See the Log page for all the available log levels.
Spawning prefabs
A "prefab" is just a small scene file containing whatever entity/entities you want to spawn
repeatedly - use LoadScene to load it, then Scene:InstantiateScene to spawn a copy of it
into the current scene at a given position:
function OnCreate()
local prefab = LoadScene("Scenes/Obstacle.scene")
CurrentScene:InstantiateScene(prefab, Vec3.new(5, 0, 0))
end
Changing scenes
function OnCreate()
ChangeScene("Scenes/MainMenu.scene")
end
Communicating between entities with signals
Two entities' scripts don't have a direct reference to each other - Signal
is how they talk without one. Signal.Emit broadcasts a named signal; any entity that's called
Signal.Connect for that name receives it, regardless of where it is in the scene.
Here, a switch entity emits a signal when pressed, and a separate door entity reacts to it:
-- Attached to the "Switch" entity
function OnUpdate(deltaTime)
if Input.IsKeyPressed('E') then
local data = {}
data.opened = true
Signal.Emit("SwitchToggled", CurrentEntity, data)
end
end
-- Attached to the "Door" entity
function OnCreate()
Signal.Connect("SwitchToggled", CurrentEntity, function(sender, data)
Log.Info(sender:GetName() .. " toggled the switch")
local transform = CurrentEntity:GetTransformComponent()
if transform then
transform.Position = transform.Position + Vec3.new(0, 2, 0)
end
end)
end
-- Always disconnect what you connected, so a destroyed/disabled entity doesn't keep
-- reacting to signals after it's gone.
function OnDestroy()
Signal.Disconnect("SwitchToggled", CurrentEntity)
end
The callback receives the sender (the entity that emitted the signal) and data (the table
passed to Emit), so you can pass along whatever information the listener needs.
Pathfinding across a tilemap
Pathfinding.FindPath(start, goal, tilemapEntity) runs A* over an orthogonal, isometric
or hexagonal tilemap and returns an array of Vec2 waypoints, the centre of each tile from start
to goal (for isometric and hex maps, the middle of each tile's diamond or hex). Tiles
whose tileset tile has a collision shape are treated as walls; empty tiles and tiles without
a collision shape are walkable. The grid comes straight from the tilemap, so it always matches
what's drawn, even if the tilemap entity is moved, scaled, rotated or parented.
local speed = 3.0
local path = {}
local nextPoint = 1
function OnCreate()
local level = CurrentScene:FindEntity("Level")
local player = CurrentScene:FindEntity("Player")
local from = CurrentEntity:GetTransformComponent().Position
local to = player:GetTransformComponent().Position
path = Pathfinding.FindPath(Vec2.new(from.x, from.y), Vec2.new(to.x, to.y), level)
end
function OnUpdate(deltaTime)
local target = path[nextPoint]
if target == nil then return end
local transform = CurrentEntity:GetTransformComponent()
local toTarget = Vec3.new(target.x, target.y, transform.Position.z) - transform.Position
local distance = toTarget:Length()
local step = speed * deltaTime
if distance <= step then
transform.Position = Vec3.new(target.x, target.y, transform.Position.z)
nextPoint = nextPoint + 1
else
transform.Position = transform.Position + toTarget * (step / distance)
end
end
The result is empty if either end is off the tilemap or on a wall, or if no route exists.
Pass false as a fourth argument to limit movement to up/down/left/right; diagonal moves
never cut the corner of a wall. A tilemap marked isTrigger doesn't block anything.
On hex maps every step moves to one of the six neighbouring hexes, so the fourth argument has no effect.
Where to go next
The Lua API Reference documents every property/function currently exposed this way, grouped by component - it's generated directly from the engine's source, so it always reflects what's actually available. Not every component is fully documented there yet (only ones migrated to the newer binding macros show their fields/functions); anything not listed yet is still usable, just not documented here for the moment.