Animations don't stop for other clients

Hello, I’ve got a project that’s supposed to release very soon, but have run into a last minute bug I’m at a loss how to solve.

I’m using a custom version of Roblox’s animation script that has the added ability to reload playing animations when their AnimationId changes.
For example, if a Player is walking and the AnimationId of their walking animation changes, the script stops the old AnimationTrack, then loads and plays the new one.

On the client the script belongs to, this works flawlessly, however, other clients don’t see the old AnimationTrack stop when an animation is updated.
When your walk animation updates like above, it looks fine for you, but to other players you’re now playing both the old and new animation at the same time.

I’ve tried multiple approaches such as inserting wait()s between stopping and destroying old animations and done extensive tests using print(), but everything looks and acts fine for the client running the script.
I’m at a loss as to what to do about this, though it seems clear it’s some kind of replication error, I’m unsure how to proceed.
Any help or insight would be greatly appreciated!

Here is the complete LocalScript found in StarterCharacterScripts.
The problem is specifically with the reloading of the currently playing animation. (Line 73 -226)
I’ve also included the script itself for testing, though you’ll have to replace the AnimationIds yourself.

Animation LocalScript.rbxm (5.7 KB)

local Character:Model = script.Parent
local Humanoid:Humanoid = script.Parent:WaitForChild("Humanoid")
local pose:String = "Standing"

local currentAnim:String = ""
local currentAnimInstance:Animation = nil
local currentAnimTrack:AnimationTrack = nil
local currentAnimKeyframeHandler:RBXScriptConnection = nil
local currentAnimSpeed:Number = 1
local animTable = {}

local animNames = {
	idle = {
		{ id = script:WaitForChild('idle'):WaitForChild('IdleAnim').AnimationId,			weight = 9 };
	};
	
	walk = {
		{ id = script:WaitForChild('walk'):WaitForChild('WalkAnim').AnimationId,			weight = 10 };
	};
	
	jump = {
		{ id = script:WaitForChild('jump'):WaitForChild('JumpAnim').AnimationId,			weight = 10 };
	};
	
	fall = {
		{ id = script:WaitForChild('fall'):WaitForChild('FallAnim').AnimationId,			weight = 10 };
	};
	
	climb = {
		{ id = script:WaitForChild('climb'):WaitForChild('ClimbAnim').AnimationId,			weight = 10 };
	};
	
	sit = {
		{ id = script:WaitForChild('sit'):WaitForChild('SitAnim').AnimationId,				weight = 10 };
	};
	
	toolnone = {
		{ id = script:WaitForChild('toolnone'):WaitForChild('ToolNoneAnim').AnimationId,	weight = 10 };
	};
}


-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- SETUP

local function conCharacterAnimationSet(name:String, fileList:{String})
	if (animTable[name] ~= nil) then
		for _, connection in pairs(animTable[name].connections) do
			connection:Disconnect()
		end
	end
	
	print('UPD ANAM '.. name.. '!')
	
	animTable[name] = {}
	animTable[name].count = 0
	animTable[name].totalWeight = 0	
	animTable[name].connections = {}
	
	-- check for config values
	local config = script:FindFirstChild(name)
	
	if (config ~= nil) then
