Skip to main content

Stage 6: Hidden Hazard Field + health bars

Course progressStage 6 of 10
~55 min
Before you start

Finish Stage 5. The turret damages you and your fireball has a cooldown. Your training dummy from Stage 3 should still be in the world — it gets the first health bar.

Build

a field of hidden hazards, the Stage 7 checkpoint, and a floating health bar for every fighter

Learn

how a BillboardGui floats UI above a part, and how Humanoid.HealthChanged keeps the bar in sync with real health

Ship

green health bars over the player and the dummy that shrink in real time as they take damage

Teacher demo

90-second demo:

  • Press Play. A green bar floats over your head and over the dummy.
  • Swing the sword or throw a fireball at the dummy — its bar shrinks with every hit, in real time.
  • Walk into the turret's shots — your own bar shrinks too.
  • Explain: "Numbers in the corner are fine for XP, but in a fight you need to see health on the fighter. That's what a health bar does — it puts the Humanoid's health right above its head."

The big idea

You've had health since Stage 1, but you couldn't see it on anyone. In a fight, that's a problem — you need to know, at a glance, who's almost down. Today every fighter gets a health bar floating above their head.

The tool is a BillboardGui — a piece of UI that hangs in the 3D world above a part and always faces the camera. Inside it you put two boxes: a dark one for the empty track, and a green one for the fill. To make the green box show the right amount, you set its width to the fraction Health / MaxHealth. Full health, full width; half health, half width.

The clever part is keeping it honest. A Humanoid fires an event called HealthChanged every single time its health moves — from a sword, a fireball, a turret, anything. If you resize the green box every time that event fires, the bar can never lie. You write the bar once as a reusable function and hand it to every Humanoid in the game.

New words
BillboardGui
UI that floats in the 3D world above a part and always faces the camera
Frame
a plain rectangle of UI; we use two — a dark track and a green fill
UDim2
how Roblox sizes UI; the Scale part (0 to 1) is a fraction of the parent, perfect for a bar
MaxHealth
a Humanoid's full health; divide Health by it to get the fraction the bar should show
HealthChanged
a Humanoid event that fires whenever its Health changes — the trigger to redraw the bar
Adornee
the part a BillboardGui attaches to; set it to the Head so the bar floats there

Build it

Step 1 — Build the hidden hazard field

A wide floor dotted with hazards. Some are obvious; make a couple nearly invisible so players have to learn the safe route.

A hidden hazard field with floating health bars over fighters in a Roblox combat obby

Build this part

HazardField

Block
Open recipe
Size
24 × 1 × 24
Color
Dark stone grey
Material
Concrete
Anchored
✓ Yes
Place
A wide platform forward from the Stage 6 violet pad

Add several hazards on top, named Hazard_1, Hazard_2, and so on:

Build this part

Hazard_1

Block
Open recipe
Size
4 × 1 × 4
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
Somewhere on the HazardField

Make this one obvious — bright and solid.

Build this part

Hazard_2

Block
Open recipe
Size
4 × 1 × 4
Color
Dark stone grey
Material
Concrete
Anchored
✓ Yes
Place
Elsewhere on the HazardField

Camouflage this one — same color as the floor, Transparency 0.3 — so players must learn it.

Add Hazard_3 and Hazard_4 to taste. Then make them dangerous — in ServerScriptService, insert a Script named Hazards:

local Players = game:GetService("Players")

for _, part in ipairs(workspace:GetDescendants()) do
if part.Name:sub(1, 7) == "Hazard_" then
part.Touched:Connect(function(otherPart)
local humanoid = otherPart.Parent:FindFirstChildOfClass("Humanoid")
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if humanoid and player then
humanoid.Health = 0
end
end)
end
end

Press Play and find the safe path across. The camouflaged hazard should catch you the first time.

Step 2 — Wire the Stage 7 checkpoint

Build this part

SpawnLocation (Stage 7 — across the field)

Block
Open recipe
Size
6 × 1 × 6
Color
Bright orange
Material
Plastic
Anchored
✓ Yes
Place
On the far edge of HazardField

