Make an efficient breath of the wild heart system

  1. I want to write a health system like in the legend of zelda: breath of the wild, with the animations and all. For now I have it so that each heart is 4 health, with a maximum of 20 hearts or 80 health.

  2. The problem is that I have no idea how to do this efficiently.

  3. I have made the images for empty heart, 1/4, 1/2 and 3/4 and full heart, but the only solution I have is that I start writing like this:

if maxhealth.Value == 12 then
 if health.Value == 1 then
for i,v in pairs(heartframe) do
if v:IsA("ImageLabel") then
v.Visible = false
end
end
heartframe.quarterheart1.Visible = true
heartframe.emptyheart1.Visible = true
heartframe.emptyheart2.Visible = true
elseif health.Value == 2 then
elseif health.Value == 3 then
end
end

ALL THE WAY TO 80…
and I need to do it for each health upgrade (same as botw: 1 heart / 4 health per upgrade)…
example:

if maxhealth.Value == 16 then
-- now copy the other version and add an extra empty heart container to each, while also adding healths 13-16
end

I’m already half way and about 3000 lines in. I got really bored of doing it, couldn’t come up with anything better and so decided to ask. There should be a way to do it without writing everything out, I hope.

So what you would do, is name the first heart, Heart1, the next Heart2, and so on. Then, when checking if you take damage, you get the highest number heart, which you can have a numbervalue set to the heartNumber when you purchase a heart. (Player purchases heart, numberValue+= 1, so once a player has 50 hearts, the value it as 50.)

Next, when a player takes damage, you could use a function, and do something like this.
(Please note this is not a very efficient way to do this.)

function dodamage(HeartNumber,Damage)

local Heart = Gui:FindFirstChild("Heart"..HeartNumber)
local Quarter = nil
if Heart.Quarter4.Visible == true then
Quarter = Heart.Quarter4
elseif Heart.Quarter3.Visible == true then
Quarter = Heart.Quarter3
elseif--and so on and so forth.
end

if Quarter == Heart.Quarter4 then
Quarter.Visible = false
Damage-=1
Quarter = Heart.Quarter3
end

if Quarter == Heart.Quarter3 and Damage >= 1 then
Quarter.Visible = false
Damage-=1
Quarter = Heart.Quarter2
end

if Quarter == Heart.Quarter2 and Damage >= 1 then
Quarter.Visible = false
Damage-=1
Quarter = Heart.Quarter1
end

if Quarter == Heart.Quarter1 and Damage >= 1 then
Quarter.Visible = false
Damage-=1

end



HeartNumber -=1

if Damage>=1 and HeartNumber ~= 0 then
dodamge(HeartNumber,Damage)
end
if HeartNumber == 0 then
--no more hearts left, game over
end

end

I would still have to write quite a bit, here for example:

 if Heart.Quarter4.Visible == true then
    Quarter = Heart.Quarter4
    elseif Heart.Quarter3.Visible == true then
    Quarter = Heart.Quarter3
    elseif--and so on and so forth.
    end
    
    if Quarter == Heart.Quarter4 then
    Quarter.Visible = false
    Damage-=1
    Quarter = Heart.Quarter3
    end
    
    if Quarter == Heart.Quarter3 and Damage >= 1 then
    Quarter.Visible = false
    Damage-=1
    Quarter = Heart.Quarter2
    end
    
    if Quarter == Heart.Quarter2 and Damage >= 1 then
    Quarter.Visible = false
    Damage-=1
    Quarter = Heart.Quarter1
    end
    
    if Quarter == Heart.Quarter1 and Damage >= 1 then
    Quarter.Visible = false
    Damage-=1
    
    end

I would have to write what it should do when taking 2 damage, 3 damage or even 10 damage
I will be adding armor into the game as well, which would make it even harder, lol.

1 Like

You could use something like this.

