Appearance
Luau essentials
The language scripts are written in is called Luau. This page teaches enough of it to build things, assuming you have never programmed before. It takes about twenty minutes and you can come back to it.
If you have written JavaScript, Python or Lua, skim the boxes marked worth knowing — the rest will be familiar.
Values
A value is a piece of information. There are four kinds you will use.
luau
local name = "Mira" -- text (a string)
local coins = 40 -- a number
local locked = true -- true or false (a boolean)
local nothing = nil -- "there is nothing here"local means this belongs to me. Always write it. A variable without local is visible to your whole script and is a common source of confusing bugs.
Numbers are just numbers. There is no separate whole-number type: 3 and 3.5 are the same kind of thing.
nil is not zero and not false — it means absent. Reading something that was never set gives you nil, which is why so many examples check for it.
Doing things with values
luau
local total = 10 + 5 -- 15
local half = 10 / 4 -- 2.5
local rest = 10 % 3 -- 1 (remainder)
local greeting = "Hello, " .. name -- "Hello, Mira"Joining text uses two dots (..), not +. Adding a number to text is an error; convert first:
luau
print("You have " .. tostring(coins) .. " coins")Shorthands you will see everywhere:
luau
coins += 1 -- same as coins = coins + 1
coins -= 5
coins *= 2Making decisions
luau
if coins >= 100 then
print("rich")
elseif coins >= 10 then
print("getting there")
else
print("broke")
endThe comparisons: == (equal), ~= (not equal — not !=), <, >, <=, >=.
Combining them:
luau
if locked and coins >= 10 then ... end
if locked or coins >= 10 then ... end
if not locked then ... endWorth knowing: only false and nil are treated as "no". Zero is true in a condition, and so is an empty string. if coins then is true even when coins is 0 — write if coins > 0 then when that is what you mean.
The short form
This appears constantly in real scripts:
luau
local angle = open and 90 or 0Read it as 90 if open, otherwise 0. It is the same as the four-line if, and it is safe as long as the middle value is never false or nil.
Repeating things
luau
for i = 1, 5 do
print(i) -- 1, 2, 3, 4, 5
end
for i = 10, 0, -2 do
print(i) -- 10, 8, 6, 4, 2, 0
endNever write a loop without an end. while true do ... end will use up your script's instruction budget and the script will be killed — it stops running until you save it again. Anything that should happen repeatedly over time is a timer, not a loop:
luau
self:on_timer(1, function()
-- once a second, forever, costing nothing in between
end)Functions
A function is a named piece of behaviour you can run more than once.
luau
local function greet(who: string)
print("Hello, " .. who)
end
greet("Mira")
greet("Ash")They can hand a value back:
luau
local function double(n: number): number
return n * 2
end
local x = double(21) -- 42Functions passed as arguments
This is the shape every event uses, so it is worth staring at for a moment:
luau
self:on_touch(function(player)
print(player.name)
end)function(player) ... end with no name is a function being handed toon_touch, to be kept and called later. The same thing, written out longhand:
luau
local function whenTouched(player)
print(player.name)
end
self:on_touch(whenTouched)Both are fine. The first is shorter and is what you will see most.
Tables
A table holds several values. It is the only container Luau has, and it does two jobs.
As a list
luau
local colors = { "red", "green", "blue" }
print(colors[1]) -- "red" (counting starts at 1, not 0)
print(#colors) -- 3 (# means "how many")
table.insert(colors, "yellow")
table.remove(colors, 1) -- removes "red"
for i, c in ipairs(colors) do
print(i .. ": " .. c)
endAs a set of named fields
luau
local door = {
open = false,
speed = 0.6,
}
print(door.open) -- false
door.open = trueThis is the form every API option uses:
luau
self:tween({ rot = { x = 0, y = 90, z = 0 }, seconds = 0.6, ease = "in_out" })Worth knowing: when a function takes exactly one table, you may drop the parentheses. These two lines are identical:
luau
self:tween({ seconds = 1, to = here })
self:tween{ seconds = 1, to = here }Both appear in this documentation. Neither is more correct.
Dots and colons
This trips up everyone once.
luau
self:move_to(0, 5, 0) -- colon: calling a method ON self
self.store -- dot: reading a field OF selfUse : when you are telling something to do something. Use . when you are reading a value out of it. If you get attempt to call a nil value, a swapped dot and colon is the first thing to check.
Types (optional, and worth it)
The --!strict on line 1 turns on checking. You can then say what kind of value something is:
luau
--!strict
local function damage(amount: number, target: string): boolean
return amount > 0
endYou are not required to annotate anything. What --!strict buys you is that the editor catches typos and wrong arguments before you save, instead of you finding them by clicking around in the world.
Nil-safety, the one habit worth forming
Many API calls answer with a value or nil plus a reason. Reading a field of nil is the single most common script error.
luau
local part = self:part("hinge")
if not part then
print("no part called 'hinge'")
return
end
part:rotate_by(0, 5, 0) -- safe: we know it existsThe pattern is always the same: check, bail out early, then carry on. It keeps the interesting code out of a nest of ifs.
Two shorthands for defaults:
luau
local n = tonumber(self.store:get("count") or "0") or 0a or b means a, unless it is nil or false, in which case b. Here it survives both a missing stored value and text that is not a number.
Useful built-ins
luau
math.floor(3.7) -- 3
math.ceil(3.2) -- 4
math.abs(-5) -- 5
math.min(3, 9) -- 3
math.max(3, 9) -- 9
math.random(1, 6) -- a whole number from 1 to 6
math.random() -- a fraction from 0 to 1
tostring(42) -- "42"
tonumber("42") -- 42
tonumber("banana") -- nil
string.upper("hi") -- "HI"
string.format("%.1f", 3.14159) -- "3.1"
#"hello" -- 5 (length of text)Comments
luau
-- a note to yourself; the engine ignores it
--[[
several lines
]]Putting it together
A lamp that is off by default, turns on when someone comes near, and turns itself off a few seconds after the last person leaves:
luau
--!strict
local self = require("self")
local inside = 0
local offTimer = nil
local function setLight(on: boolean)
self:set({
light = on,
emissive = on and 2 or 0,
color = on and "#ffe9b0" or "#3a3a3a",
})
end
setLight(false)
self:on_region({
radius = 6,
near = function(player)
inside += 1
if offTimer then
offTimer:cancel()
offTimer = nil
end
setLight(true)
end,
far = function(player)
inside -= 1
if inside > 0 then
return
end
offTimer = self:after(4, function()
offTimer = nil
setLight(false)
end)
end,
})Everything in it is on this page: variables, a function, if, the short form, a table of options, and functions passed as arguments.
Next
- How scripts run — slices, saving, budgets
- Recipes — the same ideas, applied to real things
