Beginner
Your First Luau Script
Write your very first Luau script from scratch. Learn variables, functions, and how to make things happen in your game. No coding experience required.
your-first-luau-script.lua
-- PLACE IN: Script (ServerScript) inside a Part in Workspace
-- REQUIRES: A Part named "SpinningPart" in Workspace
------------------------------------------------------------------------
-- π LUAU BEGINNER GUIDE: 5 Core Concepts of Roblox Scripting
-- This script teaches: Variables, Functions, If Statements, Loops, Events
-- Follow along with the comments - every single line is explained!
------------------------------------------------------------------------
------------------------------------------------------------------------
-- βοΈ CONFIGURATION (Change these values to customise your spinning part!)
------------------------------------------------------------------------
local SPIN_SPEED = 2 -- How many degrees the part rotates each step
local COLOR_CHANGE_TIME = 3 -- How many seconds between colour changes
local SPIN_AXIS = "Y" -- Which axis to spin on: "X", "Y", or "Z"
local PART_SIZE = Vector3.new(6, 1, 6) -- Width, Height, Depth of the part
local START_COLOR = Color3.fromRGB(255, 100, 100) -- Starting colour (red-ish)
local TOUCH_MESSAGE = "You touched the spinning part! π" -- Message on touch
------------------------------------------------------------------------
-- π¦ SERVICES (These give us access to Roblox's built-in systems)
------------------------------------------------------------------------
local Players = game:GetService("Players") -- Lets us detect players
local RunService = game:GetService("RunService") -- Gives us a heartbeat loop
------------------------------------------------------------------------
-- π§± CONCEPT 1: VARIABLES
-- A variable stores a piece of information so we can use it later.
-- Think of it like a labelled box that holds a value.
-- Syntax: local variableName = value
------------------------------------------------------------------------
local part = workspace:FindFirstChild("SpinningPart") -- Find our part in Workspace
-- If the part doesn't exist yet, we create it from scratch
if part == nil then -- "nil" means "nothing" in Luau - the part wasn't found
part = Instance.new("Part") -- Create a brand new Part object
part.Name = "SpinningPart" -- Give it a name so we can find it later
part.Size = PART_SIZE -- Set the size using our config variable above
part.Position = Vector3.new(0, 5, 0) -- Place it 5 studs above the ground
part.Anchored = true -- Stop physics from making it fall down
part.Color = START_COLOR -- Apply the starting colour from config
part.Material = Enum.Material.SmoothPlastic -- Give it a nice smooth look
part.Parent = workspace -- Add it to the game world (always set Parent last!)
end
-- These are more variables - they track the state of our spinning part
local isSpinning = true -- A "boolean" variable - it's either true or false
local currentAngle = 0 -- A "number" variable - tracks the current rotation angle
local touchDebounce = false -- Prevents the touch event firing too many times at once
-- This table (like a list) holds all the colours our part will cycle through
local colorList = {
Color3.fromRGB(255, 100, 100), -- Red
Color3.fromRGB(100, 255, 100), -- Green
Color3.fromRGB(100, 100, 255), -- Blue
Color3.fromRGB(255, 255, 100), -- Yellow
Color3.fromRGB(255, 150, 50), -- Orange
Color3.fromRGB(200, 100, 255), -- Purple
}
local colorIndex = 1 -- Tracks which colour in the list we are currently using
------------------------------------------------------------------------
-- π§ CONCEPT 2: FUNCTIONS
-- A function is a reusable block of code that does a specific job.
-- You define it once, then "call" (run) it whenever you need it.
-- Syntax: local function functionName(parameters) ... end
------------------------------------------------------------------------
-- This function rotates the part one step on the chosen axis
local function spinPart()
-- We only spin if isSpinning is true (we can pause it!)
if not isSpinning then
return -- "return" exits the function early - nothing below this runs
end
currentAngle = currentAngle + SPIN_SPEED -- Increase the angle by the spin speed
-- CFrame is Roblox's way of setting position AND rotation together
-- We keep the same position but change the rotation based on the chosen axis
if SPIN_AXIS == "Y" then
-- Rotate around the Y axis (like a spinning top)
part.CFrame = CFrame.new(part.Position) * CFrame.Angles(0, math.rad(currentAngle), 0)
elseif SPIN_AXIS == "X" then
-- Rotate around the X axis (like a cartwheel)
part.CFrame = CFrame.new(part.Position) * CFrame.Angles(math.rad(currentAngle), 0, 0)
elseif SPIN_AXIS == "Z" then
-- Rotate around the Z axis (like a clock hand)
part.CFrame = CFrame.new(part.Position) * CFrame.Angles(0, 0, math.rad(currentAngle))
end
-- math.rad() converts degrees to radians - Roblox angles need radians internally
end
-- This function moves to the next colour in our colorList table
local function cycleColor()
colorIndex = colorIndex + 1 -- Move forward by one in the colour list
-- If we've gone past the end of the list, jump back to the start
if colorIndex > #colorList then -- "#colorList" gives us the total number of items
colorIndex = 1 -- Reset back to the first colour
end
part.Color = colorList[colorIndex] -- Apply the new colour to the part
-- Tables in Luau are accessed with square brackets: table[index]
-- Luau starts counting from 1, not 0 like some other languages!
end
-- This function runs when a player touches the part
-- "hit" is a parameter - it automatically receives what touched the part
local function onPartTouched(hit)
-- Debounce check: if it already fired recently, ignore this touch
if touchDebounce then
return -- Exit early - prevents spam
end
-- Try to find a Humanoid inside what touched the part
-- Only players (and NPCs) have Humanoids, not random falling parts
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
-- If there's no Humanoid, it wasn't a player - stop here
if humanoid == nil then
return -- Nothing to do - exit the function
end
-- If we reach here, a player touched the part!
touchDebounce = true -- Lock the debounce to stop repeated firing
-- Find the player who owns this character model
local player = Players:GetPlayerFromCharacter(hit.Parent)
if player then -- Make sure we actually found a player before continuing
print(player.Name .. " " .. TOUCH_MESSAGE) -- Print their name + message to Output
-- The ".." operator joins strings together (called "concatenation")
-- Briefly stop spinning as a visual reaction to being touched
isSpinning = false -- Pause the spin
part.Color = Color3.fromRGB(255, 255, 255) -- Flash white as feedback
task.wait(0.5) -- Wait half a second (task.wait is the modern, correct way)
part.Color = colorList[colorIndex] -- Restore the correct colour
isSpinning = true -- Resume spinning
end
-- Wait before allowing another touch to be detected (debounce cooldown)
task.wait(1) -- 1 second cooldown between touches
touchDebounce = false -- Unlock the debounce - ready for the next touch
end
-- This function toggles spinning on or off (a "toggle" switches between two states)
local function toggleSpin()
isSpinning = not isSpinning -- "not" flips a boolean: true becomes false, false becomes true
if isSpinning then
print("β
Spinning RESUMED") -- Tell us in the Output window
else
print("βΈοΈ Spinning PAUSED")
end
end
------------------------------------------------------------------------
-- π CONCEPT 3: IF STATEMENTS
-- If statements let your code make decisions based on conditions.
-- "If THIS is true, do THAT. Otherwise, do something else."
-- Syntax: if condition then ... elseif condition then ... else ... end
------------------------------------------------------------------------
-- Let's check the spin speed and give feedback before the loop starts
if SPIN_SPEED <= 0 then
-- If spin speed is zero or negative, warn us and fix it
warn("β οΈ SPIN_SPEED must be greater than 0! Setting it to 1.")
SPIN_SPEED = 1 -- Override the bad value with a safe default
elseif SPIN_SPEED < 1 then
-- "elseif" checks another condition if the first one was false
print("π‘ Tip: SPIN_SPEED is very low. The spin will be very slow.")
elseif SPIN_SPEED > 10 then
-- Another elseif - checks if it's too fast
print("π‘ Tip: SPIN_SPEED is very high. The spin will be very fast.")
else
-- "else" runs when NONE of the above conditions were true
print("β
Spin speed looks good! Starting with speed: " .. SPIN_SPEED)
end
-- Check which axis was chosen and confirm it's valid
if SPIN_AXIS == "X" or SPIN_AXIS == "Y" or SPIN_AXIS == "Z" then
-- "or" means: true if AT LEAST ONE condition is true
print("π Spinning on the " .. SPIN_AXIS .. " axis.")
else
-- The axis entered in config wasn't valid
warn("β οΈ SPIN_AXIS must be X, Y, or Z. Defaulting to Y.")
SPIN_AXIS = "Y" -- Fall back to Y axis as a safe default
end
------------------------------------------------------------------------
-- π CONCEPT 4: LOOPS
-- Loops repeat a block of code multiple times.
-- Use them when you need something to happen over and over.
--
-- TYPE A: "while" loop - repeats WHILE a condition is true
-- TYPE B: "for" loop - repeats a set NUMBER of times
-- TYPE C: RunService loop - repeats every single frame (best for movement)
------------------------------------------------------------------------
-- βΆοΈ TYPE A: while loop example (runs once at start to show the concept)
-- This counts down from 3 before the spinning starts
print("\nπ¦ Get ready! Spinning starts in...")
local countdown = 3 -- Start counting from 3
while countdown > 0 do -- Keep looping WHILE countdown is greater than 0
print(" " .. countdown .. "...") -- Print the current number
task.wait(1) -- Wait 1 second before the next count
countdown = countdown - 1 -- Decrease the counter by 1 each loop
end -- When countdown hits 0, the condition (countdown > 0) is false - loop stops
print(" GO! π")
-- βΆοΈ TYPE B: for loop example (cycles through our colour list once to preview them)
print("\nπ¨ Here are all the colours this part will cycle through:")
for i = 1, #colorList do
-- "i" is the loop counter. It starts at 1 and goes up to #colorList (the list size)
-- Each time through, i increases by 1 automatically
print(" Colour " .. i .. ": " .. tostring(colorList[i]))
-- tostring() converts a Color3 value into readable text for printing
end
print("") -- Print an empty line for spacing in the Output
-- βΆοΈ TYPE C: RunService Heartbeat loop (runs every frame - perfect for smooth spinning!)
-- This is the MAIN loop that keeps the part spinning every frame of the game
RunService.Heartbeat:Connect(function(deltaTime)
-- "deltaTime" is the time in seconds since the last frame
-- This fires ~60 times per second (once per frame)
spinPart() -- Call our spinPart function every single frame
end)
-- A separate loop using task.delay to cycle colours on a timer
-- This is a recursive function - it calls itself to repeat forever
local function startColorCycle()
task.wait(COLOR_CHANGE_TIME) -- Wait the configured number of seconds
cycleColor() -- Change to the next colour
task.spawn(startColorCycle) -- Spawn it again so it repeats (task.spawn is safe & modern)
end
task.spawn(startColorCycle) -- Kick off the colour cycling loop for the first time
------------------------------------------------------------------------
-- π‘ CONCEPT 5: EVENTS
-- Events let your code REACT to things that happen in the game.
-- Instead of constantly checking "did something happen?", events
-- automatically NOTIFY your code when something occurs.
-- Syntax: something.EventName:Connect(function() ... end)
------------------------------------------------------------------------
-- EVENT 1: React when something TOUCHES our spinning part
-- This fires automatically every time any object touches the part
part.Touched:Connect(onPartTouched)
-- We "Connect" our onPartTouched function to the Touched event
-- Now Roblox will call it FOR US whenever a touch happens
-- EVENT 2: React when a player JOINS the game
Players.PlayerAdded:Connect(function(player)
-- This fires whenever a new player joins the server
-- "player" is automatically passed in - it's the player who joined
print("π Welcome, " .. player.Name .. "! Touch the spinning part!")
-- Demonstrate an if statement inside an event
if #Players:GetPlayers() == 1 then
-- GetPlayers() returns a list of all current players
-- If the count is 1, this is the first (and only) player
print(" You're the first player on the server!")
else
-- More than one player is here
print(" There are now " .. #Players:GetPlayers() .. " players on the server.")
end
end)
-- EVENT 3: React when a player LEAVES the game
Players.PlayerRemoving:Connect(function(player)
-- This fires automatically when a player disconnects
print("π Goodbye, " .. player.Name .. "! Come back soon!")
end)
------------------------------------------------------------------------
-- π STARTUP MESSAGE (Runs once when the script first loads)
------------------------------------------------------------------------
print("========================================")
print("β
Beginner Luau Script is RUNNING!")
print(" Spin Speed: " .. SPIN_SPEED)
print(" Spin Axis: " .. SPIN_AXIS)
print(" Color Timer: " .. COLOR_CHANGE_TIME .. " seconds")
print(" Touch the part to see the event fire!")
print("========================================")
------------------------------------------------------------------------
-- π‘ BONUS TIPS FOR BEGINNERS
--
-- TIP 1: PRINT IS YOUR BEST FRIEND
-- Use print("any message") to debug. It shows in the Output window.
-- Use warn("message") for warnings (shows in yellow).
-- Use error("message") for critical errors (shows in red).
--
-- TIP 2: NIL MEANS NOTHING
-- If a variable has no value, it's nil.
-- Always check "if thing ~= nil then" before using something
-- that might not exist. (~= means "not equal to")
--
-- TIP 3: LOCAL VS GLOBAL
-- Always use "local" before your variables. It's faster and safer.
-- A variable without "local" is global and can conflict with other scripts.
--
-- TIP 4: TASK LIBRARY IS MODERN
-- Use task.wait() instead of wait()
-- Use task.spawn() instead of spawn()
-- Use task.delay() instead of delay()
-- The task versions are more accurate and reliable.
--
-- TIP 5: SERVICES ARE GATEWAYS
-- game:GetService("Players") β everything about players
-- game:GetService("RunService") β frame-by-frame control
-- game:GetService("TweenService") β smooth animations
-- game:GetService("Workspace") β the 3D game world (also just "workspace")
--
-- TIP 6: VECTORS AND CFRAMES
-- Vector3.new(x, y, z) β a position or size in 3
Want this written for your specific game?
Describe exactly what you need and get working Luau code in seconds.
Try the AI Generator β