TweenBase Completed event odd behavior?

I have a function that creates a tween base, plays it, and returns the tween. When I call the function and use the .Completed event, I’m getting different results depending on how I use it.

Using .Completed:Wait() will have the proper behavior, it will yield until completed. On the other hand, using .Completed:Connect(function() it will fire the completed event immediately without any yield. Does anyone know why?

local function CreateTween(Object, Info, Goal)
	local Tween = TweenService:Create(Object, Info, Goal)
	Tween:Play()
	
	return Tween
end

Yields (desired):

CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(0, 0, 0)}).Completed:Wait()
Camera:RotateAroundPart(TargetParts[CurrentTarget], Radius, Speed, Offset)

CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(255, 255, 255)}).Completed:Wait()
Effect:Destroy()

Fires immediately:

CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(0, 0, 0)}).Completed:Connect(function()
	Camera:RotateAroundPart(TargetParts[CurrentTarget], Radius, Speed, Offset)
end)

CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(255, 255, 255)}).Completed:Connect(function()
	Effect:Destroy()
end)
1 Like

Tween.Completed fires when a Tween stops, well, tweening for any reason.

In the case of your example using Completed:Connect, the code is running the first tween, hooking up the event connection, then moving onto the next task, which creates a second tween that overrides the original one. Overriding the first tween does cause the first Tween to “complete,” but it would pass Enum.PlaybackState.Cancelled rather than Enum.PlaybackState.Complete to any connected listeners. Because your code does not differentiate between these two results, it simply executes the code for the first tween’s event.

The reason this does not happen with Completed:Wait is because this forcibly yields the thread until the first tween is completed; the second tween isn’t even created until after the first tween is done. If you would like to mirror this behavior using the Completed:Connect structure, you would just need to move the creation of the second tween into the event listener for the first tween, like this:

CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(0, 0, 0)}).Completed:Connect(function()
	Camera:RotateAroundPart(TargetParts[CurrentTarget], Radius, Speed, Offset)
	CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(255, 255, 255)}).Completed:Connect(function()
		Effect:Destroy()
	end)
end)

Bear in mind, though, this will lead to unintended consequences if the first tween is played again, as it will repeat the process, only the handler for the second tween will fire however many times the first tween has been run. If you want to daisy-chain tweens, you are much better off using the Completed:Wait method. If you need to know how the tween completed in the Completed:Wait structure, simply save it to a variable, like this:

local FirstTweenPlaybackState = CreateTween(Effect, TweenInfo.new(1), {TintColor = Color3.fromRGB(0, 0, 0)}).Completed:Wait()
2 Likes

Alright, I see now. I was attempting the .Completed connection because it simply looked cleaner to me, but using :Wait() is no issue.

2 Likes