--		print("Loading anims " .. name)
		table.insert(animTable[name].connections, config.ChildAdded:Connect(function() conCharacterAnimationSet(name, fileList) end))
		table.insert(animTable[name].connections, config.ChildRemoved:Connect(function() conCharacterAnimationSet(name, fileList) end))
		
		local idx = 1
		
		for _, childPart in pairs(config:GetChildren()) do
			if (childPart:IsA("Animation")) then
				table.insert(animTable[name].connections, childPart:GetPropertyChangedSignal('AnimationId'):Connect(function() conCharacterAnimationSet(name, fileList) end))
				
				animTable[name][idx] = {}
				animTable[name][idx].anim = childPart
				
				local weightObject = childPart:FindFirstChild("Weight")
				
				if (weightObject == nil) then
					animTable[name][idx].weight = 1
				else
					animTable[name][idx].weight = weightObject.Value
				end
				
				animTable[name].count += 1
				animTable[name].totalWeight += animTable[name][idx].weight
	--			print(name .. " [" .. idx .. "] " .. animTable[name][idx].anim.AnimationId .. " (" .. animTable[name][idx].weight .. ")")
				idx +=1
				
				if currentAnimTrack and currentAnim ==name then	-- If the Animation we're currently playing has been changed, reload it
					print('RELOAD "'.. currentAnim.. '"...')
					ReloadCurrentAnim()
				end
				
			end
		end
		
	end
	
	-- fallback to defaults
	if (animTable[name].count <= 0) then
		for idx, anim in pairs(fileList) do
			animTable[name][idx] = {}
			animTable[name][idx].anim = Instance.new("Animation")
			animTable[name][idx].anim.Name = name
			animTable[name][idx].anim.AnimationId = anim.id
			animTable[name][idx].weight = anim.weight
			animTable[name].count +=1
			animTable[name].totalWeight += anim.weight
--			print(name .. " [" .. idx .. "] " .. anim.id .. " (" .. anim.weight .. ")")
		end
	end
end


-- Setup animation objects
local function scriptChildModified(child:StringValue)
	local fileList = animNames[child.Name]
	
	if (fileList ~= nil) then
		conCharacterAnimationSet(child.Name, fileList)
	end	
end

script.ChildAdded:Connect(scriptChildModified)
script.ChildRemoved:Connect(scriptChildModified)


for name, fileList in pairs(animNames) do 
	conCharacterAnimationSet(name, fileList)
end	


-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- ANIMATION

-- declarations
local jumpAnimTime = 0
local jumpAnimDuration = 0.3

local fallTransitionTime = 0.3
local jumpMaxLimbVelocity = 0.75


-- functions
local function stopAllAnimations()
	local oldAnim = currentAnim
	
	currentAnim = ""
	currentAnimInstance = nil
	if (currentAnimKeyframeHandler ~= nil) then
		currentAnimKeyframeHandler:Disconnect()
	end
	
	if (currentAnimTrack ~= nil) then
		print('STOPPING ANIM2 ['.. currentAnimTrack.Name.. ']')	--DEBUG
		currentAnimTrack:Stop()
		currentAnimTrack:Destroy()
		currentAnimTrack = nil
	end
	
	return oldAnim
end


local function setAnimationSpeed(speed:Number)
	if speed ~= currentAnimSpeed then
		currentAnimSpeed = speed
		currentAnimTrack:AdjustSpeed(currentAnimSpeed)
	end
end


-- Preload animations
local function playAnimation(animName:String, transitionTime:Number, humanoid:Humanoid, reloadAnim:Bool)
	local roll = math.random(1, animTable[animName].totalWeight)
	local idx = 1
	while (roll > animTable[animName][idx].weight) do
		roll -= animTable[animName][idx].weight
		idx += 1
	end
	
	local anim = animTable[animName][idx].anim
	
	-- switch animation		
	if (anim ~= currentAnimInstance) or reloadAnim then
		
		if (currentAnimTrack ~= nil) then
			print('STOPPING ANIM ['.. currentAnimTrack.Name.. ']')	--DEBUG
			currentAnimTrack:Stop(transitionTime)
			currentAnimTrack:Destroy()
		end
		
		currentAnimSpeed = 1
		
		-- load it to the humanoid; get AnimationTrack if already preloaded
		if not animTable[animName][idx].loaded or reloadAnim then
		--	print('Load New ['..anim.Name..']')
			animTable[animName][idx].loaded = humanoid.Animator:LoadAnimation(anim)
		end
		
		currentAnimTrack = animTable[animName][idx].loaded
		
		-- play the animation
		print('PLAYING ANIM ['.. currentAnimTrack.Name.. ']')	--DEBUG
		currentAnimTrack:Play(transitionTime)
		currentAnim = animName
		currentAnimInstance = anim
		
		-- set up keyframe name triggers
		if (currentAnimKeyframeHandler ~= nil) then
			currentAnimKeyframeHandler:Disconnect()
		end
		
		currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:Connect(keyFrameReachedFunc)
	end
	
