Health Bar Damage Stacking

I am trying to make a Custom Health Bar with a damage stacking effect. What I mean by that is making an effect that stacks damage the player takes, by making a white bar represent the accumulated damage which goes away after 2 seconds of not taking damage.
I accomplish this using TweenService.

I have tried looking on the Dev Forum for posts similar to mine and have struggled replicating the same effect. My main issue is both executing the last tween and not to have my previous tween override the last one.
I have tried using tick() and os.time() but didn’t manage to do it.
I have also tried adding delay to my tween but it just didn’t start nor did the other damageFrame tween start.

I will attach a video of what it looks like now.

This is the script:

-- Variables
local parentFrame = script.Parent
local screenGui = parentFrame.Parent
local damageFrame = parentFrame.TakingDamage
local healthFrame = parentFrame.Health
local healthText = parentFrame.HealthText

local players = game:GetService("Players")
local ts = game:GetService("TweenService")
local plr = players.LocalPlayer

local connection

local healthTweenInfo = TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut)

-- Script
-- MAIN FUNCTION
local function updateHealthBar(Humanoid, newHealth)
	
	local currentHealth = newHealth or Humanoid.Health
	local maximumHealth = Humanoid.MaxHealth
	local percentage = currentHealth / maximumHealth -- Calculates percentage (in decimal number between 0 and 1)

	healthText.Text = math.round(currentHealth).."/"..maximumHealth -- Sets text for health
	
	local tween = ts:Create(healthFrame, healthTweenInfo, {Position = UDim2.new(-1 + percentage, 0, 0, 0)}) -- Creates health tween for the green bar
	tween:Play()
	
	tween = ts:Create(damageFrame, healthTweenInfo, {Position = UDim2.new(1, 0, 0, 0)}) -- Moves the TakingDamage frame bar to default position
	tween:Play()
	
	tween = ts:Create(damageFrame, healthTweenInfo, {Size = UDim2.new(-1 + percentage, 0, 1, 0)}) -- Creates damage tween for the white bar
	tween:Play()
	
-- THIS IS THE MAIN TWEEN I'M REFERRING TO
	task.wait(2)
	tween = ts:Create(damageFrame, TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), {Position = UDim2.new(0, 0, 0, 0)}) -- Supposed to move the damage bar out of the frame in order to be invisible
	tween:Play()
end


local function healthChangedConnection(Character)
	if connection == nil then
		local Humanoid = Character:WaitForChild("Humanoid")

		connection = Humanoid.HealthChanged:Connect(function(newHealth) -- Connect function to event
			updateHealthBar(Humanoid, newHealth) 	
		end)
	end
end

plr.CharacterAdded:Connect(healthChangedConnection)

plr.CharacterRemoving:Connect(function() -- Removes connection to update healthbar once character gets removed
	if connection ~= nil then
		connection:Disconnect() 
		connection = nil
	end
end)

if plr.Character then
	healthChangedConnection(plr.Character) -- Automatically update healthbar when player spawns
end

If you have any idea on how to fix any issues or have a completely different approach, please reach out. Thanks in advance.

7 Likes

i have a system like this that works perfectly good, sadly my computer keeps crashing when trying to test it, ill just send how the code looks like

1 Like

neat little trick; if you add specify the delayTime for the stacking tween, anytime you call another Tween:Play(), it’ll use the new delayTime without applying the first fully applying the first Tween
code excerpt and video from one of my games:

-- if health is decreasing, then change size of `DelayedHealthbar` gradually
DelayedHealthbar.BackgroundColor3 = Color3.fromRGB(140, 0, 0)
DelayedHealthbar.ImageLabel.ImageColor3 = Color3.fromRGB(140, 0, 0)
TweenService:Create(DelayedHealthbar, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, false, 0.75), {Size = UDim2.fromScale(healthBarSize, 0.5)}):Play()

in otherwords, you should just have to change your tween from

task.wait(2)
tween = ts:Create(damageFrame, TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), {Position = UDim2.new(0, 0, 0, 0)})

to

--task.wait(2) -- wait here is unnecessary
tween = ts:Create(damageFrame, TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, false, 2.0), {Position = UDim2.new(0, 0, 0, 0)})
-- ( Added more to the TweenInfo.new parameters )
2 Likes

