AnimationTrack:Stop(0) does not instantly stop animations for R6 characters that are not controlled by any player

When I create a character in a LocalScript, stopping an animation with a fadeTime duration of 0 does not instantly stop the animation. To clarify, when I say “character” I mean a character that is not controlled by any player. This bug affects cloned characters (either created directly through the Clone method on a player’s character, or by using Players:CreateHumanoidModelFromUserId().

Instead of instantly stopping the animation as I would expect to happen, the animation will instead ease out at what I assume is the default value (which is 0.100000001). In my case, I am making a stylized game where my animations are intentionally made to look like they’re at a low framerate. Being able to stop them instantly is very important to maintain consistency with the style I am aiming for.

I have tested this both in studio and in a real game, and this bug occurs in both situations.

Example: You can see how for players, they have their animations instantly stopped, but for the cloned characters, they will fade out. In the example both the player characters and cloned characters have a fadeTime of 0.

I have provided a few code snippets that showcase various scenarios. Only the first snippet is needed to see the bug in action, but the other scenarios provide valuable insight about other bizarre behavior that seems in line with this bug.

Reproduction steps:

  • Create an empty place.
  • Ensure that the rig that the players spawns in with is R6. This is done in Avatar Settings, under the “. . .” button.
  • Put an animation in ReplicatedStorage. Ensure its name is “Animation” and give it an AnimationId for an animation that you own and works for R6.
  • Pick one of the code snippets below and put them in a script that runs locally (either in a LocalScript or in a Script with a RunContext of Client). Do not have multiple code snippets active at the same time. Only test one at a time.
  • Play the game and witness the cloned character play his animation. The code snippets will loop around every 5 seconds. Read the cases for each code snippet to understand the purpose of them.

Example of my setup:
image

Code snippets:

Case 1: Despite providing Stop() with a `fadeTime` of 0 for both your character and your cloned character, only your character will stop playing their animations instantly. The cloned character will fade out smoothly back to his idle position, despite both having identical logic.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local anim = ReplicatedStorage.Animation
local localPlayer = Players.LocalPlayer

localPlayer.CharacterAdded:Connect(function(character)
	character.Archivable = true
	local newChar = character:Clone()

	newChar.Parent = workspace

	local animator = character.Humanoid.Animator :: Animator
	local dummyAnimator = newChar.Humanoid.Animator :: Animator

	task.spawn(function()
		while true do
			task.wait(5)

			local track = animator:LoadAnimation(anim)
			local dummyTrack = dummyAnimator:LoadAnimation(anim)

			track:Play(0)
			dummyTrack:Play(0)

			task.wait(0.5)

			track:Stop(0)
			dummyTrack:Stop(0)
		end
	end)
end)

Case 2: The cloned character is now given a fateTime of '0.0000001' and seems to stop instantly. Only the cloned character is animated in this case.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local anim = ReplicatedStorage.Animation
local localPlayer = Players.LocalPlayer

localPlayer.CharacterAdded:Connect(function(character)
	character.Archivable = true
	local newChar = character:Clone()

	newChar.Parent = workspace

	local animator = character.Humanoid.Animator :: Animator
	local dummyAnimator = newChar.Humanoid.Animator :: Animator

	task.spawn(function()
		while true do
			task.wait(5)

			local dummyTrack = dummyAnimator:LoadAnimation(anim)

			dummyTrack:Play(0)

			task.wait(0.5)

			dummyTrack:Stop(0.0000001)
		end
	end)
end)

Case 3: Similar to case 2, but now the player character also has an animation play and stop. Both the player character and cloned character have a fadeTime for their stop set as '0.0000001'. In this case, the cloned character does not stop instantly. The player character does, though. You would expect this fadeOut duration to work because it worked in case 2, but nope.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local anim = ReplicatedStorage.Animation
local localPlayer = Players.LocalPlayer

localPlayer.CharacterAdded:Connect(function(character)
	character.Archivable = true
	local newChar = character:Clone()

	newChar.Parent = workspace

	local animator = character.Humanoid.Animator :: Animator
	local dummyAnimator = newChar.Humanoid.Animator :: Animator

	task.spawn(function()
		while true do
			task.wait(5)

			local track = animator:LoadAnimation(anim)
			local dummyTrack = dummyAnimator:LoadAnimation(anim)

			track:Play(0)
			dummyTrack:Play(0)

			task.wait(0.5)

			track:Stop(0.000001)
			dummyTrack:Stop(0.0000001)
		end
	end)
end)

Case 4: Very similar to case 3, except there's now a very short yield/`task.wait` between the two animations being stopped. This yield has a duration of '0.0000000001'. The cloned character now stops immediately, and so does the player character. Very bizarre, considering it is practically the same as case 3, bar one line. This is an example of my ideal/expected outcome that should happen with the first case.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local anim = ReplicatedStorage.Animation
local localPlayer = Players.LocalPlayer

localPlayer.CharacterAdded:Connect(function(character)
	character.Archivable = true
	local newChar = character:Clone()

	newChar.Parent = workspace

	local animator = character.Humanoid.Animator :: Animator
	local dummyAnimator = newChar.Humanoid.Animator :: Animator

	task.spawn(function()
		while true do
			task.wait(5)

			local track = animator:LoadAnimation(anim)
			local dummyTrack = dummyAnimator:LoadAnimation(anim)

			track:Play(0)
			dummyTrack:Play(0)

			task.wait(0.5)

			track:Stop(0.000001)
			task.wait(0.0000000001)
			dummyTrack:Stop(0.0000001)
		end
	end)
end)
1 Like

Keep in mind that task.wait(0.00000001) is functionally identical to RunService.Heartbeat:Wait() according to the function documentation.

1 Like

Hi, thanks for posting!

This a subtle one. The differences you are observing are because the Animate script is not being run on the clone. Since the Animate script is a LocalScript, it will only run if it is inside a player’s character.

Since the Animate script is not running on the clone, the behavior of AnimationTrack:Stop(0) will be different than for the player. The player’s character has an idle animation always playing. This idle “absorbs” the transition — when the custom animation’s weight drops to 0, the idle (already at weight 1) takes over seamlessly.

If you want the clone to be created inside a LocalScript, you could try parenting the clone to the player using something like this:

if script.Parent.Name == "Clone" then
	return
end

local character = script.Parent

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local anim = ReplicatedStorage:WaitForChild("Animation")
local animDummy = ReplicatedStorage:WaitForChild("AnimationDummy")

character.Archivable = true
local newChar = character:Clone()
newChar.Name = "Clone"
newChar.Parent = script.Parent

local animator = character.Humanoid.Animator :: Animator
local dummyAnimator = newChar.Humanoid.Animator :: Animator

task.spawn(function()
	while true do
		task.wait(5)

		local track = animator:LoadAnimation(anim)
		local dummyTrack = dummyAnimator:LoadAnimation(anim)

		track:Play(0)
		dummyTrack:Play(0)

		task.wait(0.5)

		track:Stop(0)
		dummyTrack:Stop(0)
	end
end)

This will ensure that the clone’s Animate script runs, and you will see the same behavior for the player and the clone. Let me know if that works for you.

hi just a heads up, my project currently doesn’t need this atm so i probably wont test that for a long time. based on your explanation though, i assume just having a “core” animation playing at all times will fix my issue so i’d probably just do that.
admittedly though i would always assume that AnimationTrack:Stop(0) would stop the animation immediately, thats more intuitive for me at least. so if there is a takeaway i guess it could be that this could be better documented?