Skip to main content

Stage 2: Sphere Staircase + XP and leveling

Course progressStage 2 of 10
~50 min
Before you start

Finish Stage 1. You spawn holding a sword, and the top-right shows Level 1 and XP 0. Your CombatStats script lives in ServerScriptService.

Build

a staircase of spheres, the Stage 3 checkpoint, and the XP-and-leveling system

Learn

how to award XP by looping over every checkpoint by its StageNumber, and how a level-up loop converts XP into Levels

Ship

checkpoints that pay XP, and a Level that climbs and announces itself when you cross the XP threshold

Teacher demo

90-second demo:

  • Press Play. Top-right shows Level 1, XP 0.
  • Climb the spheres and touch the next checkpoint. XP jumps up.
  • Keep reaching checkpoints until XP crosses the threshold — the Level ticks to 2 and XP resets toward the next level.
  • Explain: "That's the whole loop of every RPG. Fight, earn XP, level up. Today the obby itself pays the XP; later, enemies will."

The big idea

In Stage 1 the XP number just sat at 0. An RPG is boring if the numbers never move — so today they start moving.

Two ideas do the work. First, awarding XP: every checkpoint pays the player some XP when they reach it. Instead of writing one block of code per checkpoint, you write one loop that finds every SpawnLocation by its StageNumber attribute and wires them all the same way. One loop, every pad — that's the same trick the base obby uses for checkpoints, now paying XP.

Second, leveling up: XP isn't meant to grow forever. When it crosses a threshold, the player gains a Level and the XP "spends down" toward the next one. The threshold gets bigger each level, so higher levels take more XP — exactly like the games you're copying.

New words
GetAttribute
reads the value of an attribute you added in Properties, like StageNumber
ipairs loop
walks through a list in order; we use it to visit every part in Workspace
GetPlayerFromCharacter
looks up which player owns a character that touched something — Touched gives a body part, not a player
debounce
a flag that stops the same event from firing too many times in a row; here, so each pad only pays once
threshold
the amount of XP needed to reach the next Level; ours grows each level so leveling gets harder
while loop
repeats as long as a condition is true; we use one to handle leveling up more than once from a big XP gain

Build it

Step 1 — Build the sphere staircase

A climb made of spheres instead of blocks. Rolling off the edge is part of the challenge.

A sphere staircase in a Roblox combat obby with XP and Level numbers visible

Build this part

StepSphere_1

Sphere
Open recipe
Size
5 × 5 × 5
Color
Bright orange
Material
Plastic
Anchored
✓ Yes
Place
In front of the Stage 2 red pad, low
Build this part

StepSphere_2

Sphere
Open recipe
Size
5 × 5 × 5
Color
Bright orange
Material
Plastic
Anchored
✓ Yes
Place
A short hop up and forward from StepSphere_1
Build this part

StepSphere_3

Sphere
Open recipe
Size
5 × 5 × 5
Color
Bright orange
Material
Plastic
Anchored
✓ Yes
Place
A short hop up and forward from StepSphere_2

Add a couple more if you want a longer climb. Press Play and confirm you can hop sphere to sphere.

Step 2 — Wire the Stage 3 checkpoint

Same checkpoint pattern as before. Place the SpawnLocation at the top of the spheres.

Build this part

SpawnLocation (Stage 3 — top of the spheres)

Block
Open recipe
Size
6 × 1 × 6
Color
Bright blue
Material
Plastic
Anchored
✓ Yes
Place
On top of the highest sphere

Add a StageNumber attribute = 3. Check AllowTeamChangeOnTouch. Uncheck Neutral. Set TeamColor to Bright blue.

Same gesture as Stage 1: add the StageNumber = 3 attribute, then make a matching Stage 3 Team (Bright blue, AutoAssignable unchecked).

Step 3 — Award XP from every checkpoint

Open your CombatStats script in ServerScriptService. You'll add the XP system in three passes, below the PlayerAdded block you wrote in Stage 1.

Pass 1 — A helper that adds XP and levels up

Add this above the Players.PlayerAdded line:

local function xpNeeded(level)
return level * 100
end

local function addXP(player, amount)
local stats = player:FindFirstChild("leaderstats")
if not stats then return end

stats.XP.Value = stats.XP.Value + amount

while stats.XP.Value >= xpNeeded(stats.Level.Value) do
stats.XP.Value = stats.XP.Value - xpNeeded(stats.Level.Value)
stats.Level.Value = stats.Level.Value + 1
print(player.Name, "leveled up to", stats.Level.Value)
end
end

This doesn't run yet — it's a helper, waiting to be called. Press Play to confirm there are no typos in Output (no red errors). Stop.

Pass 2 — Find every checkpoint and pay XP on touch

Add this below the Players.PlayerAdded block:

local awarded = {}

Players.PlayerRemoving:Connect(function(player)
awarded[player] = nil
end)

for _, part in ipairs(workspace:GetChildren()) do
if part:IsA("SpawnLocation") and part:GetAttribute("StageNumber") then
local stageNumber = part:GetAttribute("StageNumber")

part.Touched:Connect(function(otherPart)
local character = otherPart.Parent
local player = Players:GetPlayerFromCharacter(character)
if not player then return end

awarded[player] = awarded[player] or {}
if awarded[player][part] then return end
awarded[player][part] = true

addXP(player, stageNumber * 10)
print(player.Name, "reached stage", stageNumber, "and earned", stageNumber * 10, "XP")
end)
end
end

Press Play. Climb the spheres, touch the blue pad. XP jumps by 30 (Stage 3 pays 3 × 10). Touch it again — nothing, because the awarded table debounced it.

Pass 3 — Watch a level-up happen

