Getting Started with Roblox Luau
What is Luau?
Luau is the scripting language built into Roblox Studio. It is a descendant of Lua 5.1, designed specifically for Roblox game development. If you have used Lua before, Luau will feel familiar, but it has its own quirks worth knowing.
Unlike plain Lua, Luau adds optional static typing, better performance, and tighter integration with Roblox’s object model. Every interactive element in a Roblox game — parts, UI, tools, animations — gets its behavior from Luau scripts.
This tutorial assumes you have never written a line of code. By the end, you will have a working knowledge of Luau fundamentals and your first interactive Roblox game object.
Setting Up Roblox Studio
If you do not already have Roblox Studio:
- Go to roblox.com/create and click “Start Creating”.
- Download and install Roblox Studio.
- Open Studio and sign in with your Roblox account.
Create a new place by clicking “New Place”. Choose the “Baseplate” template — it gives you an empty world to work in.
To add a script to an object:
- In the Explorer panel, click on the object you want to script (e.g.,
Workspace). - Click the small
+button next to the object. - Find “Script” under “Basic Objects” and click it.
You will see a code editor open on the right side of Studio. That is where you write Luau.
Your First Script
Inside the new Script, you already see a line of code:
print("Hello world!")
This prints a message to the Output panel in Studio. Press Play (the green triangle at the top) to run your game. Look at the Output panel at the bottom of Studio — you will see Hello world! printed there.
You just ran your first Luau code.
Variables and Types in Luau
Variables store values so you can use them later. In Luau, you declare a variable with the local keyword:
local playerName = "LuauRookie"
local health = 100
local isAlive = true
Luau has a handful of basic types:
| Type | Example | Notes |
|---|---|---|
string | "hello" | Text in quotes |
number | 42, 3.14 | All numbers are doubles |
boolean | true, false | Logical values |
nil | nil | Means “no value” |
Luau is dynamically typed — you do not need to declare the type of a variable. However, Luau does support optional type annotations:
local name: string = "Roblox"
local score: number = 0
You can also reassign variables:
local count = 10
count = count + 1 -- count is now 11
Tables: Luau’s Only Data Structure
Everything that is not a basic type in Luau is a table. Tables are the single data structure for both key-value maps and ordered arrays. You will use tables constantly.
A table with string keys looks like this:
local player = {
name = "Player1",
health = 100,
level = 5
}
print(player.name) -- dot notation: prints "Player1"
print(player["health"]) -- bracket notation: prints 100
The same table structure, but with integer keys, works as an ordered array:
local inventory = {"sword", "shield", "potion"}
print(inventory[1]) -- Luau arrays are 1-indexed: prints "sword"
Both are the same table type under the hood — the difference is only in how you use the keys.
Control Flow: if/else and Loops
Conditionals
local health = 30
if health < 50 then
print("Low health!")
elseif health < 100 then
print("Getting hurt")
else
print("Fully healthy")
end
elseif and else are optional. The end keyword closes every if block.
Numeric For Loop
for i = 1, 10 do
print("Count: " .. i) -- ".." concatenates strings
end
Iterating Tables
local scores = {
Player1 = 100,
Player2 = 85,
Player3 = 92
}
for name, score in pairs(scores) do
print(name .. " scored " .. score)
end
pairs() iterates over key-value pairs in arbitrary order. ipairs() iterates over array-style tables in sequential order. Note that pairs() does not guarantee any particular order.
While Loop
local count = 1
while count <= 5 do
print(count)
count = count + 1
end
Functions
Functions group code into reusable units:
local function greet(name)
return "Hello, " .. name
end
local message = greet("Roblox")
print(message) -- prints "Hello, Roblox"
Functions can return multiple values:
local function getStats()
return 100, 50, "Player"
end
local health, mana, role = getStats()
print(health) -- 100
print(mana) -- 50
Understanding Scripts: Script vs LocalScript vs ModuleScript
Roblox has three script types:
Script (server-side) Runs on the Roblox server. Can manipulate the world, handle game logic, and access all services. Does not have access to the player’s screen or local inputs.
LocalScript (client-side)
Runs on each player’s device. Can access player-specific things like Players.LocalPlayer, the camera, and user input. Use for UI, local animations, and input handling.
ModuleScript
Does not run on its own. Stores reusable code that other scripts import with require(). Use to share logic between server and client scripts.
-- In a ModuleScript called "PlayerUtils"
local module = {}
function module.getPlayerByName(name)
return game.Players:FindFirstChild(name)
end
return module
-- In a Script, import the module
local PlayerUtils = require(path/to/PlayerUtils)
local player = PlayerUtils.getPlayerByName("Player1")
Navigating game.Workspace and game.Players
Roblox organizes everything in a hierarchy starting from game. Two of the most important services are:
game.Players — contains all currently connected players. Each player has a Player object and a Character model.
local Players = game:GetService("Players")
local playerCount = #Players:GetPlayers()
print("There are " .. playerCount .. " players online")
game.Workspace — the 3D world. Contains all Parts, Terrain, Models, and Cameras.
local part = Instance.new("Part")
part.Name = "MyPart"
part.Size = Vector3.new(4, 1, 2) -- x=4, y=1, z=2 studs
part.Position = Vector3.new(0, 3, 0) -- 3 studs above ground
part.BrickColor = BrickColor.new("Bright red")
part.Anchored = true -- stays in place (not affected by gravity)
part.Parent = workspace -- add it to the world
Connecting to Events with :Connect()
Everything interactive in Roblox works through events. You “listen” to an event by connecting a function to it:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
print(player.Name .. " joined the game!")
end)
The :Connect() method registers a callback. Every time the event fires, your function runs.
Here is a more practical example — a Part that changes color when touched:
local part = workspace.Part
local colors = {
Color3.fromRGB(255, 0, 0), -- red
Color3.fromRGB(0, 255, 0), -- green
Color3.fromRGB(0, 0, 255) -- blue
}
local currentIndex = 1
part.Touched:Connect(function(hit)
-- Change to the next color
currentIndex = (currentIndex % #colors) + 1
part.Color = colors[currentIndex]
end)
Some important gotchas with events:
Touchedfires for every body that touches the Part, including other Parts, NPCs, and the player’s character. Checkhit.Nameorhit.Parentif you need to filter.- Server scripts and LocalScripts live in different environments. A
Touchedevent on a Part in a server Script fires for all players; in a LocalScript it only fires on the local player’s device.
Your First Hands-On Project: A Color-Changing Billboard
Here is a mini-project to apply what you have learned. This is a server Script (not a LocalScript), which means it runs on the server and the color change will be visible to all players.
- In Roblox Studio, insert a Part into Workspace.
- Add a Script as a child of that Part.
- Paste this code:
local part = script.Parent
-- Create a BillboardGui on the part
local billboard = Instance.new("BillboardGui")
billboard.Size = UDim2.new(4, 0, 1.5, 0)
billboard.StudsOffset = Vector3.new(0, 3, 0)
billboard.AlwaysOnTop = true
billboard.Parent = part
local label = Instance.new("TextLabel")
label.Size = UDim2.new(1, 0, 1, 0)
label.BackgroundTransparency = 0.5
label.BackgroundColor3 = Color3.new(0, 0, 0)
label.TextColor3 = Color3.new(1, 1, 1)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.Text = "Touch me!"
label.Parent = billboard
-- Track touch count and cycle colors
local touchCount = 0
local colors = {
Color3.fromRGB(200, 50, 50),
Color3.fromRGB(50, 200, 50),
Color3.fromRGB(50, 50, 200),
Color3.fromRGB(200, 200, 50)
}
part.Touched:Connect(function(hit)
touchCount = touchCount + 1
local colorIndex = (touchCount % #colors) + 1
part.Color = colors[colorIndex]
label.Text = "Touched " .. touchCount .. " times!"
end)
Press Play and touch the Part. Each touch cycles the color and increments the counter on the billboard.
What this script demonstrates:
- Creating UI elements (BillboardGui, TextLabel) programmatically
- Handling the
Touchedevent - Modifying Part properties (Color)
- Using arithmetic with the modulo operator (
%) to cycle through a list - The distinction between server scripts (this one) and LocalScripts
See Also
- Variables and Types in Lua — goes deeper into Lua’s type system, including nil and boolean nuances
- Functions, Closures, and Varargs — covers function topics not included here, including variadic functions and closures
- Modules and the require System — learn how ModuleScripts work in practice, including require paths and circular dependency handling
Next Steps
You now know the fundamentals of Luau. Here is where to go from here:
- DataStores — Save and load player data between sessions (money, inventory, progress).
- RemoteEvents — Send messages between server scripts and LocalScripts to synchronize state.
- UserInputService — Handle keyboard, mouse, and gamepad input in LocalScripts.
- TweenService — Smoothly animate parts, UI, and camera movements.
The official Luau documentation on the Roblox Creator Hub is an excellent reference as you build your skills.