luaguides

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:

  1. Go to roblox.com/create and click “Start Creating”.
  2. Download and install Roblox Studio.
  3. 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:

TypeExampleNotes
string"hello"Text in quotes
number42, 3.14All numbers are doubles
booleantrue, falseLogical values
nilnilMeans “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")

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:

  • Touched fires for every body that touches the Part, including other Parts, NPCs, and the player’s character. Check hit.Name or hit.Parent if you need to filter.
  • Server scripts and LocalScripts live in different environments. A Touched event 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.

  1. In Roblox Studio, insert a Part into Workspace.
  2. Add a Script as a child of that Part.
  3. 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 Touched event
  • 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

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.