local function updateVisuals()
	local numberOfWholeHearts = math.floor(health.Value / 4)
	local numberOfQuarters = health.Value % 4 -- remainder when dividing health by 4
	for heartIndex = 1, math.ceil(maxHealth.Value / 4) do
		for _, imageLabel in Gui:FindFirstChild("Heart" .. heartIndex):GetChildren() do
			imageLabel.Visible = false
		end
	end
	for heartIndex = 1, numberOfWholeHearts do
		Gui:FindFirstChild("Heart" .. heartIndex).Full.Visible = true
	end
	Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 then "Empty" else "Quarter" .. numberOfQuarters).Visible = true
	for heartIndex = numberOfWholeHearts + 2, math.ceil(maxHealth.Value / 4) do
		Gui:FindFirstChild("Heart" .. heartIndex).Empty.Visible = true
	end
end
2 Likes

Hi! Thank you for the answer. I changed a bit of the code here

Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 then "Empty" else "Quarter" .. numberOfQuarters).Visible = true

to

local maxHearts = math.ceil(maxhealth.Value/4)
Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 and numberOfWholeHearts ~= maxHearts then "Empty" else "Quarter" .. numberOfQuarters).Visible = true

as it was showing an extra empty heart if my max hp was 6 hearts and my current hp was a full 6 hearts
So now it just indexes nil when trying to find the next one when it shouldn’t(???) so I’ll consider it fixed lmao.

2 Likes

I didn’t think of that problem when writing the code. To get rid of the “attempt to index nil” error, you could instead change the code to this:

local maxHearts = math.ceil(maxHealth / 4)
if maxHearts >= numberOfWholeHearts + 1 then
	Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 then "Empty" else "Quarter" .. numberOfQuarters).Visible = true
end
1 Like

okay, I’ll do that!
I still don’t have a very detailed understanding of all of this, as the +1 and +2 are messing me up, but I kinda understand

1 Like

Hey! I have a query about this part of the code

To me,

local maxHearts = math.ceil(maxHealth / 4)
if maxHearts >= numberOfWholeHearts + 1 then
	Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 then "Empty" else "Quarter" .. numberOfQuarters).Visible = true
end

looks like it should function the same as

local maxHearts = math.ceil(maxhealth.Value/4)
Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 and numberOfWholeHearts ~= maxHearts then "Empty" else "Quarter" .. numberOfQuarters).Visible = true

(before you posted I didn’t even know you could FindFirstChild(if statement))
but, how do these two differ?
I suppose it would be that one of them starts finding a child; but is unable to as it can’t find it and then tried to set the quarter’s visibility.

1 Like

Yes, when numberOfWholeHearts equals maxHearts, this

local maxHearts = math.ceil(maxhealth.Value/4)
Gui:FindFirstChild("Heart" .. numberOfWholeHearts + 1):FindFirstChild(if numberOfQuarters == 0 and numberOfWholeHearts ~= maxHearts then "Empty" else "Quarter" .. numberOfQuarters).Visible = true

tries to find a child that either doesn’t exist, or if it does exist, it shouldn’t be made visible.

It tries to find a heart frame which only exists if you have extra heart frames (more heart frames than maxHearts). You mentioned that an extra empty heart was shown with my original code so apparently you do have extra heart frames.

If you do have extra HeartFrames, and numberOfWholeHearts equals maxHearts (in which case there should only be full hearts), then it will try to find an ImageLabel called Quarter0 that is a child of the extra heart frame (And I assume such a child doesn’t exist).

The if statement in the function argument parentheses is not a regular if statement. It is a ternary operator. While a regular if statement determines whether a block of code will run based on a condition, a ternary operator chooses one of two values based on a condition.

So, in this case, the ternary operator does not affect whether the code looks for a child of the heart frame. It only affects the name with which it looks for a child.

When the original line of code is put into a regular if statement (my suggestion), the line that makes the first non-full (empty or partially full) heart visible is only run if there should be a non-full heart.

Based on the constraints of health and max health (both are integers and health cannot be greater than max health), the condition you added to the ternary operator is practically equivalent to my if statement condition in this case. It’s just in the wrong place because it should be in a regular if statement.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.