Obby Checkpoint System
A classic obby checkpoint flow. Touching a checkpoint sets the player's spawn point — and stays set across deaths and re-joins. Each player has their own checkpoint independently.
“Build me an obby checkpoint system. Each checkpoint is a part. When a player touches one, they save it as their spawn. When they die, they respawn at their last checkpoint. Each player should have their own progression.”
Paste this — or any variation — into StudByStud and you’ll get the code below in seconds.
How it works
- Each checkpoint part is tagged 'Checkpoint' in Studio. Order matters only if you want to prevent skipping — the basic version awards any checkpoint a player touches.
- When the player touches a checkpoint, the server stores it in Player.RespawnLocation, which Roblox uses automatically when the character respawns.
- We also save the checkpoint number as a player attribute so other systems (UI, leaderboard) can read it cheaply.
- On CharacterAdded we re-apply the saved spawn so a freshly-respawned player still uses their last checkpoint, not the default lobby spawn.
- To prevent skipping, the version below also tracks the checkpoint number and refuses to assign a checkpoint that's lower than the player's current one.
The generated code
One file. Copy it into Roblox Studio at the path shown.
--!strict
-- CheckpointService.lua
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local CHECKPOINT_TAG = "Checkpoint"
-- Each tagged part should have an IntValue named "Order" (1, 2, 3, ...) so we
-- can reject players touching a lower checkpoint than they've already reached.
local function getOrder(part: Instance): number
local v = part:FindFirstChild("Order")
if v and v:IsA("IntValue") then
return v.Value
end
return 0
end
local function onCheckpointTouched(part: BasePart, hit: BasePart)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local player = Players:GetPlayerFromCharacter(character)
if not player then return end
local order = getOrder(part)
local current = player:GetAttribute("CheckpointOrder") :: number? or 0
if order <= current then return end
player:SetAttribute("CheckpointOrder", order)
player.RespawnLocation = part :: any
end
local function setupCheckpoint(part: Instance)
if not part:IsA("BasePart") then return end
part.Touched:Connect(function(hit)
onCheckpointTouched(part, hit)
end)
end
for _, part in CollectionService:GetTagged(CHECKPOINT_TAG) do
task.spawn(setupCheckpoint, part)
end
CollectionService:GetInstanceAddedSignal(CHECKPOINT_TAG):Connect(setupCheckpoint)
-- Re-apply saved spawn whenever the character respawns. Roblox uses
-- player.RespawnLocation automatically, but only if it's still set when the
-- next character loads — clearing on death would break the flow.
local function onCharacterAdded(player: Player, character: Model)
-- Wait one frame so the spawn happens, then we don't actually need to do
-- anything else: Roblox already used RespawnLocation. This hook exists so
-- you can extend it (play a sound, fire a checkpoint UI, etc.).
task.wait()
local order = player:GetAttribute("CheckpointOrder") :: number? or 0
if order > 0 then
print(`{player.Name} respawned at checkpoint {order}`)
end
end
local function onPlayerAdded(player: Player)
player:SetAttribute("CheckpointOrder", 0)
player.CharacterAdded:Connect(function(char) onCharacterAdded(player, char) end)
if player.Character then
onCharacterAdded(player, player.Character)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
Wires up every tagged checkpoint and binds it to per-player spawn state. Prevents skipping by tracking the highest checkpoint each player has reached.
What you’ll learn
Want this built for your game?
Sign up, paste the prompt above, and StudByStud will generate it — and sync it straight into Roblox Studio. Free tier includes 1M Flash tokens per month.
