How to stop duplicating tween

Hello devs!

I want to make fade effect for multiple frames but when it’s fades, waits and fades again. When it’s fades again, script duplicates tween.

Example:
fades > waits 5 seconds > fades 2x > waits 5 seconds > fades x3

I want:
fades > waits 5 seconds > fades > waits 5 seconds > fades

I know the reason why it’s happening but I don’t know how to stop it.

Here is script:

--[[Gradient Fade]]--
local fadeTween = TweenInfo.new(1, Enum.EasingStyle.Circular, Enum.EasingDirection.Out)
local offset = {Offset = Vector2.new(1,0)}
local startingPos = Vector2.new(-1,0)


local function animate()
	for i, slots in pairs(weapons:GetChildren()) do
		if slots:IsA("Frame") then
			local createFade = TS:Create(slots.UIStroke.UIGradient, fadeTween, offset)

			task.spawn(function()
				
				createFade:Play()
				createFade.Completed:Wait()
				slots.UIStroke.UIGradient.Offset = startingPos

				task.wait(5)

				animate()
			end)
		end
	end
end

animate()

any suggestions how to fix it?

TweenInfo.new has a parameter to allow looping of tweens, as well as a delay and reverse them (reverse in which you do not need based on your goals):

local fadeTween = TweenInfo.new(1, Enum.EasingStyle.Circular, Enum.EasingDirection.Out, -1, false, 5)

-- -1 for infinite loop. 5 seconds delay before playing.

You don’t need to fire animate() inside task.spawn. You might not need the task.spawn at all:

local fadeTween = TweenInfo.new(1, Enum.EasingStyle.Circular, Enum.EasingDirection.Out, -1, false, 5)
local offset = {Offset = Vector2.new(1,0)}
local startingPos = Vector2.new(-1,0)


local function animate()
	for _, slots in ipairs(weapons:GetChildren()) do
		if slots:IsA("Frame") then
			local createFade = TS:Create(slots.UIStroke.UIGradient, fadeTween, offset)

			createFade:Play()
		end
	end
end

animate()
1 Like

The only problem is the OP says they need to wait 5 seconds between each tween. If task.wait() is used within the for loop, then it will only play one tween every 5 seconds and it will never replay the tween.

@OP
Spawn will create another thread and is usually used when you want to trap a thread in an infinite loop, but still run code after (like an autosave function). Also, calling the function within itself is recursion which means that you will increase how many threads there are each time the tween completes until the game crashes.

Trying to get this to work with task.wait() seems like a real headache since there are multiple tweens that need to play. Instead, I would use RunService and connect to Heartbeat so we can have a speedy way to loop indefinitely. Then I would create the tweens as well as the function to handle when they have completed and place them inside a table and use the frame’s unique name as the key. Lastly, that’s all that’s left to do is create a timer and increment based on deltaTime and loop through the tweens in the TweenTable and play each one every 5 seconds:

local RunService = game:GetService("RunService")
local TS = game:GetService("TweenService")
local weapons = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("Weapons")
local fadeTween = TweenInfo.new(1, Enum.EasingStyle.Circular, Enum.EasingDirection.Out)
local offset = {Offset = Vector2.new(1,0)}
local startingPos = Vector2.new(-1,0)

--Create the tweens outside of the function and put them inside a table:
local TweensTable = {}
for i, slots in pairs(weapons:GetChildren()) do
	if slots:IsA("Frame") then
		--Make sure the Frames have unique names!
		TweensTable[slots.Name] = {
			--Create the tween one time
			Tween = TS:Create(slots.UIStroke.UIGradient, fadeTween, offset);
		}
		--Create the completed function that points to the tween we just created
			--Doing it this way is handy since we can easily disconnect the function
		TweensTable[slots.Name].Finished = TweensTable[slots.Name].Tween.Completed:Connect(function()
			--Do stuff when the tween has completed playing
			slots.UIStroke.UIGradient.Offset = startingPos
		end)
	end
end

--How much time to wait between each tween
local waitTimer = 5
--How much total time we have waited
local totalDeltaTime = 0
local function EveryHeartbeat(deltaTime) -->deltaTime is how long since the last time this function was called (~.0167 seconds)
	--increment totalDeltaTime
	totalDeltaTime += deltaTime
	--Only allow the tween to play if it has been 5 seconds
	if totalDeltaTime < waitTimer then return end
	for frameName, tweenData in pairs(TweensTable) do
		--Play each tween in the TweensTable we created
		tweenData.Tween:Play()
		--The tween.Completed is automatically handled in the function we created
	end
	--Forgot to reset the totalDeltaTime, whoops!
	totalDeltaTime = totalDeltaTime - waitTimer
