Buttons keep multiplying when multiple hovers

Hey, I’ve been experiencing this bug with a lot of my games and I don’t really have a solution to fix this. So here’s the problem,

The script works entirely fine. Though when hovering the buttons multiple times, the buttons start to appear larger than they should be. They should only increase by x1.5 upon being hovered but they instead end up becoming bigger when the hovering event is triggered multiple times. Here’s a block of code for the script and an image showcasing this bug:

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local LocalPlayer = Players.LocalPlayer
local Mouse = LocalPlayer:GetMouse()

local Frame = script.Parent.Parent:WaitForChild("MainFrame").ButtonsFrame
local TempHolder = script:WaitForChild("SparklesHolder")
local TempSpark = script:WaitForChild("Sparkle")

local SizeMulti = 1.15 -- Cartoonish size multiplier
local RotMulti = -90 -- Cartoonish rotation effect
local HoverDebounce = {} -- Table to track debounce for each button

local SFX = script:WaitForChild("HoverSFX")
local SFX2 = script:WaitForChild("ClickSFX")

local function createRandomSparkle(Holder)
	local Sparkle = TempSpark:Clone()
	Sparkle.Parent = Holder
	Sparkle.Position = UDim2.new(math.random(), 0, math.random(), 0)
	Sparkle.Size = UDim2.new(0, math.random(10, 30), 0, math.random(10, 30))

	local InTween = TweenService:Create(Sparkle, TweenInfo.new(0.5, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out), { Size = UDim2.fromScale(0.3, 0.3)})
	InTween:Play()
	InTween.Completed:Connect(function()
		local OutTween = TweenService:Create(Sparkle, TweenInfo.new(0.5, Enum.EasingStyle.Bounce, Enum.EasingDirection.In), { Size = UDim2.fromScale(0, 0)})
		OutTween:Play()
		OutTween.Completed:Connect(function()
			Sparkle:Destroy()
		end)
	end)
end

local function onHover(UI)
	local Descendants = Frame:GetDescendants()

	for _, var in ipairs(Descendants) do
		if (var:IsA("ImageButton") or var:IsA("TextButton")) and not var:GetAttribute("Nobtneffect") then
			HoverDebounce[var] = false

			var.MouseEnter:Connect(function()
				if HoverDebounce[var] then return end
				HoverDebounce[var] = true

				local OgSize = var.Size
				local Icon = var:FindFirstChildOfClass("ImageLabel") or var.BtnIcon
				local Holder = TempHolder:Clone()
				Holder.Parent = var
				Holder.ClipsDescendants = true

				local HoverSFX = SFX:Clone()
				HoverSFX.Parent = var
				HoverSFX:Play()

				local TweenGoal = {
					Size = UDim2.new(OgSize.X.Scale * SizeMulti, OgSize.X.Offset * SizeMulti, OgSize.Y.Scale * SizeMulti, OgSize.Y.Offset * SizeMulti),
				}
				local HighlightGoal = {
					Color = Color3.new(1, 1, 1),
					Thickness = 6,
					Transparency = 0
				}
				
				local HighTween = TweenService:Create(var.BorderStroke, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal)
				local IconTween = TweenService:Create(Icon, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Rotation = RotMulti })
				local FXTween = TweenService:Create(var, TweenInfo.new(0.6, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out), TweenGoal)
				HighTween:Play()
				IconTween:Play()
				FXTween:Play()

				spawn(function()
					while HoverDebounce[var] do
						createRandomSparkle(Holder)
						wait(0.45)
					end
				end)

				var.MouseLeave:Connect(function()
					HoverDebounce[var] = false
					Holder:Destroy()

					local TweenGoal2 = {
						Size = OgSize,
					}
					local HighlightGoal2 = {
						Color = Color3.new(0, 0, 0),
						Thickness = 4,
						Transparency = 0.75
					}

					local HighTweenBack = TweenService:Create(var.BorderStroke, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal2)
					local IconTweenBack = TweenService:Create(Icon, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.In), { Rotation = 0 })
					local FXTweenBack = TweenService:Create(var, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.In), TweenGoal2)
					HighTweenBack:Play()
					IconTweenBack:Play()
					FXTweenBack:Play()
				end)

				HoverSFX.Ended:Connect(function()
					HoverSFX:Destroy()
				end)
			end)

			var.MouseButton1Click:Connect(function()
				local ClickSFX = SFX2:Clone()
				ClickSFX.Parent = var
				ClickSFX:Play()

				local ImgFX = script.OverlayFX:Clone()
				local Clip = Instance.new("CanvasGroup", var)
				Clip.Name = "OverlayClipping"
				Clip.Size = UDim2.new(1, 0, 1, 0)
				Clip.Position = UDim2.new(0, 0, 0, 0)
				Clip.BackgroundTransparency = 1
				Clip.ClipsDescendants = true
				ImgFX.Parent = Clip

				local x, y = (Mouse.X - ImgFX.AbsolutePosition.X), (Mouse.Y - ImgFX.AbsolutePosition.Y)
				ImgFX.Position = UDim2.new(0, x, 0, y)

				local size = math.max(var.AbsoluteSize.X, var.AbsoluteSize.Y) * 1.5
				ImgFX:TweenSizeAndPosition(
					UDim2.new(0, size, 0, size),
					UDim2.new(0.5, -size / 2, 0.5, -size / 2),
					Enum.EasingDirection.Out,
					Enum.EasingStyle.Elastic,
					0.3
				)

				wait(0.15)
				ImgFX:Destroy()
				Clip:Destroy()
				ClickSFX:Destroy()
			end)
		end
	end
