HealthChanged firing twice

Script

players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local billboard = Instance.new('BillboardGui')
		billboard.Adornee = character.Head
		billboard.AlwaysOnTop = false
		billboard.Size = UDim2.new(1, 0, 1, 0)
		billboard.StudsOffset = Vector3.new(0, 3, 0)
		
		billboard.Parent = character

		local humanoid = character.Humanoid
		if not humanoid then return end

		local originalHealth = humanoid.Health
		humanoid.HealthChanged:Connect(function(newHealth)
			local healthChange = math.abs(originalHealth - newHealth)
			if originalHealth > newHealth then
				local text = Instance.new('TextLabel')
				text.BackgroundTransparency = 1
				text.Name = 'Damage'
				text.Size = UDim2.new(2, 0, 2, 0)
				text.Font = 'GothamBold'
				text.Text = '-' .. math.floor(healthChange)
				text.TextColor3 = Color3.fromRGB(255, 70, 70)
				text.TextScaled = true
				
				text.Parent = billboard
				
				disappear(text)
				
				originalHealth = newHealth
			end
		end)
	end)
end)

function disappear(text)
	for i = 1, 25 do
		wait(0.01)
		text.Position = text.Position - UDim2.new(0, 0, 0.04, 0)
		text.TextTransparency = text.TextTransparency + 0.04
		text.TextStrokeTransparency = text.TextStrokeTransparency + 0.04
	end
	text:Destroy()
end

Result
com-video-to-gif%20(9)

All I am doing is setting their health to 50, so the -50 appearing is correct, but it shouldn’t show -48 as well

Because your healing right at that moment?

1 Like

I continue to heal tho and the text doesnt appear.

originalHealth > newHealth

This may be irrelevant to your issue but I noticed the way you are making the text “fade out”, I would rather recommend using tween service for that as you can easily choose exactly how long the “fade out” is going to take, and you can tween a bunch of various properties at the same time. You can also cancel currently running tweens with new ones, so it will not overlap.

1 Like

Now for your issue: could it be that Roblox’s default health script may be the issue? Can you disable the default health script to confirm that?

Ok yea, disabling the Health script seemed to work. This obviously stops natural regen tho

Great :slight_smile: Good thing is that it’s not very difficult to make your own healing script.

I’ve noticed recently that developers have had some kind of obsession over containing respawn logic in Player.Added Character.Added throwaway scopes, yet poor old StarterCharacterScripts is getting ignored for code to get added on spawn.

What you could do is put this as a script in StarterCharacterScripts and truncate any bit related to PlayerAdded and CharacterAdded. After that, create your own health regen logic and call this script Health. It’ll overwrite the default Health script and it seems like a better alternative. You also won’t have any repeated firing of HealthChanged.

1 Like

Have you tried using Humanoid:GetPropertyChangedSignal('Health')? That may be a better event for you to use.

You should set originalHealth = newHealth directly after the if statement, not after you call the disappear function because that function waits.

1 Like

That wouldn’t really change anything and would actually make the coding convention worse, not better.

HealthChanged is like GetPropertyChangedSignal, except more appropriate for checking when health changes and the new health of the humanoid is important to know.

If HealthChanged fires twice, so would GetPropertyChangedSignal.

1 Like

That’s actually a really good point :sweat_smile: although, I’d have to use a LocalScript wouldn’t I? I’m using a ModuleScript for this (because I wanna try using as little Scripts and LocalScripts as possible)

ModuleScripts take the environment of the script they’re called in. If it’s called within a LocalScript, the module will send a bytecode that’s only available to the client – and vice versa.

So it wouldn’t really matter whether or not you should switch from a ModuleScript (running on the server) to a LocalScript. You can still make use of modules, just make sure the module knows who the player is.

Module:

return function(player) -- uses function currying which "makes" the module a function. allows it to take in custom arguments upon require()'ing
    local mymodule = {}
    local methods = setmetatable({}, {
        __index = mymodule
    })
    
    function mymodule.new()
        local o = setmetatable({
            myplayer = player
        }, {
            __index = methods
        })

        return o
    end

    function methods:whoismyplayer()
        print(self.myplayer)
    end

    return mymodule
end

LocalScript:

local mymodule = require(script.ModuleScript)(game.Players.LocalPlayer) -- localplayer arg
local myplayer = mymodule.new()
myplayer:whoismyplayer() -- output: NinjoOnline