Table of Contents
Understanding Win Conditions in an Obby
Win conditions define when a player has officially completed your obby. They turn a collection of jumps and checkpoints into a real game with a clear goal and a satisfying ending. In this chapter you will focus only on how to detect that a player has finished, how to respond when they do, and how to make that ending feel rewarding.
What a Win Condition Is in an Obby
In a simple obby, a win condition is usually reached when a player touches or reaches a specific final object or area. This could be a part labeled “Finish,” a glowing pad, a trophy, or a small island at the end of the course. The important point is that the game can:
- Detect that the player has reached the end.
- React to that event in some way.
You are not designing how the levels are built or how checkpoints work here. Instead, you create the logic that says, “If the player arrives here, they win.”
A basic win condition always needs three things. There must be a clear end location. There must be a way to detect the player at that location. There must be some feedback or reward when they arrive.
Creating a Finish Part
The most common way to detect a win is to use a finish part at the end of the course. This is just a normal Part that you place where you want players to complete the obby. You can style it however you like, for example a green square, a glowing platform, or a trophy base. The visual style belongs to level design. Here you only care about its role in your win logic.
To use a finish part as a win trigger you give it a name, such as FinishPart or WinPad, and place a Script inside it. The script listens for when a player touches the part and then runs your win logic. The behavior of that script is where your win condition actually lives.
Detecting the Win with Touched Events
The usual way to detect when the player has finished is to connect to the part’s Touched event. This event fires whenever something comes into contact with the part. You must then check if that “something” belongs to a player’s character.
A minimal example looks like this:
local finishPart = script.Parent
local function onTouched(hit)
local character = hit.Parent
if not character then
return
end
local humanoid = character:FindFirstChild("Humanoid")
if not humanoid then
return
end
local player = game.Players:GetPlayerFromCharacter(character)
if not player then
return
end
print(player.Name .. " has finished the obby!")
end
finishPart.Touched:Connect(onTouched)
This script does not fully implement rewards yet, but it covers the important detection steps. The Touched event is fired many times and by many objects. That is why you must carefully filter the hit object until you find the player.
Important rule: Always verify that the Touched event came from a player character before applying win logic. Otherwise you might trigger wins from loose parts, tools, or debris.
Once detection works, you can safely add more logic inside the onTouched function to handle rewards, messages, and transitions.
Preventing Multiple Win Triggers
Without extra checks, a player could trigger the win logic several times by jumping on and off the finish part. This can become a problem if your win logic gives rewards or modifies stats. You often want each player to win only once per run or only once overall.
There are two main ways to prevent multiple triggers. You can temporarily disable the part, or you can track which players have already finished.
A simple method is to make the part non-collidable and transparent after the first touch. This works best if you do not need the part again:
local finishPart = script.Parent
local finishedPlayers = {}
local function onTouched(hit)
local character = hit.Parent
if not character then
return
end
local humanoid = character:FindFirstChild("Humanoid")
if not humanoid then
return
end
local player = game.Players:GetPlayerFromCharacter(character)
if not player then
return
end
if finishedPlayers[player.UserId] then
return
end
finishedPlayers[player.UserId] = true
print(player.Name .. " has finished!")
finishPart.CanTouch = false
end
finishPart.Touched:Connect(onTouched)
Here you store a record using player.UserId so the same player does not trigger the win code more than once. Disabling CanTouch ensures the Touched event stops firing for that part entirely. You can skip disabling when you want multiple players to win at different times.
Key statement: Use a table keyed by player.UserId to remember which players already finished. This is a simple and reliable pattern to avoid duplicate rewards.
Giving Immediate Feedback
A win condition feels satisfying when the game clearly responds. As soon as you detect that the player has finished, you should provide visible or audible feedback.
You can start with simple messages printed in the Output window while testing, but in an actual game you will want something the player can see and hear. You might display a message on the screen, play a victory sound, change the player’s appearance, or teleport them to a small “Winner” area.
For a simple visual confirmation you can use a Message or better, a ScreenGui in combination with text labels. From the win script you can set a value on the player that your UI script listens for, or you can fire an event to the client. The exact UI implementation belongs to user interface chapters, but the important part here is deciding what happens when onTouched runs.
A simple example of adding a sound from the win script looks like this:
local finishPart = script.Parent
local winSound = finishPart:FindFirstChild("WinSound")
local function onTouched(hit)
local character = hit.Parent
if not character then
return
end
local humanoid = character:FindFirstChild("Humanoid")
if not humanoid then
return
end
local player = game.Players:GetPlayerFromCharacter(character)
if not player then
return
end
if winSound then
winSound:Play()
end
end
finishPart.Touched:Connect(onTouched)You can replace or extend this with more complex reactions later. The important concept is that the finish script is where you trigger all your end-of-obby effects.
Teleporting Winners to a Victory Area
Many obbies include a special winner zone. This area might contain fun items, special tools, or simply a platform that shows everyone who has completed the course. To use a victory area as part of your win condition you teleport players there immediately after detecting a win.
You can do this by placing a part that acts as the destination, for example WinnerSpawn, and then setting the player’s character position when they win. A typical example looks like this:
local finishPart = script.Parent
local winnerSpawn = workspace:WaitForChild("WinnerSpawn")
local function moveToWinnerArea(character)
local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then
return
end
humanoidRootPart.CFrame = winnerSpawn.CFrame + Vector3.new(0, 3, 0)
end
local function onTouched(hit)
local character = hit.Parent
if not character then
return
end
local humanoid = character:FindFirstChild("Humanoid")
if not humanoid then
return
end
local player = game.Players:GetPlayerFromCharacter(character)
if not player then
return
end
moveToWinnerArea(character)
end
finishPart.Touched:Connect(onTouched)
By slightly offsetting the CFrame you reduce the risk of the player spawning inside the destination part. You can later add extra logic in moveToWinnerArea such as giving a badge or showing a UI.
Simple Win Conditions with Checkpoints
If your obby uses checkpoints, you can combine them with your win condition. One simple pattern is to treat the last checkpoint as the “finish checkpoint.” When the player touches that checkpoint part, you activate the win logic instead of just saving progress.
The setup is similar to the finish part example, but instead of using a dedicated FinishPart, you mark the final checkpoint with a special name or a boolean value inside it. Your script checks that property and only runs win code if the checkpoint is the final one.
For example, your last checkpoint part could contain a BoolValue named IsFinal. The script that handles checkpoints can test for this and, in that case, call the win code instead of normal checkpoint code. The actual checkpoint system is handled elsewhere in this course, so here you only need to understand that the win condition can be attached to any part that the player must reach.
Basic Time and Win Tracking
Even in a simple obby, you may want to store basic information when a player wins. Two useful values are completion count and completion time. Both can be stored in leaderstats or other stat systems, but the main idea here is to capture them at the moment of victory.
If you store the time when the player started the obby in a value, for example player.StartTime, then at completion you can compute:
$$
\text{CompletionTime} = \text{CurrentTime} - \text{StartTime}
$$
On Roblox, if you used tick() or os.clock() to record a start value, you repeat the same function in your win script and subtract the stored value. This difference gives you the duration in seconds. You can then display or store this number for later use.
Key formula: If a player starts at time $t_{\text{start}}$ and finishes at time $t_{\text{end}}$, then completion time is $t_{\text{end}} - t_{\text{start}}$.
How and where you display this result belongs to user interface and stats chapters, but the win script is the place where you compute it, because only the win script knows exactly when the run is over.
Designing Fair and Clear Win Conditions
A good win condition is not just technically correct. It should be fair, easy to understand, and hard to trigger by accident. If players are confused about whether they have finished or not, the game feels incomplete.
To keep your win condition clear, always give the end of your obby a distinctive look. Even with basic parts, you can change the color, material, or size to signal “This is the finish.” Combine the visual design with the feedback logic you created, such as sounds or UI messages, so the completion feels obvious.
Avoid placing the finish part too close to dangerous areas or moving obstacles. Otherwise, players might touch it mid jump without realizing they have already finished, or they might repeatedly die right after technically winning. It is usually better to give a short safe zone around the finish so that reaching it feels like a true end.
As you expand your game design knowledge, you can experiment with more complex win logic, such as collecting all coins before finishing or beating a timer, but all those variations build on the basic pattern you learned here: detect arrival, check it belongs to a player, prevent repeats, and respond with clear feedback.