Repository navigation
Entity Movement
Scripts for moving props, platforms, and characters around. Position is a Vector3 where x and
z are the ground plane and y is height. Move something by reading transform.position,
building a new Vector3, and assigning it back (or use transform:addPosition(...) for relative
moves).
Two ways to move something:
-
Teleport-style (
transform.position = ...): simple and precise, but ignores walls and colliders. Good for props, pickups, and scripted platforms. -
Physics-style (
rigidbody.velocity = ...): the entity collides with the world and is affected by gravity. Good for characters. The enemy examples in 12_enemy_ai.md use this when a Rigidbody is present.
Attach to: the moving Entity. Create two empty Entities as waypoints and assign them.
-- Scripts/Movement/Patrol.lua (entity script)
public Entity pointA = nil
public Entity pointB = nil
public number speed = 30
public number waitTime = 1.0
local transform
local goingToB = true
local waiting = 0.0
function Start()
transform = entity.transform
end
function Update()
if pointA == nil or pointB == nil then return end
local dt = GameTime.deltaTime
if waiting > 0 then
waiting = waiting - dt
return
end
local target = goingToB and pointB or pointA
local goal = target.transform.position
local p = transform.position
local dx, dz = goal.x - p.x, goal.z - p.z
local dist = math.sqrt(dx * dx + dz * dz)
local step = speed * dt
if dist <= step then
-- Arrived: snap to the point, turn around, and pause.
transform.position = Vector3(goal.x, p.y, goal.z)
goingToB = not goingToB
waiting = waitTime
else
transform.position = Vector3(p.x + dx / dist * step, p.y, p.z + dz / dist * step)
transform:lookDirection(Vector3(dx, 0, dz)) -- face the way we're going
end
endNotes
- Movement stays on the ground plane and keeps the entity's own
y. -
transform:lookDirectionturns the entity'srotationto face a direction (left and right only, by default). Directional sprites use the rotation to pick which side to show. - Want a longer route? Replace the two points with a table of waypoints and an index.
Attach to: the orbiting Entity.
-- Scripts/Movement/Orbit.lua (entity script)
public Entity center = nil
public number radius = 40
public number degreesPerSecond = 90
local transform
local angle = 0.0
local cx, cz = 0.0, 0.0
function Start()
transform = entity.transform
local p = transform.position
if center ~= nil then
-- Orbit the assigned Entity, starting at the angle we are already at around it.
local c = center.transform.position
cx, cz = c.x, c.z
angle = math.atan(p.z - cz, p.x - cx)
else
-- No center: put it `radius` behind the start position so the entity starts on its orbit.
cx, cz = p.x - radius, p.z
end
end
function Update()
angle = angle + mathT.DegToRad(degreesPerSecond) * GameTime.deltaTime
local p = transform.position
transform.position = Vector3(cx + math.cos(angle) * radius, p.y, cz + math.sin(angle) * radius)
endNotes
- With a
centerassigned, the entity picks up the orbit at whatever angle it starts at (its distance from the center is replaced byradius). With nocenter, the orbit circle is placed so the entity's starting position is on it, and it doesn't jump on the first frame.
Attach to: a pickup, a floating item, a decoration. It should not have a Rigidbody, or gravity will fight the bobbing.
-- Scripts/Movement/BobAndSpin.lua (entity script)
public number bobHeight = 3
public number bobSpeed = 2
public number spinDegreesPerSecond = 120
local transform
local baseY = 0.0
local phase = 0.0
local yaw = 0.0
function Start()
transform = entity.transform
baseY = transform.position.y
phase = mathT.RandomF(0, 6.28) -- so a row of pickups doesn't bob in lockstep
end
function Update()
local dt = GameTime.deltaTime
phase = phase + bobSpeed * dt
yaw = yaw + math.rad(spinDegreesPerSecond) * dt
local p = transform.position
transform.position = Vector3(p.x, baseY + math.sin(phase) * bobHeight, p.z)
-- Rotation is a quaternion (x, y, z, w). Rotating about the Y axis by `yaw`:
transform.rotation = Vector4(0, math.sin(yaw / 2), 0, math.cos(yaw / 2))
endNotes
- Billboard sprites always face the camera, so the spin only matters for entities that render with their rotation.
Attach to: the follower.
Eases toward a target instead of snapping, using exponential smoothing so the feel is the same at any frame rate.
-- Scripts/Movement/Follow.lua (entity script)
public Entity target = nil
public number followSpeed = 4
public Vector3 offset = Vector3(0, 0, 0)
local transform
function Start()
transform = entity.transform
end
function Update()
if target == nil or not target.isValid then return end
-- 1 - e^(-k*dt) is a frame-rate independent blend factor.
local k = 1 - math.exp(-followSpeed * GameTime.deltaTime)
local p = transform.position
local t = target.transform.position
transform.position = Vector3(
mathT.Lerp(p.x, t.x + offset.x, k),
mathT.Lerp(p.y, t.y + offset.y, k),
mathT.Lerp(p.z, t.z + offset.z, k)
)
endAttach to: any prop you want to push around with the arrow keys.
-- Scripts/Movement/KeyboardMover.lua (entity script)
public number speed = 40
local transform
function Start()
transform = entity.transform
end
function Update()
local dx, dz = 0.0, 0.0
if Input.GetKey(Key.Left) then dx = dx - 1 end
if Input.GetKey(Key.Right) then dx = dx + 1 end
if Input.GetKey(Key.Up) then dz = dz + 1 end
if Input.GetKey(Key.Down) then dz = dz - 1 end
if dx == 0 and dz == 0 then return end
-- Normalise so diagonals aren't faster.
local len = math.sqrt(dx * dx + dz * dz)
local step = speed * GameTime.deltaTime
transform:addPosition(Vector3(dx / len * step, 0, dz / len * step))
transform:lookDirection(Vector3(dx, 0, dz))
endNotes
-
Input.GetKeyis true while held,GetKeyDownonly on the frame it was pressed, andGetKeyUponly on the frame it was released. Keys come from theKeytable:Key.A-Key.Z,Key.Alpha0-Key.Alpha9,Key.Space,Key.Escape,Key.Enter,Key.Tab,Key.Backspace,Key.Left/Right/Up/Down,Key.LShift/RShift,Key.LCtrl/RCtrl,Key.LAlt/RAlt.