end

--Connect to the Heartbeat function every frame (about every .0167 seconds)
RunService.Heartbeat:Connect(EveryHeartbeat)

[EDIT] Forgot to reset totalDeltaTime when it reached 5 seconds lol

What do you mean by all these exactly? Isn’t using TweenInfo.new's repeatCount parameter enough? I find doing such code cleaner than using task.wait() for looping. The solution I have works fine for OP’s goals.

The OP says they need to wait 5 seconds between each Tween:

If the tween is a constant loop, then there’s no way to wait 5 seconds before playing the tween

With the TweenInfo.new's repeatCount set to -1, it will loop indefinitely. The same function also sports a delayTime parameter, in which you set it to 5 (or 4 if you include the amount of time the tween should play for).

The newly created Tween with the repeating and delayed TweenInfo will work almost the same as what OP wants:

Wait 5 seconds
Run Tween
Wait 5 seconds
Reset target properties to original values, then Run Tween
Wait 5 seconds
Reset target properties to original values, then Run Tween
Wait 5 seconds
Reset target properties to original values, then Run Tween
etc...

A simple test confirms this, using this command (Prepare a part in Workspace):

_G.T = game:GetService("TweenService"):Create(workspace.Part, TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.In, -1, false, 4), {Position = workspace.Part.Position + Vector3.new(10, 0, 0)})

-- To play:
_G.T:Play()

-- To cancel:
_G.T:Cancel()

The core problem is that OP needs multiple tweens to play simultaneously and to have them wait 5 seconds between each play. Using task.delay will give the same problems as using task.wait() because there are multiple tweens. For instance the following code won’t work:

for i, slots in pairs(weapons:GetChildren()) do
	local createFade = TS:Create(slots.UIStroke.UIGradient, fadeTween, offset)
	createFade:Play()
	createFade.Completed:Wait()
	slots.UIStroke.UIGradient.Offset = startingPos
	task.wait(5)
end

This doesn’t work because it is waiting 5 seconds between each tween completion before it plays the next tween (only plays one tween at a time). That and it only plays once instead of constantly. Also, this creates a tween each time one is needed instead of creating the tween only once and reusing the tween (it’s recommended to use a tween this way).

Yes, you can use a while loop, but that still doesn’t solve the problem of them playing simultaneously:

while true do
	--This will crash if the weapons container has 0 Frames
	for i, slots in pairs(weapons:GetChildren()) do
		if slots:IsA("Frame") then
			local createFade = TS:Create(slots.UIStroke.UIGradient, fadeTween, offset)
			createFade:Play()
			createFade.Completed:Wait()
			slots.UIStroke.UIGradient.Offset = startingPos
			task.wait(5)
		end
	end
end

Additionally, you could create the tweens and play them in a long list but it just adds more bloat to the code and more upkeep rather than automatically creating the tweens/completed functions only once (this also limits the ability to cancel a specific tween outside of the loop). It also seems like it’s more difficult to code it this way.

Lastly, there’s no real need to use a global variable since using a regular table can complete the job just fine. The code provided definitely works and is scalable enough to handle any number of tweens very easily. Here is a video capture of the same code provided earlier using 30 tweens (I got lazy and just decided to duplicate the frame’s gradient after 3 lol):

Your method works, but not how the OP wanted it:

I was assuming the OP wanted more control over their tweens and what is done after the tween finishes. Your method is better for looping the tween indefinitely if no other actions are needed. I may have gotten a bit carried away when designing it because I was thinking about what needs to be done to cancel the tween.

1 Like

Using a delay will give the same problems as using task.wait() because there are multiple tweens.

Tweens are scheduled via the task scheduler and are therefore ran in their own threads of execution, using task.wait will yield the active thread of execution whereas using TweenService:Create's ‘delayTime’ parameter will not.

2 Likes

I was trying to do multiple things at the same time and failed as a result. I read delay as task.delay and I also mistakingly thought that the OP’s code was part of the solution’s code (namely the task.wait part). Completely my fault for not paying more attention.

I’ll edit what you quoted to task.delay since I truly do not want to spread misinformation.