Roblox EP4 A complete obby: checkpoints, stage board, lava, finish line
Put it all together into a playable obby: SpawnLocation checkpoints, a leaderstats board, all lava run by one Script via Tags, and a finish line that counts wins.
- Obby checkpoints are several SpawnLocations, with the server setting each player's RespawnLocation to the latest one passed
- Many parts that behave the same get a Tag and one Script; change one place and the whole game follows
- leaderstats is a specially named folder; put values in it and Roblox shows them as a leaderboard
Over three episodes we gathered every piece: a scene you can walk and jump through (EP1), lava and fading floors (EP2), and an understanding of where code runs (EP3). This episode assembles them into a real, playable obby from start to finish line.
The game you will have
- 5 stages, each harder than the last
- Checkpoints on every stage: fall in lava and you respawn at your latest stage, not the very start
- A leaderboard top right, showing your stage and how many times you have won
- Lava and fading floors, as many as you like, run by one piece of code
- A finish line that gives +1 win and sends you back to the start to play again
Preparing the scene
1. Clear out the old. Delete the Scripts you put inside each floor in EP2, or the code will run twice.
2. Checkpoints. In Workspace, create a Folder called Checkpoints (hover Workspace → + → Folder) and put 5 SpawnLocations in it.
- The Baseplate template already has one SpawnLocation. Drag it into
Checkpointsand rename it1. That is the start - Add four more from the Model tab → Spawn, named
2,3,4,5, each at the start of the next stage - Set Duration to
0on all of them (no protective bubble on spawn) - No SpawnLocation may sit outside this folder, or players may randomly spawn there
The names must be numbers, because the code uses the name as the stage number.
3. Lava. Place as many lava floors as you like, all anchored, then add a Tag: select the part, scroll to the Tags section in Properties, press + and type Lava. You can select many parts and tag them at once.
4. Fading floors. Same again, with the Tag FadePad.
5. Finish line. Create a Part in Workspace named Finish, anchored, and make it stand out, for example gold with the Neon material.
6. Settings. Use the Config ModuleScript in ReplicatedStorage from EP3; it needs FADE_SECONDS and GONE_SECONDS.
Designing stages that are fun
| Stage | Obstacle | Tip |
|---|---|---|
| 1 | plain stairs | let players get used to jumping |
| 2 | slabs apart over lava | 5–6 studs apart, still easy |
| 3 | fading floors | force players to hurry |
| 4 | a 1–2 stud bridge with lava on both sides | tests precision |
| 5 | everything combined, jumps near the limit | the last stage should be the hardest |
The golden rule of obbies: every stage must be beatable. Test every one yourself; if it takes more than ten tries, most players will quit first.
The whole game: one Script in ServerScriptService
Hover ServerScriptService → + → Script, name it ObbyServer, and paste in all of this. Each part is explained below.
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Config = require(ReplicatedStorage:WaitForChild("Config"))
local checkpoints = workspace:WaitForChild("Checkpoints")
local finish = workspace:WaitForChild("Finish")
local firstSpawn = checkpoints:WaitForChild("1")
-- Who touched? Returns the player (or nil if not a player) and the Humanoid.
local function playerFromHit(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildWhichIsA("Humanoid")
if not humanoid or humanoid.Health <= 0 then
return nil, nil
end
return Players:GetPlayerFromCharacter(character), humanoid
end
-- 1) Leaderboard: Stage and Wins
Players.PlayerAdded:Connect(function(player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
local stage = Instance.new("IntValue")
stage.Name = "Stage"
stage.Value = 1
stage.Parent = stats
local wins = Instance.new("IntValue")
wins.Name = "Wins"
wins.Parent = stats
stats.Parent = player
player.RespawnLocation = firstSpawn
end)
-- 2) Checkpoints: SpawnLocations named 1, 2, 3 ... in the Checkpoints folder
for _, spawn in checkpoints:GetChildren() do
local number = tonumber(spawn.Name)
if spawn:IsA("SpawnLocation") and number then
spawn.Touched:Connect(function(hit)
local player = playerFromHit(hit)
if not player then
return
end
local stage = player.leaderstats.Stage
if number > stage.Value then -- forward only
stage.Value = number
player.RespawnLocation = spawn
end
end)
end
end
-- 3) Lava: every part tagged "Lava"
local function setupLava(part)
part.Touched:Connect(function(hit)
local _, humanoid = playerFromHit(hit)
if humanoid then
humanoid.Health = 0
end
end)
end
for _, part in CollectionService:GetTagged("Lava") do
setupLava(part)
end
CollectionService:GetInstanceAddedSignal("Lava"):Connect(setupLava)
-- 4) Fading floors: every part tagged "FadePad"
local function setupFade(pad)
local busy = false -- each pad has its own busy
pad.Touched:Connect(function(hit)
if busy or not playerFromHit(hit) then
return
end
busy = true
for i = 1, 10 do
pad.Transparency = i / 10
task.wait(Config.FADE_SECONDS / 10)
end
pad.CanCollide = false
task.wait(Config.GONE_SECONDS)
pad.CanCollide = true
pad.Transparency = 0
busy = false
end)
end
for _, pad in CollectionService:GetTagged("FadePad") do
setupFade(pad)
end
CollectionService:GetInstanceAddedSignal("FadePad"):Connect(setupFade)
-- 5) Finish line
local finishing = {} -- stops double counting, per player
finish.Touched:Connect(function(hit)
local player = playerFromHit(hit)
if not player or finishing[player] then
return
end
finishing[player] = true
player.leaderstats.Wins.Value += 1
player.leaderstats.Stage.Value = 1
player.RespawnLocation = firstSpawn
task.wait(1)
local character = player.Character
if character then
character:PivotTo(firstSpawn.CFrame + Vector3.new(0, 4, 0))
end
finishing[player] = nil
end)
Players.PlayerRemoving:Connect(function(player)
finishing[player] = nil
end)
Part by part
playerFromHit. Every part of the game asks the same question, “who touched?”, so it is one function used everywhere. It checks three things: is there a Humanoid, is it still alive, and is it a real player or just an NPC character (GetPlayerFromCharacter returns nil for non-players). Luau functions can return several values, so it hands back both the player and the Humanoid.
The leaderboard. A folder named leaderstats (all lowercase, spelled exactly) under a player makes Roblox show its values as the top-right board. The values must be value objects such as IntValue (whole numbers), and each one’s name becomes a column heading. Notice we fill everything in first and set stats.Parent = player last, so the board appears complete in one go.
Checkpoints. player.RespawnLocation is where the player respawns after dying. Set it, and Roblox handles respawning. The condition number > stage.Value makes progress forward-only: walking back over an old checkpoint does not move you back. tonumber(spawn.Name) turns the name "3" into the number 3; any other name gives nil and that spawn is skipped.
Tags and CollectionService. Instead of a Script inside every lava part, each part carries the label Lava, and CollectionService:GetTagged("Lava") returns every part with that label. GetInstanceAddedSignal catches parts added later during the game. Place another hundred lava parts and the code never changes; just tag them.
A developer’s habit worth having from day one: things that behave the same get one piece of code.
Each pad’s own busy. In setupFade, busy is created fresh each time the function is called, so every pad has its own. One pad fading does not freeze the others. Move local busy = false outside the function and they all share one, so stepping on one pad makes all the others ignore you.
The finish line. The finishing table is a debounce per player. With a single variable as in EP2, when one person crosses the line, someone arriving at the same moment would not be counted. character:PivotTo(...) moves the whole character; we put it 4 studs above the start so it does not sink into the floor. PlayerRemoving clears entries for people who leave, so the table does not keep growing.
Testing
- Press Play and look top right: a board with Stage = 1 and Wins = 0
- Touch checkpoint 2: Stage becomes 2
- Jump into lava: you respawn at checkpoint 2, not the start
- Reach
Finish: Wins becomes 1 and you are sent back to the start - Test with 2 players (Test → Clients and Servers) and have both cross the finish together: both get +1
If you spawn in the wrong place, check for a SpawnLocation left outside the folder. If lava does nothing, check the Tag is spelled Lava with the same capitals, and look for red text in Output.
Try it in 10 minutes
- Add stage 6 by placing a SpawnLocation named
6in the folder; no code changes at all - Add 10 more lava parts just by tagging them
- Set
GONE_SECONDSin Config to 1 and replay stage 3; the whole game’s difficulty changes from one line
Common mistakes
- Forgetting to delete the old Scripts inside each lava part, so things run twice
- A SpawnLocation outside the folder, so players spawn in the wrong place at random
- Spelling
leaderstatswith capitals, so the board never appears - One debounce for all players at the finish, so people arriving together are not counted
- Naming checkpoints
Stage2instead of2, sotonumberreturns nil and that checkpoint does nothing
Next episode
The game is playable, but leave it and your stage and wins vanish. EP5 builds a second game, Lava Rising, with the whole level made in code and the rules tested outside Studio. Then EP6 saves player data between sessions with DataStore.
FAQ
If I leave and come back, is my progress gone?
For now, yes. Nothing is saved yet; everything lives in the server's memory. Saving between sessions with DataStore is EP5.
Why is there a bubble around me after respawning?
A SpawnLocation has a Duration, the time you get a ForceField after spawning, 10 seconds by default. An obby does not need it; set it to 0.
Commands and settings were tested on sample systems. Try them on a test machine before production.