Lua syntax - specifically for Roblox

Lesson#9 of 22 in project Theory

Core syntax

-- single-line comment
--[[ multi-line
     comment ]]

local name = "Damir"      -- always use `local`, otherwise it's global
local health = 100
local isAlive = true
local nothing = nil       -- like None/null

print(name, health)       -- Roblox prints to the Output window

Types: nil, boolean, number (one type for ints and floats), string, table, function, plus Roblox types like Vector3, CFrame, Instance.

Operators that differ from what you're used to:

print("a" .. "b")     -- string concat is ..
print(10 // 3)        -- floor division → 3
print(2 ^ 3)          -- power → 8
print(5 ~= 4)         -- not equal is ~=
print(not true, true and false, true or false)
print(#"hello")       -- length operator → 5

Control flow

if health > 50 then
    print("healthy")
elseif health > 0 then
    print("hurt")
else
    print("dead")
end

for i = 1, 5 do            -- inclusive: 1,2,3,4,5
    print(i)
end

for i = 10, 1, -2 do end   -- with step

local n = 0
while n < 3 do
    n += 1                 -- compound assignment works in Luau (Roblox)
end

Note: only nil and false are falsy. 0 and "" are truthy.

Functions

local function add(a, b)
    return a + b
end

local greet = function(who)
    return "Hello, " .. who
end

-- multiple return values
local function minMax(a, b)
    return math.min(a, b), math.max(a, b)
end
local lo, hi = minMax(3, 9)

Tables (arrays, dicts, objects, all in one)

-- array (1-indexed!)
local items = {"sword", "shield", "potion"}
print(items[1])            -- "sword"
table.insert(items, "bow")
table.remove(items, 2)
print(#items)

-- dictionary
local player = {name = "Damir", level = 5}
player.level += 1
player["gold"] = 250

-- iterate
for i, item in ipairs(items) do    -- arrays, in order
    print(i, item)
end
for key, value in pairs(player) do -- dictionaries, unordered
    print(key, value)
end

Gotcha: arrays start at 1, not 0.

Tables as classes (OOP)

local Enemy = {}
Enemy.__index = Enemy

function Enemy.new(name, hp)
    local self = setmetatable({}, Enemy)
    self.name = name
    self.hp = hp
    return self
end

function Enemy:takeDamage(amount)   -- colon passes `self` automatically
    self.hp -= amount
    if self.hp <= 0 then
        print(self.name .. " died")
    end
end

local goblin = Enemy.new("Goblin", 30)
goblin:takeDamage(40)

Roblox specifics

Services: get built-in systems via game:GetService.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

Instances and the hierarchy:

local part = Instance.new("Part")
part.Size = Vector3.new(4, 1, 4)
part.Position = Vector3.new(0, 10, 0)
part.BrickColor = BrickColor.new("Bright red")
part.Anchored = true
part.Parent = workspace     -- setting Parent is what makes it appear

local child = workspace:FindFirstChild("SpawnPoint")
local waited = workspace:WaitForChild("Map")   -- yields until it exists

Events (the core of Roblox scripting):

part.Touched:Connect(function(otherPart)
    local humanoid = otherPart.Parent:FindFirstChild("Humanoid")
    if humanoid then
        humanoid.Health -= 25
    end
end)

Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " joined")
end)

Waiting and loops:

task.wait(1)                 -- use task.wait, not the old wait()
task.spawn(function() ... end)
task.delay(5, function() print("5s later") end)

RunService.Heartbeat:Connect(function(dt)
    -- runs every frame; dt = seconds since last frame
end)

Script types

TypeRuns onLives inUse for
ScriptServerServerScriptService / Workspacegame logic, data, security
LocalScriptClientStarterPlayerScripts, StarterGuiUI, input, camera
ModuleScriptWhoever requires itReplicatedStorage / ServerStorageshared code
-- ModuleScript
local Utils = {}
function Utils.double(x) return x * 2 end
return Utils

-- another script
local Utils = require(game.ReplicatedStorage.Utils)
print(Utils.double(4))

Client ↔ Server: RemoteEvents

Clients can't be trusted, so the server does the real work and the client only asks.

-- LocalScript
local remote = game.ReplicatedStorage:WaitForChild("BuyItem")
remote:FireServer("Sword")

-- Script
local remote = game.ReplicatedStorage.BuyItem
remote.OnServerEvent:Connect(function(player, itemName)
    -- validate itemName and check player's gold here!
end)

Luau extras (Roblox's Lua dialect)

--!strict
local function heal(hp: number, amount: number): number
    return math.min(hp + amount, 100)
end

type Item = {name: string, price: number}

Optional type annotations give you autocomplete and error checking in Studio.

Common mistakes to avoid

  • Forgetting local → accidental globals
  • Using 0 as the first index
  • Forgetting .Parent = ... so the part never shows up
  • Trusting values sent from the client
  • Using wait() instead of task.wait()