I was curious on how I would make a health system similar to Spiral Knights
when the player takes damage, it transitions to a Half State or an Empty State depending on how big the damage the player has taken. So let’s say the player takes 1 damage point, then an entire pip (starting from the right) will be in a complete empty State. If the player takes a damage in form of a decimal,such as .5 then it’ll become a half State, if the player takes the same damage point of .5, then the pip in the half state will become empty, but if the pip is in a half state and the player takes 1 damage, then i’ll make the half state pip empty and make the pip before it half state. The Pips are the health bars you see in the image above
I’ve tried making this myself, but it didn’t really work out, sometimes the pips won’t even be cloned to the player health, and most of the time, when the player takes damage, the pips just vanish
The ClipsDescendants property can be used to create a sort of mask to hide parts of UI. Adjust a UI element with this property with another layer underneath to simulate a health bar this way.
I’d start by implementing with a pip class for each individual pip and a health bar class which handles the state of the pips when given the current health data.
Something like this:
local Pip = {}
Pip.__index == Pip
function Pip.new()
# Create a new pip object with frames, images etc...
end
function Pip:SetState(newState: "Full" | "Half" | "Empty")
# Implement the visual changes to the individual pip based on the state
end
local HealthBar = {}
HealthBar.__index = HealthBar
function HealthBar.new(maxHealth: number, currentHealth: number?)
local self = {}
self.MaxHealth = maxHealth
# Implement / reference visual gui stuff
# Assuming maxHealth (number of pips) is a whole number
assert(math.ceil(maxHealth) == math.floor(maxHealth))
# - 0.5 damage takes a pip from full to half state
# - 1 damage takes a pip from full to empty
self.Pips = {}
for i = 1, maxHealth do
local newPip = Pip.new()
self.Pips[i] = newPip
end
setmetatable(self, HealthBar)
self:SetCurrentHealth(currentHealth or maxHealth)
return self
end
function HealthBar:SetCurrentHealth(health: number)
local fullEnd = math.floor(health) # The index of the last full pip
local isHalfFull = (health % 1 == 0.5) # true if there needs to be a half full pip
local emptyStart = isHalfFull and fullEnd + 2 or fullEnd + 1 # Index of the first empty pip
for i = 1, fullEnd do
self.Pips[i]:SetState("Full")
end
if isHalfFull then
self.Pips[fullEnd + 1]:SetState("Half")
end
for i = emptyStart, self.MaxHealth do
self.Pips[i]:SetState("Empty")
end
end
if I may ask, It doesn’t seem to be cloning any “Pip” based on the character/Player’s maxHealth, I edited it but it still doesn’t seem to be doing anything?
local Pip = {}
local PipTemplate = script.Parent.Pip
local maxHealth = game:GetService("Players").LocalPlayer.Character:WaitForChild("Humanoid").MaxHealth
local currentHealth = game:GetService("Players").LocalPlayer.Character:WaitForChild("Humanoid").Health
Pip.__index = Pip
function Pip.new()
local pip = PipTemplate:Clone()
pip.Parent = script.Parent
return setmetatable(pip, Pip)
end
function Pip:SetState(newState: "Full" | "Half" | "Empty")
if newState == "Full" then
self.Image = "rbxassetid://12404917036"
end
if newState == "Half" then
self.Image = "rbxassetid://12409136414"
end
if newState == "Empty" then
self.Image = "rbxassetid://12409173552"
end
end
local HealthBar = {}
HealthBar.__index = HealthBar
function HealthBar.new()
local self = {}
self.MaxHealth = maxHealth
assert(math.ceil(maxHealth) == math.floor(maxHealth))
self.Pips = {}
for i = 1, maxHealth do
self.Pips[i] = Pip.new()
end
setmetatable(self, HealthBar)
self:SetCurrentHealth(currentHealth)
return self
end
function HealthBar:SetCurrentHealth(health: number)
local fullEnd = math.floor(health)
local isHalfFull = (health % 1 == 0.5)
local emptyStart = isHalfFull and fullEnd + 2 or fullEnd + 1
for i = 1, fullEnd do
self.Pips[i]:SetState("Full")
end
if isHalfFull then
self.Pips[fullEnd + 1]:SetState("Half")
end
for i = emptyStart, self.MaxHealth do
self.Pips[i]:SetState("Empty")
end
end
the Pip constructor trying to overwrite the metatable of a roblox class which you can’t do
you need to create an instance of the HealthBar class
connect health changed event to update the health bar instance
For (1):
function Pip.new()
local self = {}
local imageLabel = PipTemplate:Clone()
imageLabel .Parent = script.Parent
self.ImageLabel = imageLabel
return setmetatable(self, Pip)
end
function Pip:SetState(newState: "Full" | "Half" | "Empty")
if newState == "Full" then
# Change .Label to .ImageLabel.Label
self.ImageLabel.Image = "rbxassetid://12404917036"
end
...
end
For (2) at the bottom of your code or in another module / script:
local healthBar = HealthBar.new(humanoid.MaxHealth, humanoid.Health)
humanoid.HealthChanged:Connect(function(health)
healthBar:SetCurrentHealth(health)
end)
Edit: I think more needs doing when cloning the pips too in order to put them in the correct positions. Depends if you have a UIListLayout or something going on already though.