Roblox Luau Tutorial: From Zero to Your First Game
Last updated:
Quick answer: A Roblox Luau tutorial takes you from your first print('Hello world') in Roblox Studio to a working mini-game by teaching the core language step by step: variables, strings, conditionals, loops, functions, and where each script type actually runs.
A Roblox Luau tutorial teaches you to write Luau, the Lua-based language Roblox runs, inside Roblox Studio. You start with print('Hello world'), then build up to variables, loops, and functions that move parts and keep score. This guide takes you from zero to a working mini-game.
Where do I write Luau code in Roblox Studio?
Roblox Studio is the free editor where every Roblox game is built, and it is where all of this tutorial happens. Get the setup right first, because half of beginner confusion is code running in the wrong place rather than wrong code.
- Install Roblox Studio from the Roblox website and sign in. Create a new place with the Baseplate template — one flat floor, nothing in the way.
- Find the Explorer window (View → Explorer). It lists every object in your game: Workspace holds the visible parts, ServerScriptService holds server code, ReplicatedStorage holds shared assets.
- Find the Properties window (View → Properties). Selecting an object shows its Position, Color, Anchored, and every other setting you can change from code.
- Open the Output window (View → Output). Every
printmessage and every error appears here — this is the first place to look when something does nothing. - Create a script: right-click ServerScriptService in the Explorer → Insert Object → Script. A fresh script opens in the editor with a default
print("Hello world!")line already inside.
The rule of thumb: Script objects go in ServerScriptService (or inside the part they control), and the editor tab that opens is a normal text editor with syntax highlighting and autocomplete for Luau.
What is the first script to write in Roblox?
The traditional first program prints a message to the Output window. Replace whatever the new script contains with this and press Play (F5):
print("Hello world!")
The game starts, the Output window shows Hello world!, and you have just run Luau code on the Roblox server. print writes any value to Output — numbers, strings, tables — which makes it your main debugging tool from day one. Its sibling warn prints in yellow and is the convention for "something unexpected happened but keep running":
print("Game started")
warn("This shows up in yellow")
print(2 + 2) -- 4
Stop the playtest with the Stop button before editing again: changes made while playing do not stick.
How do local variables work in Luau?
Variables store values you want to reuse or change. In Luau you almost always declare them with local, which limits the variable to the block it is written in:
local coins = 0
local playerName = "Stefi"
local isRunning = true
coins = coins + 5
print(coins) -- 5
coins += 3 -- shorthand for coins = coins + 3
print(coins) -- 8
Every value has a type, but Luau infers it for you: numbers, strings, booleans, nil (the "no value" value), tables, functions, and Roblox Instances like parts and scripts. A variable declared without a value is nil, and reading a typo'd name gives nil instead of an error — a classic source of the attempt to index nil error. Skipping local creates a global variable: slower to look up, visible everywhere, and a bug factory, so use it only on purpose.
How do strings and string interpolation work in Luau?
Strings are text in quotes. You can join them with .., measure them with #, and — the modern way — embed values directly with backtick interpolation:
local name = "Stefi"
local coins = 10
print("Hello, " .. name) -- classic concatenation
print("Name length: " .. #name) -- 5
print(`Hello, {name}!`) -- interpolation
print(`You have {coins * 2} coins.`) -- expressions work inside the braces
Backtick strings treat {...} as code to evaluate, which makes building messages far less error-prone than a chain of .. and tostring. Remember that .. joins strings only: "Coins: " .. coins errors when coins is a number, so wrap the number with tostring(coins) — or just use an interpolated backtick string, which calls tostring for you.
How does if/else work in Luau?
Conditionals decide which code runs. Luau uses then, elseif, and end — and the comparison operator is ==, never a single =:
local coins = 10
if coins >= 10 then
print("you can buy the sword")
elseif coins > 0 then
print("almost there")
else
print("no coins yet")
end
if coins ~= 10 then -- ~= means "not equal"
print("changed")
end
Combine conditions with and, or, and not. One Luau quirk to memorize: only nil and false are falsey — the number 0 and the empty string are both truthy, unlike languages such as C or JavaScript.
How do loops work in Luau?
Loops repeat work: counting with for, repeating with while. task.wait(n) pauses the script for n seconds, which is how you keep a loop from freezing the whole game:
for i = 1, 5 do
print("countdown " .. i)
task.wait(1)
end
local coins = 0
while coins < 3 do
coins += 1
print(coins)
task.wait(0.5)
end
for _, part in workspace:GetChildren() do
print(part.Name)
end
Always use task.wait, task.spawn, and task.defer. The older globals wait(), spawn(), and delay() are legacy: less precise, scheduler-hostile, and deprecated — there is a full task.wait vs wait() comparison if you are migrating old code. The last loop uses the generic for form to walk a list of objects; _ is the conventional name for a loop variable you ignore.
How do functions work in Luau?
Functions package reusable behaviour. Declare them with local function, pass values in as parameters, and send a result back with return:
local function doubleCoins(amount)
return amount * 2
end
local function greet(playerName)
print(`Welcome, {playerName}!`)
end
print(doubleCoins(5)) -- 10
greet("Stefi")
local function makeCoinValue(startAmount)
local value = Instance.new("IntValue")
value.Value = startAmount
return value
end
Functions are values, so you can store them in variables and pass them to Roblox events — exactly how event callbacks like part.Touched:Connect(function(hit) ... end) work. A function without a return gives back nil.
What is the difference between Script, LocalScript, and ModuleScript?
This is the concept that decides whether your first code works at all. Roblox runs the game on a server and shows it to each player through their own client, and each script type lives on one side of that line:
| Type | Runs on | Put it here | Use it for |
|---|---|---|---|
| Script | The server (one copy for everyone) | ServerScriptService, Workspace | Game rules, currency, damage, saving data |
| LocalScript | One player's own client | StarterPlayerScripts, StarterCharacterScripts, StarterGui | Camera, keyboard/mouse input, UI, visual effects |
| ModuleScript | Wherever it is required | ReplicatedStorage, ServerStorage | Shared code, config tables, reusable services |
A Script changing a coin value is the truth for every player. A LocalScript changing the same value only lies to that one player's screen — and exploiters can edit anything on their client, which is why game rules never live there. ModuleScripts are not scripts at all until someone loads them with require:
-- ModuleScript named Config in ReplicatedStorage
local Config = {}
Config.StartingCoins = 50
return Config
-- In any other script:
local Config = require(game:GetService("ReplicatedStorage"):WaitForChild("Config"))
print(Config.StartingCoins)
If your very first script should print something in the Output window while you play alone in Studio, a Script in ServerScriptService is the one to pick — the FAQ below covers why.
How do I build a first mini-game: a coin collector with leaderstats?
Enough syntax — let's make something playable. The project: coins scattered on the baseplate, a counter in the player list, and a score that goes up when you touch a coin. This needs exactly two scripts.
First, the scoreboard. Add a Script to ServerScriptService that creates a leaderstats folder for every joining player — Roblox automatically shows IntValues inside it in the player list:
-- Script in ServerScriptService
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = leaderstats
end)
Then make a coin: insert a Part into Workspace, name it "Coin", set Anchored to true, and give it a gold color. Insert a Script inside that part:
-- Script inside the coin Part in Workspace
local Players = game:GetService("Players")
local coin = script.Parent
local debounce = false
coin.Touched:Connect(function(hit)
if debounce then
return
end
local player = Players:GetPlayerFromCharacter(hit.Parent)
if not player then
return
end
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
if not coins then
return
end
debounce = true
coins.Value += 1
print(`{player.Name} picked up a coin ({coins.Value} total)`)
coin.Transparency = 1
coin.CanCollide = false
task.wait(2) -- respawn the coin
coin.Transparency = 0
coin.CanCollide = true
debounce = false
end)
Press Play: walking into the coin bumps the counter in the player list, prints to Output, hides the coin, and brings it back two seconds later. Duplicate the coin part a few times and you have a working collectible loop. The debounce flag is what stops one touch from counting ten times per step.
From here the sky is the limit: store the score permanently with DataStore saving, or spend coins in a shop — the leaderboard structure above is the same one used by leaderstats in every Roblox game.
Common mistakes beginners make in Roblox Luau
- Putting game logic in a LocalScript and expecting everyone to see it. A LocalScript runs only on one client. Coins, damage, and anything with authority belong in a Script on the server.
- Using
wait(),spawn(), ordelay(). These legacy globals are deprecated and imprecise; usetask.wait,task.spawn, andtask.deferinstead. - Using
=inside a condition.if coins = 10 thenassigns instead of comparing and throws an error — the comparison operator is==. - Forgetting
local. An undeclared variable becomes an invisible global that is slow to read and easy to clobber by accident. - Ignoring the Output window. Every red error line names the script and the line number; the second line usually contains the actual problem, such as an attempt to index a nil value.
- Editing during Play mode. Studio silently discards those changes when you stop — if a fix "does nothing", check whether you were still playtesting.
Learn Luau by doing
Reading a Roblox Luau tutorial is the map, but the language sticks when you type the code yourself. On Luablox you can study the concepts in depth on /theory, drill syntax and debugging on /problems, and follow structured Luablox lessons that carry this exact path from first print to full gameplay systems. When you want the deeper language background, the what is Luau guide covers how Luau relates to Lua and why Roblox uses it.
Frequently asked questions
Is Luau hard to learn?
No. Luau is one of the gentlest first languages: it has a small syntax, no semicolons or curly braces to memorize, and Roblox Studio shows errors immediately in the Output window. Most beginners write their first working script within an hour and a playable coin-collector loop within a day; the parts that take longer are Roblox concepts like client vs server, not the language itself.
Do I need to know Lua before learning Luau?
No. Luau is based on Lua 5.1 but is a separate, better-typed language, and learning Lua first is unnecessary — you would still have to learn Roblox APIs, Roblox Studio, and Luau additions like string interpolation and += either way. If you already know Lua, you are ahead on syntax but should skip legacy Lua patterns like module() and learn task.* instead of wait()/spawn().
Should my first script be a Script or a LocalScript?
A Script in ServerScriptService. It runs once on the server, its print output shows in the Output window immediately, and it is the correct home for game logic like scores and damage. Reach for a LocalScript only when you need one player's input, camera, or UI — and remember a LocalScript's changes are visible only to that player.
How long does a Roblox Luau tutorial like this take to finish?
The walkthrough above takes one to two hours if you type every example and build the mini-project in Studio. Reaching comfort with the language — writing scripts without copying — usually takes a few weeks of small projects, and this guide's sections map directly onto the order you should practice them.
What is the difference between Luau and Lua in Roblox?
Luau is Roblox's fork of Lua 5.1: the core syntax is the same, but Luau adds type annotations, string interpolation with backticks, compound assignment like +=, and performance optimizations, while dropping or deprecating pieces like getfenv and the legacy wait()/spawn() scheduler functions in favor of task.*.
Understand loop structures in Luau, including numeric, while, generic pairs, and ipairs iterators.