You don't add code for this pass — you test the threshold. Level 1 needs 100 XP. Reaching Stage 2's pad pays 20, Stage 3's pays 30. Keep reaching checkpoints (and reset between them so you can re-touch earlier pads is not allowed — the debounce blocks repeats). To force a level-up now, temporarily lower the threshold:

Change xpNeeded to return level * 30 and press Play. After two or three checkpoints, watch Output print "PlayerN leveled up to 2" and the top-right Level tick up. Then set it back to level * 100 — the real curve.

Understand it

The one-loop-for-every-pad pattern is the heart of this stage. You didn't write Stage 2's pad and Stage 3's pad separately — you wrote a loop that finds anything that is a SpawnLocation with a StageNumber, and wired all of them at once. When you add Stage 4's pad next stage, this loop already covers it. That's why Setup made you tag every pad with the attribute.

The addXP helper keeps the leveling logic in one place. Anything that should grant XP — a checkpoint now, a defeated enemy later, a loot orb after that — just calls addXP(player, amount). The level-up rules live in exactly one function, so when you tune them, you tune them once.

The while loop inside addXP matters more than it looks. If a player earns a huge chunk of XP — enough for two levels at once — a single if would only level them up once and leave the extra XP stranded. A while keeps spending XP and adding Levels until there isn't enough left for another, so big XP gains level you up correctly every time.

Script anatomy

How a checkpoint touch becomes XP and a Level

The XP system is two pieces: a helper that converts XP into Levels, and a loop that calls it whenever a player touches a checkpoint.

local function xpNeeded(level)
return level * 100
end

local function addXP(player, amount)
local stats = player:FindFirstChild("leaderstats")
if not stats then return end

stats.XP.Value = stats.XP.Value + amount

while stats.XP.Value >= xpNeeded(stats.Level.Value) do
stats.XP.Value = stats.XP.Value - xpNeeded(stats.Level.Value)
stats.Level.Value = stats.Level.Value + 1
end
end

local awarded = {}

Players.PlayerRemoving:Connect(function(player)
awarded[player] = nil
end)

for _, part in ipairs(workspace:GetChildren()) do
if part:IsA("SpawnLocation") and part:GetAttribute("StageNumber") then
local stageNumber = part:GetAttribute("StageNumber")
part.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player then return end
awarded[player] = awarded[player] or {}
if awarded[player][part] then return end
awarded[player][part] = true
addXP(player, stageNumber * 10)
end)
end
end
  1. Lines 1–3The XP curve.

    xpNeeded(level) returns how much XP this level requires. level * 100 means Level 1 needs 100, Level 2 needs 200, and so on — higher levels take more.

  2. Lines 5–17Add XP, then level up as many times as earned.

    Bump XP, then the while loop spends XP and raises the Level repeatedly until there isn't enough for the next level. One call handles a tiny gain or a giant one.

  3. Lines 19–21Find every checkpoint at once.

    Loop over Workspace; for each SpawnLocation that has a StageNumber, wire it. This automatically covers every pad you've built and every pad you'll add later.

  4. Lines 22–31Pay XP once per pad per player.

    On touch, find the player, then use a nested table as a debounce so each player is paid only the first time they reach each pad. The payout scales with the pad's StageNumber.

Try this

Learning beat

Try this

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

Predict first

Predict what xpNeeded(3) returns with the level * 100 curve. Then predict how many checkpoints a fresh player must reach to hit Level 2. Check your math against Output.

Compare

Change addXP(player, stageNumber * 10) to addXP(player, 100) so every pad pays a flat 100. Press Play and compare how fast you level. Which feels more like a real RPG — flat XP or XP that scales with the stage?

Connect

The addXP helper doesn't care who calls it. In Stage 3 your sword will damage a dummy, and in Stage 8 a defeated enemy will drop loot. Which of those should also call addXP? What amount would feel fair for defeating an enemy?

Test your stage

  • Press ▶ Play. Top-right shows Level 1, XP 0.
  • Climb the spheres and touch the blue Stage 3 pad. XP jumps by 30.
  • Touch the same pad again — XP does not change (the debounce works).
  • With the threshold temporarily lowered, reach enough checkpoints to see Output print a level-up and the Level tick up.
  • Set xpNeeded back to level * 100.
  • Design check. Does leveling up feel like a reward? Right now it's just a number change in Output. Notice if it lands — Stage 6's health bars and later polish are what make it feel big.

If it breaks

  • XP never changes. The loop didn't find your pads. Confirm each SpawnLocation has a StageNumber attribute with a number value. Output's print in Pass 2 tells you which stage was reached.
  • XP goes up but Level never does. Your XP hasn't crossed xpNeeded(level) yet, or the while condition is wrong. Lower the curve temporarily (Pass 3) to confirm leveling works.
  • The Level jumps up forever / freezes Studio. Your while loop never subtracts XP. Make sure the line stats.XP.Value = stats.XP.Value - xpNeeded(stats.Level.Value) is inside the loop — without it the condition stays true and the loop spins forever.
  • Output says attempt to index nil with 'XP'. A player without a leaderstats folder reached the pad. The if not stats then return end guard at the top of addXP is what prevents this — make sure it's there.
Coach notes

The conceptual leap this stage is "one loop wires every pad." Campers who took Tycoon will recognize it; for everyone else, slow down on Pass 2 and point out that adding a new checkpoint later needs zero new code here.

  • The while vs if distinction in addXP is the subtle bit. Demonstrate it: temporarily call addXP(somePlayer, 1000) and show that a while loop levels them up several times while an if would not.
  • Watch for campers who lower the threshold in Pass 3 and forget to set it back. The real curve is level * 100.
  • Total time: 50 minutes. Sphere climb 15, checkpoint 5, three-pass script 30 (the helper and the loop each need a careful read).