Skip to main content

Stage 1: Ascending Walls + first coin

Course progressStage 1 of 10
~50 min
Before you start

Finish the Setup page. Your file should have a Start Platform, a SpawnLocation centered on it, and ServerScriptService open in Explorer.

Build

a three-block climbing wall, the Stage 2 checkpoint, and the first leaderstats script

Learn

how Roblox's leaderstats system shows a number above every player's name, and how a server script finds a part by its StageNumber attribute

Ship

a playable obstacle where reaching the second checkpoint pays the player 1 coin — visible in the top-right Roblox UI

Teacher demo

Before campers start, run this demo (90 seconds):

  • Open a finished Stage 1 file in Studio. Press Play.
  • Climb the wall slowly. Watch the top-right of the Roblox UI — it shows Coins 0.
  • Touch the second pad. The number flips to Coins 1.
  • Reset character. The pad's now done — coins stay at 1.
  • Explain: "Every tycoon game has that little number in the corner. By the end of Stage 1 you'll have your own."

The big idea

Every obby in this course is the same shape you've seen before — a climbing wall, a checkpoint, a chain of obstacles. What's new is what happens when the player reaches a checkpoint.

In a regular obby, touching a checkpoint just changes where you respawn. In a tycoon, touching a checkpoint also pays you. That payout is what turns "I finished an obstacle" into "I earned something I can spend." Stage 1's job is to wire that first payout.

The Roblox feature that handles this is leaderstats — a folder you build per player, with a value inside (like Coins). Roblox automatically shows whatever is inside leaderstats in the top-right of the screen. You don't have to build a UI; you just have to fill the folder.

Today you build the climbing wall (familiar), the Stage 2 checkpoint (familiar), and one server script that does two things: gives every joining player a leaderstats.Coins value, and adds 1 to it the first time they reach the Stage 2 pad.

New words
leaderstats
a magic Folder name Roblox watches — anything you put inside it appears in the top-right player list
IntValue
a Roblox object that holds one whole number, like 0 or 7 or 1000
PlayerAdded event
fires once for every player who joins the game; perfect for setting up their leaderstats
GetAttribute
reads the value of an attribute you added in Properties (like StageNumber)
GetPlayerFromCharacter
looks up which player owns the character that touched something — needed because Touched gives you a body part, not a player
debounce
a flag that prevents the same event from firing too many times in a row (here: we only want to award the coin once)

Build it

Step 1 — Build the climbing wall

Same three-block staircase as the base obby. You've built this shape before; the recipes go fast.

A three-block climbing wall in Roblox Studio

In Workspace, insert three Parts. Tune each one.

Build this part

ClimbBlock_Short

Block
Open recipe
Size
4 × 5 × 2
Color
Medium stone grey
Material
Slate
Anchored
✓ Yes
Place
In front of the Start Platform, sitting on the ground
Build this part

ClimbBlock_Mid

Block
Open recipe
Size
4 × 7 × 2
Color
Medium stone grey
Material
Slate
Anchored
✓ Yes
Place
Right next to ClimbBlock_Short, taller
Build this part

ClimbBlock_Tall

Block
Open recipe
Size
4 × 10 × 2
Color
Medium stone grey
Material
Slate
Anchored
✓ Yes
Place
Right next to ClimbBlock_Mid, tallest

Press ▶ Play and confirm you can climb all three. If not, move the blocks closer together.

Step 2 — Wire the Stage 2 checkpoint

The Stage 2 SpawnLocation goes on top of the tallest block. Same pattern as the base obby — but the StageNumber attribute is what the tycoon script will look for, so don't skip it.

Build this part

SpawnLocation (Stage 2 — top of the climb)

Block
Open recipe
Size
6 × 1 × 6
Color
Bright red
Material
Plastic
Anchored
✓ Yes
Place
On top of ClimbBlock_Tall

Also: check AllowTeamChangeOnTouch. Uncheck Neutral. Set TeamColor to Bright red.

2.1 Tag it with its stage number

In the Attributes section of Properties, add a StageNumber attribute (number type) with value 2. This is what the tycoon script will use to find this exact pad — not its name, not its color. Attributes are the contract.

If you forgot how to add an attribute: Properties → scroll to Attributes → click + → name StageNumber, type number, value 2.

2.2 Create the matching Team

  • In Explorer, find the Teams folder. If it doesn't exist, right-click Explorer → Insert ServiceTeams.
  • Right-click TeamsInsert ObjectTeam.
  • Rename it Stage 2.
  • Set TeamColor to Bright red.
  • Uncheck AutoAssignable.

(Don't forget to also tag the original Stage 1 SpawnLocation — the one on the Start Platform — with StageNumber = 1. The tycoon script doesn't care about Stage 1 today, but Stage 2 of the course will, and Stage 5 of the course will iterate over all of them.)

