§ How to · with Picoo
How to make a health bar in Roblox
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
A health bar is a GUI problem pretending to be a combat problem — and the mistakes are all in how it is attached and updated.
Over-the-head bars need a BillboardGui with a distance limit; the player's own bar needs Roblox's built-in one out of the way first.
Over-the-head bars
BillboardGui adorned to the head, with MaxDistance so a hundred bars do not render across the map.
Event-driven updates
HealthChanged instead of a loop polling health every frame.
Default GUI removed
Roblox's built-in health display disabled properly, with the retry it needs on join.
MaxHealth aware
Fill is a ratio, so buffs that raise MaxHealth do not overflow the bar.
Damage feedback
A trailing white bar and a colour shift at low health, so a hit reads at a glance.
Death handling
Bars hide on death and reattach on respawn instead of leaving orphans.
Files Picoo ships for this prompt
2 files · 145 lines · ~25s · 1 credit
HealthBarService
Bar creation per character, attach/reattach on respawn, cleanup.
85 lines
HealthBarClient
Local player bar, CoreGui removal, damage flash.
60 lines
Sample output: StarterPlayerScripts.HealthBarClient
-- Hiding Roblox's own health display can FAIL on join: SetCore errors if the
-- CoreGui module has not registered yet. Fire-and-forget leaves some players
-- looking at two health bars, so retry until it takes.
task.spawn(function()
for _ = 1, 10 do
local ok = pcall(function()
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Health, false)
end)
if ok then break end
task.wait(0.2)
end
end)
-- Over-the-head bar: adorn to the HEAD, cap the distance. Without MaxDistance,
-- every bar in the place renders at every range and a busy server turns into a
-- wall of floating rectangles.
local function attach(char: Model)
local hum = char:WaitForChild("Humanoid") :: Humanoid
local bb = TEMPLATE:Clone()
bb.Adornee = char:WaitForChild("Head")
bb.MaxDistance = 60
bb.Parent = char
-- Ratio, not raw health: a buff that raises MaxHealth must not overflow,
-- and HealthChanged fires only on change instead of polling every frame.
local function redraw()
local ratio = hum.MaxHealth > 0 and hum.Health / hum.MaxHealth or 0
bb.Fill.Size = UDim2.fromScale(math.clamp(ratio, 0, 1), 1)
bb.Fill.BackgroundColor3 = ratio < 0.3 and LOW or FULL
end
hum.HealthChanged:Connect(redraw)
hum:GetPropertyChangedSignal("MaxHealth"):Connect(redraw)
redraw()
endBuilding a health bar in Roblox
Health bars are simple enough that the interesting part is entirely in the details, and each detail has a specific failure attached.
Attachment first. An over-the-head bar is a BillboardGui adorned to the character's Head, which makes it face the camera regardless of orientation. The property that gets forgotten is MaxDistance, and forgetting it is expensive: without a cap, every bar in the place renders at every range, so a server with thirty characters draws thirty GUIs that are two pixels tall and completely unreadable. Sixty studs is a sane default and costs you nothing visually.
Updating is next. It is tempting to read Health in a loop, and it works, and it burns frames doing nothing most of the time. HealthChanged fires exactly when the value moves. Connect MaxHealth's changed signal too, because buffs and difficulty scaling both change it — and if your fill is sized from raw health rather than the ratio, a MaxHealth of 200 gives you a scale of 2 and a bar that runs off its own frame.
The player's own bar has one extra step: getting Roblox's out of the way. SetCoreGuiEnabled with CoreGuiType.Health does it, but the call can throw when it runs before the CoreGui modules have registered, which is exactly when a LocalScript first runs. Called once and forgotten, it silently fails for some players and they see two health bars. A short retry loop makes it reliable.
Lastly, respawns. A character is destroyed and rebuilt on death, so a bar attached to the old one is an orphan. Hook CharacterAdded and reattach, and let the old GUI die with its character rather than trying to move it.
See more on the Luau generator, the game builder, or browse the full blog.
Frequently asked
How do I hide the default Roblox health bar?+
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Health, false) from a LocalScript. It can throw if called before the CoreGui modules have registered, which is common right at join — so wrap it and retry a few times rather than calling it once and assuming it worked.
Should the bar be a BillboardGui or a SurfaceGui?+
BillboardGui for anything over a head: it always faces the camera and can scale with distance. SurfaceGui is for a bar painted onto a physical surface, like a boss health display on a wall, where you want it to obey the surface's orientation.
Why do my health bars tank performance?+
Almost always missing MaxDistance. Without it every bar on the server renders regardless of range, so a busy place draws dozens of GUIs the player cannot even read. Sixty studs is a reasonable default for enemies.
Why does my bar overflow when a player gets a buff?+
The fill is sized from raw health rather than health divided by MaxHealth. Raising MaxHealth to 200 then sets a scale of 2. Always size from the ratio, clamp it, and redraw when MaxHealth changes as well as Health.
Polling or HealthChanged?+
HealthChanged. A loop reading Health every frame does the same work whether or not anything happened, and with many characters that adds up for no benefit. The event fires exactly when the value moves.
Related Picoo prompts
damage system in Roblox
Damage is where hitbox accuracy, server authority and spawn protection all meet, and every one of them has a common way of going wrong.
Roblox boss fight
7 files · 420 lines · 2m 10s · 1 credit. Three phases out of the box, easily extended.
Roblox respawn system
4 files · 130 lines · 38 seconds · 1 credit. Replaces Roblox default respawn with a controlled flow.