end

spawn(onHover)

I’ve tried using Debounce for this but it still hasn’t been working up until now. If anyone could fix this it’d be really great.

Are you sure you did it correctly? Could you provide the implementation with the debounce?

Here’s the script with that debounce system. When I used debounce, the buttons weren’t affected by it and they would sit at the same size they were at after being hovered on:

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local LocalPlayer = Players.LocalPlayer
local Mouse = LocalPlayer:GetMouse()

local Frame = script.Parent.Parent:WaitForChild("MainFrame").ButtonsFrame
local TempHolder = script:WaitForChild("SparklesHolder")
local TempSpark = script:WaitForChild("Sparkle")

local SizeMulti = 1.15 -- Cartoonish size multiplier
local RotMulti = -90 -- Cartoonish rotation effect
local HoverDebounce = {} -- Table to track debounce for each button

local SFX = script:WaitForChild("HoverSFX")
local SFX2 = script:WaitForChild("ClickSFX")

local function createRandomSparkle(Holder)
	local Sparkle = TempSpark:Clone()
	Sparkle.Parent = Holder
	Sparkle.Position = UDim2.new(math.random(), 0, math.random(), 0)
	Sparkle.Size = UDim2.new(0, math.random(10, 30), 0, math.random(10, 30))

	local InTween = TweenService:Create(Sparkle, TweenInfo.new(0.5, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out), { Size = UDim2.fromScale(0.3, 0.3) })
	InTween:Play()
	InTween.Completed:Connect(function()
		local OutTween = TweenService:Create(Sparkle, TweenInfo.new(0.5, Enum.EasingStyle.Bounce, Enum.EasingDirection.In), { Size = UDim2.fromScale(0, 0) })
		OutTween:Play()
		OutTween.Completed:Connect(function()
			Sparkle:Destroy()
		end)
	end)
end

