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.