How to Make an Obby in Roblox Studio: Step by Step, With Scripts (2026)
Build a Roblox obby from an empty Baseplate to a published game: stages, kill bricks, checkpoints that save, moving platforms that carry players, a speed-run timer and a finish line, with every script included.
“Look scared then turn and run away with big dramatic strides”
- An obby is a Roblox obstacle course: jump from stage to stage, respawn at your last checkpoint when you fall, and race to the end.
- You need four scripts: kill bricks (one script for all of them, using tags), checkpoints with saving, a moving platform, and a finish. All are below, ready to paste.
- Use SpawnLocations as checkpoints and set
Player.RespawnLocation, the approach Roblox’s own tutorials use. Save the stage withUpdateAsync. - A tweened, anchored platform will not carry players. Drive an unanchored platform with a PrismaticConstraint instead.
- End on something worth reaching: a victory animation and a cheering NPC make a finish line feel like a finish.
On this page
- 01How do you plan an obby before you build?
- 02How do you build the stages?
- 03How do you make a kill brick in Roblox?
- 04How do you make checkpoints that save progress?
- 05How do you make a moving platform that carries players?
- 06What other obstacles can you add?
- 07How do you add a speed-run timer?
- 08How do you make a finish line worth reaching?
- 09How do you publish your obby?
- 10Why isn’t my obby working?
To make an obby in Roblox, build a row of stages out of parts, add checkpoints so players respawn at the last one they reached, add hazards that reset them, and publish. The building is the fun part. The scripts are where most first obbies break, so every one you need is here in full, with the reasoning behind it.
This guide starts from the Baseplate template so you see how every piece works. Roblox’s Classic Obby template ships with checkpoints, jump pads and hazards already made, and is a good next step once you know what is inside them. Note that it turns on Roblox’s new Character Controller Library by default; the scripts here are written for the standard Humanoid character.
How do you plan an obby before you build?
- Start with ten stages. Ten finished stages beat fifty half-built ones, and you can add more after people play it.
- Ramp the difficulty. Stages 1 to 3 should be impossible to fail. Introduce one new obstacle type every couple of stages, then combine them.
- Pick a theme with a twist. Not “space obby” but “an obby where you are mouse-sized in a giant’s kitchen”. Our list of Roblox game ideas has more.
- Keep gaps fair. A default character walks at 16 studs per second and clears a gap of about eight studs comfortably. Measure your early gaps and playtest every jump.
How do you build the stages?
- 1Open Studio and create a Baseplate place. Save it straight away with File › Save to Roblox so your work is in the cloud.
- 2Make folders. In the Explorer, add two Folders to the Workspace:
StagesandCheckpoints. Put each stage’s parts in a Model insideStages. - 3Build with parts. Insert parts from the Home tab and shape them with Move, Scale and Rotate. Turn on snapping in the Model tab so platforms line up.
- 4Anchor everything that should not move. Select all your platforms and tick
Anchored, or they will fall when the game starts. - 5Colour by meaning. Players learn fast if red always kills, green is always safe and blue always moves. Keep the rule for the whole obby.
- 6Playtest constantly. Press F5 (Play) after every stage and run it yourself. If you fail a jump twice, players will fail it ten times.
How do you make a kill brick in Roblox?
Do not put a script in every lava part. Give each one a tag instead (select the part, then in Properties › Tags add KillBrick) and let one script handle them all:
-- Script in ServerScriptService. Kills on touch for every part
-- tagged "KillBrick", including ones added while the game runs.
local CollectionService = game:GetService("CollectionService")
local function onTouched(otherPart)
local character = otherPart:FindFirstAncestorOfClass("Model")
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Health > 0 then -- the Health check is the debounce
humanoid.Health = 0
end
end
local function setUp(part)
if part:IsA("BasePart") then
part.Touched:Connect(onTouched)
end
end
CollectionService:GetInstanceAddedSignal("KillBrick"):Connect(setUp)
for _, part in CollectionService:GetTagged("KillBrick") do
setUp(part)
endSetting Health to 0 kills even a player with a spawn ForceField. If you want spawn protection to save players, use humanoid:TakeDamage(humanoid.MaxHealth) instead, which respects ForceFields.
How do you make checkpoints that save progress?
Use a SpawnLocation for every checkpoint and set the player’s RespawnLocation when they reach it. That is the approach in Roblox’s own spawn tutorial, and it avoids filling the player list with a team per stage.
- 1Place a SpawnLocation at the start of each stage and move it into the
Checkpointsfolder. - 2Name them in order:
1,2,3and so on. The script reads the number from the name. - 3Set their properties:
Neutralon,AllowTeamChangeOnTouchoff,Anchoredon, andDurationto 0 so players do not get a ForceField at every checkpoint. - 4Turn on data stores for testing: File › Experience Settings › Security › Enable Studio Access to API Services. Do this on a test copy, not your live game.
- 5Add the script below to ServerScriptService.
-- Script in ServerScriptService. Needs a folder Workspace.Checkpoints
-- holding SpawnLocations named 1, 2, 3 ... in order.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("ObbyProgress_v1")
local checkpoints = workspace:WaitForChild("Checkpoints")
local function checkpointFor(stage)
local checkpoint = checkpoints:FindFirstChild(tostring(stage))
if checkpoint and checkpoint:IsA("SpawnLocation") then
return checkpoint
end
return nil
end
local function onPlayerAdded(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats" -- must be exactly this name
local stage = Instance.new("IntValue")
stage.Name = "Stage"
stage.Value = 1
stage.Parent = leaderstats
leaderstats.Parent = player
player.RespawnLocation = checkpointFor(1) -- set before the first spawn
local ok, saved = pcall(function()
return store:GetAsync("User_" .. player.UserId)
end)
if not ok then
warn("Could not load progress for", player.Name, saved)
player:SetAttribute("LoadFailed", true) -- never overwrite good data
return
end
if typeof(saved) == "number" and checkpointFor(saved) then
stage.Value = saved
player.RespawnLocation = checkpointFor(saved)
if player.Character then -- spawned before the data arrived
player.Character:PivotTo(checkpointFor(saved).CFrame + Vector3.new(0, 4, 0))
end
end
end
for _, checkpoint in checkpoints:GetChildren() do
if not checkpoint:IsA("SpawnLocation") then
continue
end
checkpoint.Touched:Connect(function(hit)
local player = Players:GetPlayerFromCharacter(hit.Parent)
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
if not player or not humanoid or humanoid.Health <= 0 then
return
end
local stage = player.leaderstats.Stage
if tonumber(checkpoint.Name) == stage.Value + 1 then -- no skipping
stage.Value += 1
player.RespawnLocation = checkpoint
end
end)
end
local function save(player)
if player:GetAttribute("LoadFailed") then
return
end
local leaderstats = player:FindFirstChild("leaderstats")
local stage = leaderstats and leaderstats:FindFirstChild("Stage")
if not stage then
return
end
local ok, err = pcall(function()
store:UpdateAsync("User_" .. player.UserId, function(old)
if typeof(old) == "number" and old >= stage.Value then
return nil -- nil cancels the write: never save less progress
end
return stage.Value
end)
end)
if not ok then
warn("Could not save progress for", player.Name, err)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
Players.PlayerRemoving:Connect(save)
game:BindToClose(function() -- the server waits up to 30 seconds for this
local pending = 0
for _, player in Players:GetPlayers() do
pending += 1
task.spawn(function()
save(player)
pending -= 1
end)
end
while pending > 0 do
task.wait()
end
end)leaderstatsmust be spelled exactly like that for the Stage column to appear on the leaderboard.- Checkpoints only count in order, so a player cannot skip from stage 2 to stage 9 by falling onto a lower platform.
UpdateAsyncrefuses to save a lower stage than the one already stored, so a failed load can never wipe someone’s progress.BindToClosesaves everyone when the server shuts down, whichPlayerRemovingalone can miss.
How do you make a moving platform that carries players?
The obvious approach, tweening an anchored part, moves the platform out from under the player, because a CFrame change is not physics and the character is not carried along. Drive an unanchored platform with a constraint instead:
- 1Make an invisible base. An anchored part named
Base, withTransparency1 andCanCollideoff, where the platform should start. - 2Make the platform. An unanchored part, in the same position as the base.
- 3Add attachments. One Attachment in the base and one in the platform. Point both so their X axis runs along the direction of travel.
- 4Add a PrismaticConstraint to the base, with Attachment0 set to the base’s attachment and Attachment1 to the platform’s.
- 5Set it to Servo:
ActuatorTypeServo,Speed10,ServoMaxForcelarge (10,000 or more), andLimitsEnabledon with limits that cover the travel distance. - 6Add the script below inside the base.
-- Script inside the anchored Base part, next to the PrismaticConstraint.
local prismatic = script.Parent:WaitForChild("PrismaticConstraint")
local platform = prismatic.Attachment1.Parent
platform:SetNetworkOwner(nil) -- simulated by the server: no jitter
while true do
prismatic.TargetPosition = 20
task.wait(4)
prismatic.TargetPosition = 0
task.wait(4)
endWhat other obstacles can you add?
A conveyor belt. An anchored part with a velocity pushes anything standing on it without moving itself. Set it from a server Script:
-- Script inside an anchored part. Pushes anything standing on it
-- along the part's front face at 12 studs per second.
local belt = script.Parent
belt.AssemblyLinearVelocity = belt.CFrame.LookVector * 12A disappearing platform. It fades when touched, stops being solid, and comes back after three seconds, based on Roblox’s own fading trap tutorial:
-- Script inside the platform part.
local TweenService = game:GetService("TweenService")
local platform = script.Parent
local busy = false
platform.Touched:Connect(function(hit)
if busy or not hit.Parent:FindFirstChildOfClass("Humanoid") then
return
end
busy = true
local fade = TweenService:Create(platform, TweenInfo.new(0.6), { Transparency = 1 })
fade:Play()
fade.Completed:Wait()
platform.CanCollide = false
task.wait(3)
platform.CanCollide = true
platform.Transparency = 0
busy = false
end)- Spinning bars use a HingeConstraint set to Motor on an unanchored bar.
- Truss ladders need no script: insert a TrussPart and players climb it automatically.
- Jump pads set an upward velocity on whoever touches them; the Classic Obby template includes one to study.
- Wall jumps and wraps are pure building. Some of the best stages have no scripts at all.
How do you add a speed-run timer?
Keep the time on the server so nobody can fake a record, and only display it on the client. Add parts named StartPad and FinishPad, then:
-- Script in ServerScriptService. Needs parts named StartPad and
-- FinishPad in the Workspace.
local Players = game:GetService("Players")
local function playerFrom(hit)
return Players:GetPlayerFromCharacter(hit.Parent)
end
workspace.StartPad.Touched:Connect(function(hit)
local player = playerFrom(hit)
if player then
player:SetAttribute("RunStart", workspace:GetServerTimeNow())
end
end)
workspace.FinishPad.Touched:Connect(function(hit)
local player = playerFrom(hit)
local started = player and player:GetAttribute("RunStart")
if not started then
return
end
player:SetAttribute("RunStart", nil)
local seconds = workspace:GetServerTimeNow() - started
print(player.Name, string.format("finished in %.2f seconds", seconds))
end)-- LocalScript inside a TextLabel in a ScreenGui in StarterGui.
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local label = script.Parent
RunService.Heartbeat:Connect(function()
local started = player:GetAttribute("RunStart")
if started then
label.Text = string.format("%.2f", workspace:GetServerTimeNow() - started)
else
label.Text = ""
end
end)From here a leaderboard is one step away: save each player’s best time with the same data store pattern as the checkpoints, or use an OrderedDataStore for a global top ten.
How do you make a finish line worth reaching?
Most obbies end on a flat pad and a chat message. The end is the moment players decide whether to tell their friends, so make it a moment: confetti particles, a badge, a winners’ room, and above all movement. A player who reaches the top should celebrate, and the place should celebrate back.
-- Script inside the FinishPad part. Plays a victory animation on
-- whoever reaches it. Put your own animation ID below.
local Players = game:GetService("Players")
local pad = script.Parent
local victory = Instance.new("Animation")
victory.AnimationId = "rbxassetid://YOUR_VICTORY_ID"
local celebrating = {}
pad.Touched:Connect(function(hit)
local player = Players:GetPlayerFromCharacter(hit.Parent)
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")
if not player or not animator or celebrating[player] then
return
end
celebrating[player] = true
local track = animator:LoadAnimation(victory)
track.Priority = Enum.AnimationPriority.Action
track.Looped = false -- a looping track would never fire Stopped
track:Play()
track.Stopped:Wait()
track:Destroy()
celebrating[player] = nil
end)Then add a cheering NPC beside the pad and a guide NPC waving at the start. Our guide to animating an NPC in Roblox has the script; the animations themselves you can generate.
How do you publish your obby?
- 1File › Publish to Roblox. Give it a name, a description and a creator (a group is recommended if anyone else will help), choose devices, and click Create.
- 2It starts private. In the Creator Dashboard, open the experience, then Configure › Settings › Audience, and choose Public, or Limited for friends and testers first.
- 3Complete the requirements. Making a game public needs an account in good standing, an age check and the content maturity questionnaire.
- 4Add a thumbnail and icon. These decide whether anyone clicks. Show a player mid-jump on your best-looking stage.
- 5Test on a phone. Many obby players are on mobile, where thumbstick movement can be slower than on a keyboard. If a jump is hard on a phone, widen the platform.
Why isn’t my obby working?
| Problem | Likely cause | Fix |
|---|---|---|
| “Stage” never appears on the leaderboard | The folder is not named exactly leaderstats | Rename it, all lower case |
| Stuck on Stage 1 in Studio | Data stores are off for Studio | Enable Studio Access to API Services on a test copy |
| “API Services rejected request” | The same setting, or an unpublished place | Publish the place, then enable the setting |
| Respawning at the start after rejoining | The stage loaded but RespawnLocation was not set, or the load failed | Use the checkpoint script above and check the Output for load warnings |
| Kill brick does nothing | The tag is misspelled, or CanTouch is off | Tag it KillBrick exactly and tick CanTouch |
| Platforms fall when the game starts | They are not anchored | Anchor every static part |
| Moving platform slides out from under players | An anchored part moved by a tween | Use the PrismaticConstraint setup |
| Checkpoint skipped | Checkpoints named out of order | Name them 1, 2, 3 in the order players reach them |
Sources and further reading (checked Oct 6, 2026)
- Roblox Creator Hub, Templates: create.roblox.com
- Roblox Creator Hub, Spawn and respawn: create.roblox.com
- Roblox Engine API, SpawnLocation: create.roblox.com
- Roblox Creator Hub, Data stores: create.roblox.com
- Roblox Engine API, CollectionService: create.roblox.com
- Roblox Creator Hub, Create elevators: create.roblox.com
- Roblox Creator Hub, Fading trap: create.roblox.com
- Roblox Creator Hub, Publish experiences and places: create.roblox.com