local function onHover()
	local Descendants = Frame:GetDescendants()

	for _, var in ipairs(Descendants) do
		if (var:IsA("ImageButton") or var:IsA("TextButton")) and not var:GetAttribute("Nobtneffect") then
			HoverDebounce[var] = false

			var.MouseEnter:Connect(function()
				if HoverDebounce[var] then return end
				HoverDebounce[var] = true

				local OgSize = var.Size
				local Icon = var:FindFirstChildOfClass("ImageLabel") or var.BtnIcon
				local Holder = TempHolder:Clone()
				Holder.Parent = var
				Holder.ClipsDescendants = true

				local HoverSFX = SFX:Clone()
				HoverSFX.Parent = var
				HoverSFX:Play()

				local TweenGoal = {
					Size = UDim2.new(OgSize.X.Scale * SizeMulti, OgSize.X.Offset * SizeMulti, OgSize.Y.Scale * SizeMulti, OgSize.Y.Offset * SizeMulti),
				}
				local HighlightGoal = {
					Color = Color3.new(1, 1, 1),
					Thickness = 6,
					Transparency = 0
				}

				local HighTween = TweenService:Create(var.BorderStroke, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal)
				local IconTween = TweenService:Create(Icon, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Rotation = RotMulti })
				local FXTween = TweenService:Create(var, TweenInfo.new(0.6, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out), TweenGoal)
				HighTween:Play()
				IconTween:Play()
				FXTween:Play()

				spawn(function()
					while HoverDebounce[var] do
						createRandomSparkle(Holder)
						wait(0.45)
					end
				end)

				var.MouseLeave:Connect(function()
					if not HoverDebounce[var] then return end
					HoverDebounce[var] = false
					Holder:Destroy()

					local TweenGoal2 = {
						Size = OgSize,
					}
					local HighlightGoal2 = {
						Color = Color3.new(0, 0, 0),
						Thickness = 4,
						Transparency = 0.75
					}

					local HighTweenBack = TweenService:Create(var.BorderStroke, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal2)
					local IconTweenBack = TweenService:Create(Icon, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.In), { Rotation = 0 })
					local FXTweenBack = TweenService:Create(var, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.In), TweenGoal2)
					HighTweenBack:Play()
					IconTweenBack:Play()
					FXTweenBack:Play()
				end)

				HoverSFX.Ended:Connect(function()
					HoverSFX:Destroy()
				end)
			end)

			var.MouseButton1Click:Connect(function()
				if HoverDebounce[var] then return end
				HoverDebounce[var] = true

				local ClickSFX = SFX2:Clone()
				ClickSFX.Parent = var
				ClickSFX:Play()

				local ImgFX = script.OverlayFX:Clone()
				local Clip = Instance.new("CanvasGroup", var)
				Clip.Name = "OverlayClipping"
				Clip.Size = UDim2.new(1, 0, 1, 0)
				Clip.Position = UDim2.new(0, 0, 0, 0)
				Clip.BackgroundTransparency = 1
				Clip.ClipsDescendants = true
				ImgFX.Parent = Clip

				local x, y = (Mouse.X - ImgFX.AbsolutePosition.X), (Mouse.Y - ImgFX.AbsolutePosition.Y)
				ImgFX.Position = UDim2.new(0, x, 0, y)

				local size = math.max(var.AbsoluteSize.X, var.AbsoluteSize.Y) * 1.5
				ImgFX:TweenSizeAndPosition(
					UDim2.new(0, size, 0, size),
					UDim2.new(0.5, -size / 2, 0.5, -size / 2),
					Enum.EasingDirection.Out,
					Enum.EasingStyle.Elastic,
					0.3
				)

				wait(0.15)
				ImgFX:Destroy()
				Clip:Destroy()
				ClickSFX:Destroy()
				HoverDebounce[var] = false
			end)
		end
	end
end

spawn(onHover)

I personally use attributes for my GUI. I suggest doing that if nobody else has a fix for ya!

I’m not sure how Attributes will help, unless needed for Debounce and/or different size multiplying.

1 Like

Well it’s the fact your issue is the gui is simply not returning back to its original size, an attribute is constant unless you directly change it so I suggest doing that as a bandaid fix.

I think your problem has something to do with you connecting in connections or possibly you using the same debounce for Click and Enter. I’m gonna keep investigating.

I think it might be because everytime the var.MouseEnter event fires, it creates a new var.MouseLeave event connection. That means that if your mouse enters a gui 10 times, there will be 10 var.MouseLeave events connected. If this isn’t the full solution, it could be part of the solution. Here is a solution for this issue, I have only included the onHover function for convenience:

local function onHover()
	local Descendants = Frame:GetDescendants()

	for _, var in ipairs(Descendants) do
		if (var:IsA("ImageButton") or var:IsA("TextButton")) and not var:GetAttribute("Nobtneffect") then
			HoverDebounce[var] = false

			var.MouseEnter:Connect(function()
				if HoverDebounce[var] then return end
				HoverDebounce[var] = true

				local OgSize = var.Size
				local Icon = var:FindFirstChildOfClass("ImageLabel") or var.BtnIcon
				local Holder = TempHolder:Clone()
				Holder.Parent = var
				Holder.ClipsDescendants = true

				local HoverSFX = SFX:Clone()
				HoverSFX.Parent = var
				HoverSFX:Play()

				local TweenGoal = {
					Size = UDim2.new(OgSize.X.Scale * SizeMulti, OgSize.X.Offset * SizeMulti, OgSize.Y.Scale * SizeMulti, OgSize.Y.Offset * SizeMulti),
				}
				local HighlightGoal = {
					Color = Color3.new(1, 1, 1),
					Thickness = 6,
					Transparency = 0
				}

				local HighTween = TweenService:Create(var.BorderStroke, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal)
				local IconTween = TweenService:Create(Icon, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Rotation = RotMulti })
				local FXTween = TweenService:Create(var, TweenInfo.new(0.6, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out), TweenGoal)
				HighTween:Play()
				IconTween:Play()
				FXTween:Play()

				spawn(function()
					while HoverDebounce[var] do
						createRandomSparkle(Holder)
						wait(0.45)
					end
				end)

				local connection
				connection = var.MouseLeave:Connect(function()
					if not HoverDebounce[var] then return end
					connection:Disconnect()
					connection = nil
					HoverDebounce[var] = false
					Holder:Destroy()

					local TweenGoal2 = {
						Size = OgSize,
					}
					local HighlightGoal2 = {
						Color = Color3.new(0, 0, 0),
						Thickness = 4,
						Transparency = 0.75
					}

					local HighTweenBack = TweenService:Create(var.BorderStroke, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal2)
					local IconTweenBack = TweenService:Create(Icon, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.In), { Rotation = 0 })
					local FXTweenBack = TweenService:Create(var, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.In), TweenGoal2)
					HighTweenBack:Play()
					IconTweenBack:Play()
					FXTweenBack:Play()
				end)
				
				local connectionHoverSFX
				connectionHoverSFX = HoverSFX.Ended:Connect(function()
					connectionHoverSFX:Disconnect()
					connectionHoverSFX = nil
					HoverSFX:Destroy()
				end)
			end)

			var.MouseButton1Click:Connect(function()
				if HoverDebounce[var] then return end
				HoverDebounce[var] = true

				local ClickSFX = SFX2:Clone()
				ClickSFX.Parent = var
				ClickSFX:Play()

				local ImgFX = script.OverlayFX:Clone()
				local Clip = Instance.new("CanvasGroup", var)
				Clip.Name = "OverlayClipping"
				Clip.Size = UDim2.new(1, 0, 1, 0)
				Clip.Position = UDim2.new(0, 0, 0, 0)
				Clip.BackgroundTransparency = 1
				Clip.ClipsDescendants = true
				ImgFX.Parent = Clip

				local x, y = (Mouse.X - ImgFX.AbsolutePosition.X), (Mouse.Y - ImgFX.AbsolutePosition.Y)
				ImgFX.Position = UDim2.new(0, x, 0, y)

				local size = math.max(var.AbsoluteSize.X, var.AbsoluteSize.Y) * 1.5
				ImgFX:TweenSizeAndPosition(
					UDim2.new(0, size, 0, size),
					UDim2.new(0.5, -size / 2, 0.5, -size / 2),
					Enum.EasingDirection.Out,
					Enum.EasingStyle.Elastic,
					0.3
				)

				wait(0.15)
				ImgFX:Destroy()
				Clip:Destroy()
				ClickSFX:Destroy()
				HoverDebounce[var] = false
			end)
		end
	end
end

If you look at the var.MouseLeave event, you can see that I’ve added a variable called ‘connection’ to it, which stores the RBXScriptConnection object. If you want to stop this object from running a connected function, you should disconnect it with RBXScriptConnection:Disconnect() and set its value to nil. This ensures that after var.MouseLeave has been called, it immediately stops from firing the function more than once. This also saves memory and prevents something called a memory-leak, which can lag your game.

Edit: I’ve also added connection variable to the HoverSFX.Ended event.

By the way, if you want to use this method of connecting and disconnecting events, which I highly recommend because it prevents memory-leaks, I would suggest following these guidelines so you don’t fall into the same mistakes I made:

  1. Make sure you first define the “connection” variable above your event. For example:
local part = script.Parent

--this is wrong!
local connection = part.Touched:Connect(function(hit)
	--do stuff
end)