in my game i have a billboard GUI cuz its above the players head, but it should work as long as you have a regular GUI

this is the code i use:

local healthGUI = script.Parent.Health
local player =  game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local Hum = char:WaitForChild("Humanoid")

local bufferModule = require(game:GetService("ReplicatedStorage").Modules.BufferHandler)
local buff = bufferModule.Create(5)

local tweenS = game:GetService("TweenService")

function handlehp(gui : BillboardGui, humanoid : Humanoid)
	local amount : Frame = gui.BG.amount
	local take : Frame = gui.BG.amountTaken
	
	humanoid.HealthChanged:Connect(function(a)
		task.spawn(function()
			buff:startBuffer()
			
			for _, v in pairs(gui:GetDescendants()) do
				if v:IsA("Frame") then
					tweenS:Create(v, TweenInfo.new(0.2), {BackgroundTransparency = 1}):Play()
				end
			end
		end)
		
		for _, v in pairs(gui:GetDescendants()) do
			if v:IsA("Frame") then
				v.BackgroundTransparency = 0
			end
		end
		
		local goal = {
			Size = UDim2.fromScale(a / 100, 0.73)
		}
		tweenS:Create(amount, TweenInfo.new(0.2), goal):Play()

		task.spawn(function()
			local b = 0.4
			
			task.wait(b)
			tweenS:Create(take, TweenInfo.new(b), goal):Play()
		end)
	end)
end

function addHealthGui(c)
	local hp = healthGUI:Clone()
	hp.Parent = c:WaitForChild("Head")
	
	handlehp(hp, Hum)
end

addHealthGui(char)

player.CharacterAdded:Connect(function(c)
	Hum = c:WaitForChild("Humanoid")
	addHealthGui(c)
end)

the “amount” frame is the frame that reflects the amount of health the player has
the “amount taken” frame is how much health the player lost, BG is obviously just the background of it all and you can ignore the stroke because i use it for aesthetic purposes

also i set it to be relative to the max health of 100, but if you have a different max health, just replace local goal = { Size = UDim2.fromScale(a / 100, 0.73) }
with
local goal = { Size = UDim2.fromScale(a / MAX_HEALTH, 0.73) }

and also make sure to replace the Y section there with the actual Y size of yours because i made it specifically for mine

basically instead of doing anything complicated, every time the health variable is changed, it will just tween the “amount taken” frame size to the same size as the regular frame and it makes it have a buffer of 2 seconds which is why i have a module called “buffer handler” its a simple module i made to make buffers easier, the source code for it is:

local module = {}

function module.Create(WaitTime : number)
	local self = {}
	self.time = WaitTime
	self.bufferActive = false
	local old
	
	function self:startBuffer()
		old = tick()
		self.bufferActive = true
		
		repeat 
			task.wait()
			self.bufferActive = true
		until math.round(tick()) == math.round(old + self.time)
		
		self.bufferActive = false
	end
	
	return self
end

return module

basically this module just creates a buffer that waits a certain amount of time, but if the buffer is activated while its still active, it resets the buffer, so it will wait until it has been 2 seconds since the last activation of it

ok but lastly, just make sure to change the handleHP gui with your gui and make sure the names of the variables are changed so it matches with the names of your health GUI

1 Like

or do what that other guy did :sweat_smile: i did not know that the delay part of tween does that

2 Likes

This is an issue I stated in my original post, when I change this it seems just not to execute the damageFrame tweens at all. I will probably try both of your replies’ suggestions and change my system to tween sizes instead of positions.

i hadn’t realized it was changing position, setting the anchorpoint to be left-sided and changing size is probably a better way of doing it lol

I changed my script to have 2 tweens and just tween the sizes. It works perfectly, thank you!

I have used the other person’s solution but I will check into your reply aswell. The module you provided seems very useful, and another solution to this is with tick(), anyway.

ah thanks, no worries, i think i may have overcomplicated things a bit cuz its a bit much code, but yeah changing positions is not the best, you should set the anchor point to the left side and then just use the size, but anyways yea

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