Articles › Game development

Roblox EP2 Your first Luau: colour pads, lava floors and floors that vanish

Write your first Luau in Roblox Studio. Script, Touched, Humanoid and debounce through three examples every real obby uses, every line explained.

Beginner 6 min read October 16, 2026 Your first Roblox game · lesson 2/5

What you will learn
  • Roblox code runs on events. Touched fires when something touches a part, and the function you connected runs
  • Always check that the toucher is a real character, by finding a Humanoid, before doing anything
  • Touched fires dozens of times for one step, so you need a debounce to stop code repeating

In EP1 we built a scene you can walk and jump through, but nothing moves. This episode writes your first code, through three examples every obby needs, with every line explained. No programming experience needed.

Adding your first Script

  1. Make a new Part, tick Anchored, name it ColorPad
  2. In Explorer, hover over ColorPad; a + button appears. Click it and choose Script
  3. Studio opens the code editor with one line already there:
print("Hello world!")
  1. Press Play and look at the Output panel: Hello world! appears

print writes a message to Output. You will use it constantly to see how far your code got.

A Script inside a Part runs by itself when the game starts, and knows what it sits in through script.Parent.

Example 1: change colour when stepped on

Delete that line and type this instead:

local pad = script.Parent

pad.Touched:Connect(function(otherPart)
	local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		pad.BrickColor = BrickColor.random()
	end
end)

Line by line:

  • local pad = script.Parent makes a variable called pad holding the Part this script is inside. local means the variable exists only in this file
  • pad.Touched is an event that fires every time something touches pad
  • :Connect(function(otherPart) ... end) connects a function to the event. Every touch runs it, passing otherPart, the part that touched
  • otherPart.Parent:FindFirstChildWhichIsA("Humanoid"): the toucher is a foot or leg; the whole character is its Parent, and every character contains a Humanoid. Finding one means a real character touched, not a ball or rock rolling in
  • if humanoid then ... end acts only when one was found
  • pad.BrickColor = BrickColor.random() picks a random colour

Press Play and step on it; the colour changes.

Most Roblox code has this shape: wait for an event → check it is what you care about → act.

First problem: Touched fires a lot

Add this as the first line inside the function:

	print("touched by", otherPart.Name)

Press Play, walk on and stand still. Output floods with dozens of lines, because the left foot, right foot and each leg segment touch separately, and touch again with every tiny movement. The colour flickers too fast to see.

The fix is called a debounce: a variable that says “busy, don’t repeat”.

local pad = script.Parent
local cooling = false

pad.Touched:Connect(function(otherPart)
	if cooling then
		return
	end
	local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
	if not humanoid then
		return
	end

	cooling = true
	pad.Color = Color3.fromRGB(255, 90, 90)  -- red
	task.wait(1)
	pad.Color = Color3.fromRGB(90, 200, 120) -- green
	cooling = false
end)
  • cooling starts false and becomes true while working. Any touch in between meets if cooling then return end and leaves at once
  • return leaves the function right there; written this way instead of nesting several ifs, it reads more easily
  • task.wait(1) waits one second
  • Color3.fromRGB(r, g, b) sets a colour by red, green, blue, each 0 to 255
  • Text after -- is a comment: the program ignores it; it is there for people
Touched firessomething touched the partBusy? (debounce)yes → leaveIs there a Humanoid?no → leave (ball, rock)Actcolour / lava / fadetask.wait, then unlockready for the next touch
One shape for every example: wait for the event, check, then act

Example 2: a lava floor

Every obby has floors that send you back. Make a new Part named Lava, tick Anchored, set Material to Neon and Color to orange-red in Properties, and add this Script:

local lava = script.Parent

lava.Touched:Connect(function(otherPart)
	local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		humanoid.Health = 0
	end
end)

humanoid.Health = 0 sets health to zero; the character falls and respawns at a spawn point. This one needs no debounce, because setting health to zero again and again has the same result.

To decide whether you need a debounce, ask: “if this ran ten times in a row, would the result differ from running once?” If yes, you need one.

Example 3: a floor that vanishes if you linger

A floor that fades away when stepped on, then comes back, makes players hurry. Make a Part named FadePad and add this Script:

local pad = script.Parent
local busy = false

pad.Touched:Connect(function(otherPart)
	if busy then
		return
	end
	if not otherPart.Parent:FindFirstChildWhichIsA("Humanoid") then
		return
	end

	busy = true
	for i = 1, 10 do
		pad.Transparency = i / 10   -- fade 10% at a time
		task.wait(0.1)
	end
	pad.CanCollide = false          -- nothing to stand on now
	task.wait(3)
	pad.CanCollide = true
	pad.Transparency = 0
	busy = false
end)
  • for i = 1, 10 do ... end repeats 10 times, with i taking 1, 2, 3 up to 10
  • Transparency: 0 is solid, 1 is invisible
  • CanCollide = false makes the part non-solid; the character falls through
  • It fades for 1 second, stays gone for 3, then returns

Tune the numbers to taste; task.wait(0.05) fades twice as fast.

Reading errors

Change the line that finds the Humanoid in the lava script to this:

local humanoid = otherPart.Parent.Humanoid  -- no FindFirstChild

When something that is not a character touches it, such as an unanchored ball rolling into it, Output shows red text along the lines of Humanoid is not a valid member of ..., because we demanded a Humanoid from something that has none.

If you see attempt to index nil with ..., you are using something that is nil (empty, not there), like trying to open a drawer in a cabinet that does not exist.

Both are fixed the same way: find it first, check it was found, then use it, which is exactly the FindFirstChildWhichIsA plus if humanoid then pattern we have used all along.

Try it in 10 minutes

  1. Build ColorPad with a debounce, and make it return to its original colour after 2 seconds
  2. Put two or three Lava slabs between the steps from EP1
  3. Make FadePad a bridge over the lava, then press Play and try it

Common mistakes

  • Not checking for a Humanoid, so a rolling ball or loose part triggers the code
  • Forgetting a debounce where repeating changes the result
  • Forgetting busy = false at the end, so it works only once per game
  • Putting the Script in the wrong place, so script.Parent is not the part you meant

Next episode

We now have scripts scattered across parts, all running on the server. EP3 explains that a Roblox game has two sides, the server and each player’s device, why you must know which side your code runs on, and why that is the heart of stopping cheaters.

FAQ

Why task.wait and not wait?

The old wait still works, but Roblox now recommends task.wait, which is more precise and part of the newer task library. Many older articles and videos still use wait; swap it for task.wait when you see it.

What does red text in Output mean?

Your code hit an error. The message names the script and line number; click it and Studio jumps to that line. The beginner's most common error, attempt to index nil, is explained in this article.

Commands and settings were tested on sample systems. Try them on a test machine before production.