Press Play. Climb the wall, touch the red pad, reset your character. You should respawn on red. No coins yet — that's what Step 3 adds.

Step 3 — Wire the first leaderstats script

In Explorer, right-click ServerScriptServiceInsert ObjectScript. Rename the new Script to TycoonEconomy. Double-click to open it. Delete the placeholder line.

You'll build this script in three passes. After each pass, press Play and confirm Output matches before moving on.

Pass 1 — Give every joining player a leaderstats folder

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
stats.Parent = player

local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = stats

print(player.Name, "joined with", coins.Value, "coins")
end)

Press Play. Look at the top-right of the Roblox UI — you should see Coins 0 next to your player name. Output should print "Player1 joined with 0 coins" (or similar).

If you don't see Coins in the top-right, the folder name is wrong — it must be exactly leaderstats (lowercase L, no space). Roblox watches for that exact spelling.

Stop.

Pass 2 — Find the Stage 2 SpawnLocation by attribute

Type this above the Players.PlayerAdded line:

local Players = game:GetService("Players")

-- Find the Stage 2 SpawnLocation by checking StageNumber attribute.
local stage2Pad = nil
for _, part in ipairs(workspace:GetChildren()) do
if part:IsA("SpawnLocation") and part:GetAttribute("StageNumber") == 2 then
stage2Pad = part
break
end
end

print("Found Stage 2 pad:", stage2Pad and stage2Pad.Name or "NOT FOUND")

Players.PlayerAdded:Connect(function(player)
-- (Pass 1 code stays the same)
end)

Press Play. Output should print "Found Stage 2 pad: SpawnLocation" (the default name; your pad might be named differently if you renamed it).

If Output prints "NOT FOUND", the StageNumber attribute on your Stage 2 SpawnLocation is missing or has the wrong value. Go back to Step 2.1 and confirm.

Stop.

Pass 3 — Pay 1 coin the first time the player touches Stage 2's pad

Below the Players.PlayerAdded block, add:

local hasReachedStage2 = {}

stage2Pad.Touched:Connect(function(otherPart)
local character = otherPart.Parent
local player = Players:GetPlayerFromCharacter(character)
if player and not hasReachedStage2[player] then
hasReachedStage2[player] = true
player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + 1
print(player.Name, "earned a coin. Total:", player.leaderstats.Coins.Value)
end
end)

Press Play. Climb the wall. Touch the red pad. The top-right should flip from Coins 0 to Coins 1. Reset. Touch the pad again — Coins stays at 1 (the debounce flag did its job).

Walk away from the pad and back onto it. Still 1. The script only pays you the first time per join. That's the design.

Understand it

The leaderstats folder is a Roblox convention, not a feature you import. Roblox watches every Player for a child Folder named exactly leaderstats. If it finds one, it puts whatever values are inside into the top-right player list. Misspell the name (LeaderStats, leaderstat, leader-stats) and nothing appears.

The PlayerAdded event is the only safe place to build per-player state. If you tried to build the folder when the script first runs, you'd build one folder — for nobody. PlayerAdded fires once for every player who joins, so each one gets their own folder.

The attribute lookup is the real tycoon trick. The script doesn't know the SpawnLocation's name (you could rename it). It doesn't know the SpawnLocation's color (you could change it). It only knows that the Stage 2 pad has StageNumber = 2. That's why Setup made you add the attribute — every later tycoon script in this course will find pads the same way.

The hasReachedStage2 table is a debounce. The Touched event fires once per body part per collision — your character has ~12 parts, so without debounce the player would earn 12 coins per touch. The table remembers "has this player already been paid for this pad?" so the answer flips to true the first time and stays true.

Script anatomy

How this script pays a coin for reaching the second checkpoint

The script does three jobs: find the right pad once, give every player a coin folder when they join, and pay them the first time they reach the pad.

local Players = game:GetService("Players")

local stage2Pad = nil
for _, part in ipairs(workspace:GetChildren()) do
if part:IsA("SpawnLocation") and part:GetAttribute("StageNumber") == 2 then
stage2Pad = part
break
end
end

