Tutorials→beginner→Your First Luau Script
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
Luanaut

Want this written for your specific game?

Describe exactly what you need and get working Luau code in seconds.

Try the AI Generator β†’