Add checkpoints and hazards
Outcome and prerequisites
Extend the five-platform obby. Players who reach an intermediate checkpoint will respawn there after falling. Checkpoint progress belongs to the Player and survives a character death within that server session; it does not yet persist after leaving the game.
Add checkpoint metadata
Select Workspace.Checkpoints.Start. Add a number Attribute named Index with value 0 in Properties. Duplicate Start inside the same Checkpoints folder:
- Rename one copy
Middle, set Position=(24,4,0), and Index=1 - Rename another
Finish, set Position=(48,6,0), and Index=2
All three must remain actual SpawnLocations with Neutral=true, Enabled=true, Anchored=true and AllowTeamChangeOnTouch=false. Use distinct colors. The next script sets each player's RespawnLocation explicitly, so randomly selecting among eligible spawns is not the intended rule.
Insert a Script named Checkpoints under ServerScriptService. Replace its default contents:
-- Place: ServerScriptService.Checkpoints (Script)
local Players = game:GetService("Players")
local checkpoints = workspace:WaitForChild("Checkpoints")
local start = checkpoints:WaitForChild("Start")
assert(start:IsA("SpawnLocation"), "Checkpoints.Start must be a SpawnLocation")
local function initialize(player: Player)
player:SetAttribute("CheckpointIndex", 0)
player.RespawnLocation = start
end
Players.PlayerAdded:Connect(initialize)
for _, player in Players:GetPlayers() do initialize(player) end
for _, checkpoint in checkpoints:GetChildren() do
if not checkpoint:IsA("SpawnLocation") then continue end
local index = checkpoint:GetAttribute("Index")
assert(typeof(index) == "number", checkpoint.Name .. " needs numeric Index")
checkpoint.Touched:Connect(function(hit)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local player = Players:GetPlayerFromCharacter(character)
local humanoid = character:FindFirstChildOfClass("Humanoid")
local root = character:FindFirstChild("HumanoidRootPart")
if not player or not humanoid or humanoid.Health <= 0 then return end
if not root or not root:IsA("BasePart") then return end
if not ((root.Position - checkpoint.Position).Magnitude <= 10) then return end
local previous = player:GetAttribute("CheckpointIndex") or 0
if index <= previous then return end
player:SetAttribute("CheckpointIndex", index)
player.RespawnLocation = checkpoint
print(player.Name, "reached checkpoint", index)
end)
end
Download: checkpoints.luau
The Index is an authored ordering, not a coordinate. A higher index advances progress. Returning to Start must not erase Middle. The server checks for a live character and a nearby root before updating the Player's attribute and respawn target. This is a small teaching rule, not complete anti-cheat: competitive progression would require stronger route validation.
At startup the script sets respawn state for existing players as well as future arrivals. Setting RespawnLocation does not teleport an already-loaded character; install this script in edit mode and start a fresh Test for the initial-spawn check. Hot-loading it into a running test only changes where an existing character will respawn next. Keep exactly one copy of it. Do not also add the older team-color checkpoint technique found in some tutorials; that creates a second owner for respawn behavior.
Add a fall hazard
Insert a Folder named Hazards under Workspace. Add a Part named FallZone inside it. Set Anchored=true, CanCollide=true, CanTouch=true, Size=(140,1,60), Position=(24,-8,0). Use a bright contrasting color.
Insert ServerScriptService.Hazards as a separate Script:
-- Place: ServerScriptService.Hazards (Script)
local Players = game:GetService("Players")
local hazards = workspace:WaitForChild("Hazards")
for _, hazard in hazards:GetChildren() do
if not hazard:IsA("BasePart") then continue end
hazard.Touched:Connect(function(hit)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local player = Players:GetPlayerFromCharacter(character)
local humanoid = character:FindFirstChildOfClass("Humanoid")
if player and humanoid and humanoid.Health > 0 then
humanoid.Health = 0
end
end)
end
Download: hazards.luau
This intentionally sets Health to zero. It is a simple lethal hazard, not a damage-per-second system. Multiple body contacts do not need to subtract damage repeatedly because a dead character is rejected on subsequent callbacks. If you later design gradual damage, add a per-character cooldown and consider spawn protection explicitly.
Verify the player and character lifetimes
Run Test. Confirm you begin at Start. Reach Middle, then deliberately fall onto FallZone. After respawning, you should appear at Middle with CheckpointIndex still 1. Walk back to Start; dying again should still return you to Middle. Reach Finish and repeat.
Use the server view during testing to inspect the Player's CheckpointIndex and RespawnLocation. The old Character is replaced after death, but the Player remains. Stop and start a fresh test: progress begins at 0 because this lesson stores only session state.
Test with two clients later: one player can have Middle while the other remains at Start. A shared currentCheckpoint variable would break that independence.
Troubleshooting
Everyone spawns at a later checkpoint immediately: confirm Checkpoints runs on the server, Start is spelled correctly and no other respawn script overrides it. Index assertion fails: create a number Attribute, not a string or a child IntValue. Touch never advances: check CanTouch and whether a character can reach the checkpoint surface. A shield is visible: SpawnLocation.Duration controls the temporary visual ForceField. This sample sets Health directly, which bypasses ForceField damage protection; use TakeDamage instead when a damage mechanic should respect that protection. Changing a checkpoint during Test resets it: make permanent edits after Stop.
Exercise
Add a checkpoint between Middle and Finish. Give it an integer Index greater than Middle and less than Finish by renumbering Finish to 3. Confirm that walking backwards never reduces progress. Explain why a published game would need a migration plan before changing the meaning of checkpoint numbers already stored in a data store.
Save obby-workshop-06.rbxl before proceeding.
Sources and navigation
Verified 2026-10-03: Player.RespawnLocation, SpawnLocation, attributes, Humanoid.
Previous: Build an obby · Next: Collectibles · Related: Events, Saving