--this is right.
local connection1
connection1 = part.Touched:Connect(function(hit)
	--do some more stuff
end)
  1. Make sure that the variables you assign the events with, are uniquely named.
  2. Disconnect and set the event to nil as soon as possible. This is some kind of roblox bug where if you disconnect later in the function, the event will fire many times more than is expected.

You just didn’t implement the debounce correctly. Not entirely sure what you did wrong, but this is a better and simpler way of doing it generally anyway.

-- ... variables and stuff

local function debounce(event : RBXScriptSignal, callback : () -> ()) 
	local function connection()
		xpcall(callback, warn)
		event:Once(connection)
	end
	event:Once(connection)
end

-- add hover behaviour to buttons
for _, button in ipairs(Frame:GetDescendants()) do
	if (button:IsA("ImageButton") or button:IsA("TextButton")) and not button:GetAttribute("Nobtneffect") then
		
		local HoverSFX = SFX:Clone()
		HoverSFX.Parent = button
		
		debounce(button.MouseEnter, function ()
			local OgSize = button.Size
			local Icon = button:FindFirstChildOfClass("ImageLabel") or button.BtnIcon
			
			local Holder = TempHolder:Clone()
			Holder.Parent = button
			Holder.ClipsDescendants = true
			local sparkleThread = task.spawn(function()
				createRandomSparkle(Holder)
				task.wait(0.45)
			end)

			HoverSFX:Play()

			local TweenGoal = {
				Size = UDim2.new(OgSize.X.Scale * SizeMulti, OgSize.X.Offset * SizeMulti, OgSize.Y.Scale * SizeMulti, OgSize.Y.Offset * SizeMulti),
			}
			local HighlightGoal = {
				Color = Color3.new(1, 1, 1),
				Thickness = 6,
				Transparency = 0
			}

			local HighTween = TweenService:Create(button.BorderStroke, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal)
			local IconTween = TweenService:Create(Icon, TweenInfo.new(0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Rotation = RotMulti })
			local FXTween = TweenService:Create(button, TweenInfo.new(0.6, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out), TweenGoal)
			HighTween:Play()
			IconTween:Play()
			FXTween:Play()

			button.MouseLeave:Wait()

			local TweenGoal2 = {
				Size = OgSize,
			}
			local HighlightGoal2 = {
				Color = Color3.new(0, 0, 0),
				Thickness = 4,
				Transparency = 0.75
			}

			local HighTweenBack = TweenService:Create(button.BorderStroke, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), HighlightGoal2)
			local IconTweenBack = TweenService:Create(Icon, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.In), { Rotation = 0 })
			local FXTweenBack = TweenService:Create(button, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.In), TweenGoal2)
			HighTweenBack:Play()
			IconTweenBack:Play()
			FXTweenBack:Play()

			FXTweenBack.Completed:Wait()
			task.cancel(sparkleThread)
		end)

		debounce(button.MouseButton1Click, function()

			local ClickSFX = SFX2:Clone()
			ClickSFX.Parent = button
			ClickSFX:Play()

			local ImgFX = script.OverlayFX:Clone()
			local Clip = Instance.new("CanvasGroup", button)
			Clip.Name = "OverlayClipping"
			Clip.Size = UDim2.new(1, 0, 1, 0)
			Clip.Position = UDim2.new(0, 0, 0, 0)
			Clip.BackgroundTransparency = 1
			Clip.ClipsDescendants = true
			ImgFX.Parent = Clip

			local x, y = (Mouse.X - ImgFX.AbsolutePosition.X), (Mouse.Y - ImgFX.AbsolutePosition.Y)
			ImgFX.Position = UDim2.new(0, x, 0, y)

			local size = math.max(button.AbsoluteSize.X, button.AbsoluteSize.Y) * 1.5
			ImgFX:TweenSizeAndPosition( -- this is deprecated, don't use it 
				UDim2.new(0, size, 0, size),
				UDim2.new(0.5, -size / 2, 0.5, -size / 2),
				Enum.EasingDirection.Out,
				Enum.EasingStyle.Elastic,
				0.3
			)

			task.wait(0.15)

			ImgFX:Destroy()
			Clip:Destroy()
			ClickSFX:Destroy()
		end)
	end
end

I’m still of the same opinion as before:

2 Likes

Didn’t work but appreciate the breakdown

1 Like

Thanks! This ended up working just fine afterwards using the function and improved for index statement

1 Like

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