An obby (obstacle course) is one of the best first projects you can build in Roblox Studio. You'll learn platform basics, simple scripting, and how players respawn — skills that carry into any game type.

We'll build a 5-stage course with working checkpoints and respawn logic. By the end, players can jump between platforms and restart from their last checkpoint if they fall off the map.

Set Up Your Workspace

Open Roblox Studio and create a new blank project. You'll see the default Baseplate in the viewport — delete it (right-click → Delete) so you have a clean workspace.

Now let's build the spawn area. In the Model tab, click Part and select Block. A new part appears in the workspace. In the Properties panel on the right, rename it to SpawnPlatform. Resize it to be large and flat — set Size to 20, 1, 20 and Position to 0, 5, 0. This is where players start.

Make sure CanCollide is checked and Material is set to something visible like Plastic. Change Color to something you like — try a green-ish color (RGB: 0, 150, 0).

💡 Tip — Always name your parts clearly in the Explorer. "Part" and "Part1" get confusing fast. Use SpawnPlatform, Checkpoint1, etc.

Build Your Platform Stages

Now we'll add 5 platforms that the player jumps across. Each one will be slightly higher and further away than the last.

Create your first platform: Insert a new Block part. Rename it Platform1. Set its Size to 6, 1, 6 and Position to 12, 7, 0. Make it a different color so it stands out from the spawn area — try red (RGB: 255, 0, 0).

Repeat this process 4 more times with the following positions and names:

  • Platform2: Position 20, 9, 5
  • Platform3: Position 22, 11, 15
  • Platform4: Position 15, 13, 22
  • Platform5: Position 5, 15, 20

Play the game and jump around. You should be able to make the jumps if you time them right. If a platform feels too hard or too easy to reach, adjust its Position slightly closer or further.

Add Checkpoints

Checkpoints let players respawn at that spot if they fall. We'll use simple parts with special names and a script to detect them.

Create a new Block part at Position 12, 6, 0 and name it Checkpoint1. Make it thin — Size 6, 0.5, 6 — so it doesn't block the platform. Make it semi-transparent: set Transparency to 0.5. Color it yellow (RGB: 255, 255, 0).

Add one more checkpoint at Platform5: Create a Block at Position 5, 14, 20, name it Checkpoint2, Size 5, 0.5, 5, Transparency 0.5.

Script the Checkpoint System

Now the magic happens. We'll create a script that saves each player's checkpoint position when they touch a checkpoint part. If they fall into the void (Y < 0), they respawn there.

First, insert a new Script in ServerScriptService (right-click ServerScriptService → Insert Object → Script). Name it CheckpointSystem. This is a server script, so it runs on the server and affects all players.

ServerScriptService > CheckpointSystem (Script)
local Players = game:GetService("Players")
local checkpointFolder = Instance.new("Folder")
checkpointFolder.Name = "PlayerCheckpoints"
checkpointFolder.Parent = game.ServerStorage

local function setupPlayer(player)
	local checkpointData = Instance.new("ObjectValue")
	checkpointData.Name = player.Name
	checkpointData.Parent = checkpointFolder
	
	local spawnPlatform = workspace:WaitForChild("SpawnPlatform")
	checkpointData.Value = spawnPlatform
end

local function onCharacterSpawned(character, player)
	local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
	local checkpointData = checkpointFolder:WaitForChild(player.Name)
	
	-- Teleport player to their checkpoint
	humanoidRootPart.CFrame = checkpointData.Value.CFrame + Vector3.new(0, 5, 0)
	
	-- Detect falling into void
	while character.Parent do
		if humanoidRootPart.Position.Y < 0 then
			character:MoveTo(checkpointData.Value.Position + Vector3.new(0, 5, 0))
		end
		wait(0.1)
	end
end

local function onCharacterAdded(character, player)
	onCharacterSpawned(character, player)
end

local function onPlayerAdded(player)
	setupPlayer(player)
	
	if player.Character then
		onCharacterAdded(player.Character, player)
	end
	
	player.CharacterAdded:Connect(function(character)
		onCharacterAdded(character, player)
	end)
end

Players.PlayerAdded:Connect(onPlayerAdded)

for _, player in pairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end

This script stores each player's checkpoint and respawns them at Y < 0. Now we need to detect when they touch a checkpoint and update their saved location.

Select your Checkpoint1 part in the workspace. Insert a new Script inside it (right-click Checkpoint1 → Insert Object → Script).

Workspace > Checkpoint1 > Script
local checkpoint = script.Parent
local checkpointFolder = game.ServerStorage:WaitForChild("PlayerCheckpoints")

local debounce = {}

checkpoint.Touched:Connect(function(hit)
	local humanoid = hit.Parent:FindFirstChild("Humanoid")
	if not humanoid then return end
	
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if not player then return end
	
	-- Debounce: prevent multiple triggers in quick succession
	if debounce[player.UserId] and tick() - debounce[player.UserId] < 0.5 then
		return
	end
	debounce[player.UserId] = tick()
	
	local checkpointData = checkpointFolder:FindFirstChild(player.Name)
	if checkpointData then
		checkpointData.Value = checkpoint
		print(player.Name .. " reached " .. checkpoint.Name)
	end
end)

Copy this same script into Checkpoint2 (select Checkpoint2 → Insert Object → Script and paste the code). Each checkpoint part now updates the player's saved location.

💡 Tip — The debounce table prevents the checkpoint from being touched 100 times per second. It waits 0.5 seconds between valid touches.

Test and Polish

Press the Play button and jump through your course. When you reach a checkpoint, you should respawn there if you fall. If you fall before reaching the first checkpoint, you return to the spawn platform.

If something doesn't work:

  • Player doesn't respawn: Check that the checkpointFolder exists in ServerStorage. Look at the Output window (View → Output) for error messages.
  • Jumps feel unfair: Adjust platform positions or sizes. Remember the player's jump distance is fixed, so spacing matters.
  • Checkpoint not triggering: Make sure CanCollide is ON for checkpoint parts and the part in ServerStorage is actually being saved.

Try adding more platforms or checkpoints now that you have the system working. You can also:

  • Add lava or spikes using wedge or kill parts with similar checkpoint detection
  • Create a finish platform that teleports players to a victory area
  • Adjust player walk speed or jump power in the Humanoid properties
  • Use different colors and materials to make the course visually interesting

Common Beginner Mistakes

Mistake 1: Script in the wrong place. Server scripts must live in ServerScriptService or inside workspace parts. LocalScripts go in the player's character or PlayerGui. Double-check where you put each script.

Mistake 2: Parts with CanCollide off. If your platforms are see-through to the player, they have CanCollide disabled. Always check the properties.

Mistake 3: Forgetting to wait for objects. Use :WaitForChild() when you need a part to exist before the script runs. This prevents nil errors.

Mistake 4: Scaling platforms too small. A 2×2 platform is hard to jump onto. Start with 6×6 or bigger, then make later stages smaller.

You now have a working obby. This foundation scales to bigger courses with dozens of stages, special mechanics, and custom themes. Keep building!