I have an Attack animation for my NPC. After I :Play() the AnimationTrack the animation fires 0 events. I made some debbuging and I found that the AnimationTrack Length is 0 even after 10+ seconds of waiting. I reuploaded the animation and I still get no length and no events that get fired. The animation priority is set to Action, Loop is not toggled on. Here is a snippet of the debugged code:
function State:OnEnter(data: NPCStateMachineData.NPCData)
if not NPCStateMachineData.ReturnNPCHumanoidRootPartIfNPCDataHasGoodState(data) then
return
end
ParametricFunctions.StopAnimationTracksFromTable(data.AnimationTracks :: any);
self.AttackEndedConnection = data.AnimationTracks.DefaultAttack.Ended:Once(function()
local nextState = HandleAttackEndedEvent(data)
if not nextState then
self:ChangeState(NPCStateMachineData.States.Patrolling)
return
end
self:ChangeState(nextState)
end);
self.HitConnection = data.AnimationTracks.DefaultAttack:GetMarkerReachedSignal(NPCEnums.AnimationEvents.Hit):Once(function()
HandleHitEvent(data)
end)
data.AnimationTracks.DefaultAttack:GetMarkerReachedSignal(NPCEnums.AnimationEvents.Hit):Once(function()
print("hit1")
end)
data.AnimationTracks.DefaultAttack.Ended:Once(function()
print("end1")
end)
data.AnimationTracks.DefaultAttack.Stopped:Once(function()
print("stopped1")
end)
data.AnimationTracks.DefaultAttack.Changed:Once(function(propertyName)
print("Changed1", propertyName)
end)
data.AnimationTracks.DefaultAttack.DidLoop:Once(function()
print("DidLoop1")
end)
data.AnimationTracks.DefaultAttack.Destroying:Once(function()
print("Destroying1")
end)
print(data.AnimationTracks.DefaultAttack.IsPlaying, "isplaying")
local test = data.AnimationTracks.DefaultAttack
data.AnimationTracks.DefaultAttack:Play()
print("PLAY!")
print(data.AnimationTracks.DefaultAttack.IsPlaying, "isplaying")
print(test.IsPlaying, "isplaying test")
data.AnimationTracks.DefaultAttack.Ended:Once(function()
print("end2")
end)
data.AnimationTracks.DefaultAttack.Stopped:Once(function()
print("stopped2")
end)
data.AnimationTracks.DefaultAttack.Changed:Once(function()
print("Changed2")
end)
data.AnimationTracks.DefaultAttack.DidLoop:Once(function()
print("DidLoop2")
end)
data.AnimationTracks.DefaultAttack.Destroying:Once(function()
print("Destroying2")
end)
task.delay(10, function()
print(data.AnimationTracks.DefaultAttack.IsPlaying)
print(test.IsPlaying)
end)
task.wait(20)
self:ChangeState(NPCStateMachineData.States.Patrolling)
end
So the code I wrote for my NPC worked a few days ago. (The animation fired events). Now with the old code it doesn’t. I fixed it by Loading the animations inside the Animator later, not after cloning the Model, but when the Attack/Idle/Walk states appear, which has a little delay. I did it with this code:
Shared.LoadAnimations = function(npcData: NPCData)
if not Shared.ReturnNPCHumanoidRootPartIfNPCDataHasGoodState(npcData) then
return
end
if npcData.AnimationTracks then
return
end
npcData.AnimationTracks = {
Idle = npcData.NPCModel.Humanoid.Animator:LoadAnimation(AnimationInstances.IdleAnimation),
Walk = npcData.NPCModel.Humanoid.Animator:LoadAnimation(AnimationInstances.WalkAnimation),
DefaultAttack = npcData.NPCModel.Humanoid.Animator:LoadAnimation(AnimationInstances.DefaultAttackAnimation),
Attack1 = npcData.NPCModel.Humanoid.Animator:LoadAnimation(AnimationInstances.Attack1Animation),
Attack2 = npcData.NPCModel.Humanoid.Animator:LoadAnimation(AnimationInstances.Attack2Animation),
}
end
It makes no sense to me this fix, if something is not working, I expect an error, not this. Just give an error like: The animator was not prepared to load animations, try again, or… The model is not fully loaded so you can’t LoadAnimations yet, or… idk. The Wierd part is that it worked before without this workarround.
Should I mark my answer as solution? I will not because I still belive it is a bug.
--!strict
local ContentProvider = game:GetService("ContentProvider")
local NPCIds = require(script.Parent.Parent.Data.NPCIds)
local Local = {}
local Shared = {}
Local.AnimationInstancesArray = {}
for animationName, animationId in pairs(NPCIds.AnimationIds) do
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://" .. tostring(animationId)
Shared[animationName .. "Animation"] = animation
table.insert(Local.AnimationInstancesArray, animation)
end
ContentProvider:PreloadAsync(Local.AnimationInstancesArray)
return Shared
Same problem, the only solution is my workaround for now. Don’t ask me how I found the workaround :)) I stayed 7 hours trying diffrent things until I had the idea to delay a little the Loading of animations inside the Animator. Also, is there a problem with the fact that I load the animation inside the model that has a NIL parent? because I do all the preparation before spawning the model in the workspace (setting the parent to a folder that sits in the workspace): clone.Parent = SpawnedEnemies
Found another solution! Which is better in my opinion. Put a force field on the model and place the clone of NPC in the workspace before loading the animations. It works like this also. I added a force field so the NPC has all the health and regen scripts assigned. SO the problem is that animations can’t be loaded properly on a model that has the parent nil. The thing is that it worked before. Maybe the old Version of Roblox studio was a little delayed and made the loading when the model was already in workspace. I will still leave this forum open because there is no proper error/warning for this case.
Code snippet:
Snippet
Shared.SpawnOnce = function(spawnArea: Part): (Model?, StateMachine.RobloxStateMachine?)
local clone: typeof(Model) = Model:Clone()
local waitedHumanoid = clone:WaitForChild("Humanoid", 7)
if not waitedHumanoid then
clone:Destroy()
return nil, nil
end
local waitedAnimator = waitedHumanoid:WaitForChild("Animator", 7)
if not waitedAnimator then
clone:Destroy()
return nil, nil
end
clone.Name = script.Parent.Name
clone:SetAttribute("HostileNPCStringId", clone.Name)
clone.HumanoidRootPart:SetAttribute("HostileNPCStringId", clone.Name)
clone.Torso:SetAttribute("HostileNPCStringId", clone.Name)
clone.Humanoid:SetAttribute("HostileNPCStringId", clone.Name)
local cloneAttack1VisualHitBox = clone.VisualHitBoxes.WoodGolemCage
ParametricFunctions.DeepSetTransparencyOfInstance(cloneAttack1VisualHitBox, 1)
local clonePosition = ParametricFunctions.GetRandomPointOnCylinderSurface(
spawnArea,
clone.Humanoid.HipHeight + (clone.HumanoidRootPart.Size.Y / 2) + Constants.SPAWN_Y_OFFSET
) :: Vector3
clone.HumanoidRootPart.CFrame = CFrame.new(clonePosition)
local forceField = Instance.new("ForceField")
forceField.Parent = clone
clone.Parent = SpawnedEnemies
Debris:AddItem(forceField, 2)
local npcData: NPCStateMachineData.NPCData & {[string]: any} = {
NPCSpawnArea = spawnArea,
NPCModel = clone :: any,
NPCHumanoid = clone.Humanoid :: any,
RandomInstance = Random.new(),
VisualHitBoxes = {
Attack1 = cloneAttack1VisualHitBox,
} :: any,
AnimationTracks = {
Idle = clone.Humanoid.Animator:LoadAnimation(AnimationInstances.IdleAnimation),
Walk = clone.Humanoid.Animator:LoadAnimation(AnimationInstances.WalkAnimation),
DefaultAttack = clone.Humanoid.Animator:LoadAnimation(AnimationInstances.DefaultAttackAnimation),
Attack1 = clone.Humanoid.Animator:LoadAnimation(AnimationInstances.Attack1Animation),
Attack2 = clone.Humanoid.Animator:LoadAnimation(AnimationInstances.Attack2Animation),
},
}
-- ... I can't reveal more
Thank you! This was very helpful. I think I understand the problem now.
If you call LoadAnimation on an Animator that isn’t parented to the datamodel it will return an empty track that never properly loads the clip. This is a bug, what we want is for the track to work as intended once the Animator gets placed in the scene.
For now, you’ll need to defer calling LoadAnimation until after the character is parented to the dm. We’ll look into a fix for this on our end.
Here is server script that will reproduce your issue. If you run this and look at the server view you’ll notice the cloned character does not animate.
-- preload the animation so its ready to go!
local ContentProvider = game:GetService("ContentProvider")
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://507766666" -- idle
ContentProvider:PreloadAsync({animation})
function cloneRig(srcRig, position)
local clone = srcRig:Clone()
local humanoid = clone:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
-- adding to server storage before calling LoadAnimation will also work
--clone.Parent = game.ServerStorage
-- This will not play, the animator is not in the datamodel
local track = clone.Humanoid.Animator:LoadAnimation(animation)
track:Play()
clone.Parent = workspace
clone:MoveTo(position)
-- This will play properly, the animator is in the datamodel
--local track = clone.Humanoid.Animator:LoadAnimation(animation)
--track:Play()
return clone
end
wait(5)
local clone = cloneRig(workspace.Rig, Vector3.new(0, 0, 0))
I like to provide information that makes the error reproducible. I also found another bug I think… I will create a diffrent topic for it, but the new bug that I found is that the BreakJointsOnDeath was not working if the RequiresNeck was disabled. Is this a bug or normal behavior?
Hi, we’ve just deployed a fix that should correct issues when starting an animation before the animator is parented to the datamodel. Please check that everything is working on your end and let us know if you have any further questions.