Add StageNumber = 7. Check AllowTeamChangeOnTouch. Uncheck Neutral. Set TeamColor to Bright orange.

Same gesture: StageNumber = 7 attribute and a Stage 7 Team (Bright orange, AutoAssignable unchecked).

Step 3 — Build a health bar for every fighter

In ServerScriptService, insert a Script named HealthBars. You'll build it in three passes.

Pass 1 — A function that builds one bar

local function addHealthBar(humanoid)
local character = humanoid.Parent
local head = character:FindFirstChild("Head")
if not head then return end
if head:FindFirstChild("HealthBar") then return end

local gui = Instance.new("BillboardGui")
gui.Name = "HealthBar"
gui.Size = UDim2.new(0, 100, 0, 12)
gui.StudsOffset = Vector3.new(0, 2.5, 0)
gui.AlwaysOnTop = true
gui.Adornee = head
gui.Parent = head

local track = Instance.new("Frame")
track.Size = UDim2.new(1, 0, 1, 0)
track.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
track.BorderSizePixel = 0
track.Parent = gui

local fill = Instance.new("Frame")
fill.Name = "Fill"
fill.Size = UDim2.new(1, 0, 1, 0)
fill.BackgroundColor3 = Color3.fromRGB(70, 200, 60)
fill.BorderSizePixel = 0
fill.Parent = track
end

-- Try it on the dummy:
local dummy = workspace:FindFirstChild("Dummy")
if dummy then
addHealthBar(dummy:FindFirstChildOfClass("Humanoid"))
end

Press Play. A green bar floats over the dummy's head. Hit the dummy — the bar doesn't shrink yet, because nothing redraws it. That's Pass 2. Stop.

Pass 2 — Keep the bar in sync with health

Inside addHealthBar, before the function ends, add an update routine wired to HealthChanged:

local function update()
local fraction = humanoid.Health / humanoid.MaxHealth
fill.Size = UDim2.new(fraction, 0, 1, 0)
end

humanoid.HealthChanged:Connect(update)
update()

Press Play. Swing or fireball the dummy — its green bar shrinks with each hit, instantly. The update() at the end draws it correctly the moment it's created. Stop.

Pass 3 — Give every fighter a bar

The dummy has a bar; now give one to players and to any enemy that spawns later. Replace the "Try it on the dummy" block at the bottom with:

local Players = game:GetService("Players")

-- Players, now and in the future.
local function onCharacter(character)
local humanoid = character:WaitForChild("Humanoid")
addHealthBar(humanoid)
end

Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(onCharacter)
if player.Character then
onCharacter(player.Character)
end
end)

-- Any Humanoid already in the world (like the dummy).
for _, descendant in ipairs(workspace:GetDescendants()) do
if descendant:IsA("Humanoid") then
addHealthBar(descendant)
end
end

-- Any Humanoid that appears later (enemies you spawn in Stage 7+).
workspace.DescendantAdded:Connect(function(descendant)
if descendant:IsA("Humanoid") then
task.wait(0.1)
addHealthBar(descendant)
end
end)

Press Play. A bar floats over your head and the dummy's. Take a turret shot and watch your own bar drop. Every fighter you add from now on gets a bar automatically.

Understand it

A BillboardGui lives in the 3D world but holds 2D UI. Its Adornee is the part it floats over (the Head), and StudsOffset lifts it above the hair. AlwaysOnTop means walls don't hide it. Inside, ordinary UI Frames behave like they would on a screen.

The fraction trick is why the bar is just two boxes. The fill Frame's size is UDim2.new(fraction, 0, 1, 0) — the first number, the Scale, is a fraction of the track's width. Health 100 of 100 is 1.0 (full width); health 25 of 100 is 0.25 (a quarter). You never compute pixels; you let Scale do it.

The HealthChanged event is what makes the bar trustworthy. Instead of checking health every frame (wasteful) or only once (wrong), you redraw exactly when health changes. Sword, fireball, turret, hazard — all of them move health, all of them fire HealthChanged, and your one update function catches them all.

Writing it as one reusable function is the real win. addHealthBar doesn't care if it's given a player, the dummy, or a boss — anything with a Humanoid and a Head gets a working bar. That's why the same function serves the enemies in Stage 7, the wave in Stage 9, and the boss in Stage 10 without a single change.