Players.PlayerAdded:Connect(function(player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
stats.Parent = player

local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = stats
end)

local hasReachedStage2 = {}

stage2Pad.Touched:Connect(function(otherPart)
local character = otherPart.Parent
local player = Players:GetPlayerFromCharacter(character)
if player and not hasReachedStage2[player] then
hasReachedStage2[player] = true
player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + 1
end
end)
  1. Line 1Grab the Players service.

    Players is the Roblox service that tracks who's in the game. Every tycoon script in this course starts with this line because every payout is tied to a specific player.

  2. Lines 3–9Find the Stage 2 pad by its StageNumber attribute.

    Loop over every direct child of Workspace. For each one that's a SpawnLocation AND has StageNumber == 2, save a reference. Break out as soon as you find it. This runs ONCE when the script starts, not on every touch.

  3. Lines 11–21Build a leaderstats folder for every joining player.

    PlayerAdded fires when someone joins. The function creates a Folder named exactly leaderstats (Roblox watches for that spelling), then puts an IntValue named Coins inside. Roblox sees both and shows 'Coins: 0' in the top-right.

  4. Line 23The debounce table.

    Keys are Player objects, values are true/false. hasReachedStage2[player] starts as nil (falsy). After the first payout it becomes true. The if check below stops repeat payouts.

  5. Lines 25–32Pay 1 coin the first time the player touches Stage 2's pad.

    Touched gives us otherPart — a body part. Walk up: otherPart.Parent is the character, GetPlayerFromCharacter turns it into a Player. If that player exists AND hasn't been paid yet, flip the flag and bump their Coins value by 1.

Try this

Learning beat

Try this

Three short experiments. Predict before you run, then test your guess.

Predict first

Comment out the hasReachedStage2 debounce — remove the if not hasReachedStage2[player] then check and just bump the coins unconditionally. Predict what happens when you touch the pad once. Then run it. How high does the counter go from a single touch?

Compare

Open the Stage 2 SpawnLocation. Change its StageNumber attribute from 2 to 99. Press Play, climb, touch the pad. What happens? Open Output — what does the Pass 2 print say? Why does the lookup matter more than the pad's name or color?

Connect

Right now the script only knows about Stage 2's pad. In Stage 2 of this course you'll wire ALL of the SpawnLocations (Stage 3, Stage 4, ...) to pay coins too. What single change would you make to the script's lookup loop so it grabs every SpawnLocation at once instead of just the one with StageNumber == 2?

Test your stage

  • Press ▶ Play. The top-right shows Coins 0 next to your player name.
  • Climb the wall to the red pad. The counter flips to Coins 1.
  • Walk away and back onto the pad. Counter stays at 1 — the debounce works.
  • Reset your character (Esc → Reset Character). Counter still at 1; you respawn on red.
  • Open the Output window. You see "PlayerN joined with 0 coins" and "PlayerN earned a coin. Total: 1".
  • Design check. Does reaching the pad feel like a payout? If the coin appearing is too subtle, the next stages won't carry weight. (Stage 4 fixes this with a coin pickup sound — for now, just notice if the moment lands.)

If it breaks

  • The top-right doesn't show Coins at all. The folder name is wrong. It must be exactly leaderstats — lowercase L, no underscore, no space. Roblox watches for that exact string.
  • Output says "Found Stage 2 pad: NOT FOUND". Your Stage 2 SpawnLocation either doesn't have a StageNumber attribute, or its value isn't 2. Go to Step 2.1.
  • The pad pays me 12 coins, not 1. You skipped or broke the debounce. Re-check the if player and not hasReachedStage2[player] then line — both conditions matter.
  • Output says attempt to index nil with 'Touched'. The Pass 2 lookup didn't find the pad, so stage2Pad is nil when Pass 3 tries to wire its Touched event. Run Pass 2 alone first and confirm Output prints the pad's name.
  • The coin only pays out the first time I play. After a restart it doesn't. That's correct — the debounce table is per-server-run, not per-player-forever. In a real game you'd persist this with DataStoreService; we'll do that in Stage 4 of this course. For now, the debounce is intentional.
  • Studio crashes when I press Play. Open Output (View → Output) and read the red error message. The most common Stage 1 crash is a typo in leaderstats (capital L) — Roblox doesn't crash, but the folder fails silently. The actual crash is usually a missing end on the PlayerAdded function.
Coach notes

The biggest pedagogical risk in Stage 1 is campers entering the whole 3-pass script in one shot and never running the in-between checks. Don't let them — the three passes exist because each one teaches a separate thing (PlayerAdded / attribute lookup / Touched debounce). If they skip straight to the final version and it works, they learned one thing instead of three.

  • The debounce table (hasReachedStage2 = {}) is the most subtle line in the script. Walk every camper through why — count the body parts on a Roblox character with them (~12), then count what the coin value would be without debounce.
  • Common typo: Player.leaderstats instead of player.leaderstats. Lua is case-sensitive. The error in Output will say attempt to index nil with 'leaderstats' — that's the clue.
  • Some campers will try to put the Script in Workspace (habit from Part 1 / Makers). It will work, but tell them: starting Stage 4, coin and machine logic should live in ServerScriptService because the server should own rewards and payouts.
  • Total time: 50 minutes. The obby climb takes 15, the SpawnLocation + attribute takes 10, and the 3-pass script takes 25 (with reading Output between each pass). Don't rush the Output checks.