end


function ReloadCurrentAnim()
--	print('---- Reload', currentAnim)
	playAnimation(currentAnim, 0.2, Humanoid, true)
end


function keyFrameReachedFunc(frameName:String)
	if (frameName == "End") then
		local repeatAnim = currentAnim
		
		local animSpeed = currentAnimSpeed
		playAnimation(repeatAnim, 0, Humanoid)
		setAnimationSpeed(animSpeed)
	end
end


-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- HUMANOID STATES

local function onRunning(speed:Number)
	if speed > 1 then
		playAnimation("walk", 0.2, Humanoid)
		setAnimationSpeed(speed / Humanoid.WalkSpeed)
		pose = "Walking"
		
	else
		playAnimation("idle", 0.2, Humanoid)
		pose = "Standing"
	end
end


local function onDied()
	pose = "Dead"
end


local function onJumping()
	playAnimation("jump", 0.1, Humanoid)
	jumpAnimTime = jumpAnimDuration
	pose = "Jumping"
end


local function onClimbing(speed:Number)
	playAnimation("climb", 0.2, Humanoid)
	setAnimationSpeed(speed / Humanoid.WalkSpeed)--12)
	pose = "Climbing"
end


local function onGettingUp()
	pose = "GettingUp"
end


local function onFreeFall()
	if (jumpAnimTime <= 0) then
		playAnimation("fall", fallTransitionTime, Humanoid)
	end
	pose = "FreeFall"
end


local function onFallingDown()
	pose = "FallingDown"
end


local function onSeated()
	pose = "Seated"
end


local function onPlatformStanding()
	pose = "PlatformStanding"
end


local function onSwimming(speed:Number)
	if speed > 0 then
		pose = "Walking"
	else
		pose = "Standing"
	end
end


-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
local lastTick = 0


local function move(waitTime:Number)
	local amplitude = 1
	local frequency = 1
	local deltaTime = waitTime - lastTick
	lastTick = waitTime
	
	local climbFudge = 0
	local setAngles = false

  	if (jumpAnimTime > 0) then
  		jumpAnimTime = jumpAnimTime - deltaTime
	end
	
	if (pose == "FreeFall" and jumpAnimTime <= 0) then
		playAnimation("fall", fallTransitionTime, Humanoid)
		
	elseif (pose == "Seated") then
		playAnimation("sit", 0.5, Humanoid)
		return
		
	elseif (pose == "Walking") then
		playAnimation("walk", 0.2, Humanoid)
		
	elseif (pose == "Dead" or pose == "GettingUp" or pose == "FallingDown" or pose == "Seated" or pose == "PlatformStanding") then
		playAnimation("idle", 0.2, Humanoid)
		--[[
		stopAllAnimations()
		amplitude = 0.1
		frequency = 1
		setAngles = true
		]]
	end
end


-- connect events
Humanoid.Died:Connect(onDied)
Humanoid.Jumping:Connect(onJumping)
Humanoid.Climbing:Connect(onClimbing)
Humanoid.Running:Connect(onRunning)
Humanoid.GettingUp:Connect(onGettingUp)
Humanoid.FreeFalling:Connect(onFreeFall)
Humanoid.FallingDown:Connect(onFallingDown)
Humanoid.Seated:Connect(onSeated)
Humanoid.PlatformStanding:Connect(onPlatformStanding)
Humanoid.Swimming:Connect(onSwimming)


-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- initialize to idle
playAnimation("idle", 0.2, Humanoid)
pose = "Standing"


while Character.Parent ~= nil do
	local _, waitTime = wait(0.1)
	move(waitTime)
end

Thank you for your time and input!
Please let me know if you need any more information.

2 Likes

I’ve found the solution!
Changing the AnimationId of an Animation before destroying the AnimationTrack that was created with it causes the animation to replicate incorrectly.
The solution is to destroy the AnimationTrack before changing the AnimationId.

2 Likes

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