Script anatomy

One reusable health bar, driven by HealthChanged

The function builds a dark track with a green fill above the head, then resizes the fill to the health fraction every time health changes.

local function addHealthBar(humanoid)
local head = humanoid.Parent:FindFirstChild("Head")
if not head or head:FindFirstChild("HealthBar") then return end

local gui = Instance.new("BillboardGui")
gui.Name = "HealthBar"
gui.Size = UDim2.new(0, 100, 0, 12)
gui.StudsOffset = Vector3.new(0, 2.5, 0)
gui.AlwaysOnTop = true
gui.Adornee = head
gui.Parent = head

local track = Instance.new("Frame")
track.Size = UDim2.new(1, 0, 1, 0)
track.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
track.Parent = gui

local fill = Instance.new("Frame")
fill.BackgroundColor3 = Color3.fromRGB(70, 200, 60)
fill.Parent = track

local function update()
fill.Size = UDim2.new(humanoid.Health / humanoid.MaxHealth, 0, 1, 0)
end
humanoid.HealthChanged:Connect(update)
update()
end
  1. Line 2Bail out early.

    No Head, or a bar already exists? Stop. This keeps the function safe to call on the same fighter twice.

  2. Lines 4–11Float the UI above the head.

    A BillboardGui adorned to the Head, lifted 2.5 studs, always on top. This is the container the bar lives in.

  3. Lines 13–21Two boxes: track and fill.

    A dark track at full size, and a green fill on top of it. The fill's width is what changes to show health.

  4. Lines 23–27Resize on every health change.

    update() sets the fill's Scale width to Health/MaxHealth. Wiring it to HealthChanged means the bar redraws the instant anything deals damage.

Try this

Learning beat

Try this

Three short experiments. Predict before you run, then test your guess.

Predict first

Predict what the bar looks like at 30 health out of 100. What number goes in the fill's Scale width? Hit the dummy down to about there and check.

Compare

Set the BillboardGui's AlwaysOnTop to false, press Play, and walk behind a wall from the dummy. Compare what you see versus true. When would you actually want health bars to hide behind walls?

Connect

Right now the bar is always green. In a real game it turns red as health drops. The Stretch challenge does this — but first: which value would you check inside update to decide the color?

Test your stage

  • Press ▶ Play. A green bar floats over your head and the dummy's.
  • Sword or fireball the dummy — its bar shrinks with each hit, in real time.
  • Walk into a turret shot or hazard — your own bar drops.
  • Cross the hazard field; the camouflaged hazard catches you at least once.
  • Reach the Stage 7 orange pad.
  • Design check. Is the bar readable from across the field? If it's too small or too low, bump the Size or StudsOffset. A health bar you can't read is no better than none.

If it breaks

  • No bar appears. The fighter needs a part named Head. Players and Rig Builder dummies have one. Also confirm the BillboardGui's Adornee and Parent are both the Head.
  • The bar shows but never moves. You skipped the HealthChanged connection, or the update() call. Both are in Pass 2.
  • The bar is full even at low health. Your fraction math is flipped or using the wrong values. It must be humanoid.Health / humanoid.MaxHealth.
  • The player's bar never appears. CharacterAdded may have already fired before your connection. The if player.Character then onCharacter(player.Character) end line covers that case — make sure it's there.
  • Two bars stack on one fighter. The if head:FindFirstChild("HealthBar") then return end guard prevents duplicates — confirm the BillboardGui is named exactly HealthBar.
Coach notes

This is the first heavy UI stage. The fraction-to-Scale idea is the concept to nail: a bar is just a box whose width is a fraction. Draw it on the board — full, half, quarter — before the code.

  • The DescendantAdded block at the end is forward-looking: it's what auto-bars the enemies campers build next stage. If it errors, the small task.wait(0.1) gives the new Humanoid a frame to gain its Head.
  • Campers love decorating bars. Timebox it — the goal is a working, readable bar, not a perfect one.
  • Total time: 55 minutes. Hazard field + script 15, checkpoint 5, three-pass health bar 35.