Issues with loading and playing animations

Hello developers!
This topic is very important to both my game and I, so I will keep it as short as possible in the hopes that more people will spend the time to read this topic. If I am missing any information, please do not hesitate to let me know in the replies.

I am developing a game in which players “walk the runway” and strike a pose when they get to the center of said runway. The animation that is loaded and played on the character (a cloned character) is the specific animation that the player has equipped. It’s cloned and loaded to the humanoid, and then played.

Thing is, it doesn’t load. And then it doesn’t play. At least that is what I think is happening.

Here's the code that handles the runway (cleaned of excess):
local EquippedFolder = Player:WaitForChild("Equipped") 

Status.Value = "Now on the runway: " .. Player.Name

local Character = Player.Character or Player.CharacterAdded:Wait()
Character.Archivable = true

local Rig = Character:Clone()
Rig.Name = "RIG"
local Humanoid = Rig:WaitForChild("Humanoid")

if Rig:FindFirstChild("Animate") then
	Rig.Animate:Destroy()
end

local AnimateScript = script.Animate:Clone()

for _, AnimationValue in pairs(Rig:WaitForChild("Animate"):GetChildren()) do
	AnimationValue.Parent = AnimateScript
end

AnimateScript.Parent = Rig
AnimateScript.Disabled = false

local Waypoints = {
	RunwayFolder.WaypointParts.BackLeft.Position,
	RunwayFolder.WaypointParts.FrontLeft.Position,
	RunwayFolder.WaypointParts.Front.Position,
	RunwayFolder.WaypointParts.FrontRight.Position,
	RunwayFolder.WaypointParts.BackRight.Position,
	RunwayFolder.WaypointParts.Exit.Position,
}
Rig.Parent = workspace.FocusedRig
Rig:SetPrimaryPartCFrame(RunwayFolder.WaypointParts.Enter.CFrame)

local ChosenAnimation = ItemsFolder.Emotes:WaitForChild(EquippedFolder:WaitForChild("EquippedEmote").Value):Clone()
local LoadedAnimation = Humanoid:LoadAnimation(ChosenAnimation)
LoadedAnimation.Looped = false

wait(2)

for v, Position in pairs(Waypoints) do
	Humanoid:MoveTo(Position)
	Humanoid.MoveToFinished:Wait()
	if v == 3 then
		local LoadedAnimation = Rig.Humanoid:LoadAnimation(ChosenAnimation)
		LoadedAnimation.Looped = true
		if LoadedAnimation.Length == 0 then repeat wait() until LoadedAnimation.Length ~= 0 end
		local AnimationLength = LoadedAnimation.Length
		local WaitTime = 0
		if AnimationLength > 8 then
			WaitTime = 6
		elseif AnimationLength < 4 then
			repeat if not ((WaitTime + AnimationLength) > 5) then WaitTime += AnimationLength else break end until WaitTime <= 6
			LoadedAnimation.Looped = false
		else
			WaitTime = AnimationLength
		end
		LoadedAnimation:Play()
		repeat wait() until LoadedAnimation.IsPlaying
		
		wait(WaitTime)
		LoadedAnimation:Stop()
		ParticleExplosion(Rig,ItemsFolder.Particles:WaitForChild(EquippedFolder:WaitForChild("EquippedParticle").Value):GetChildren(),false)
	end
end
Rig:Destroy()

Initially, I tried loading it once before the playing the animation. Didn’t work. Then I tried loading it before the player starts walking and then playing it when they reach the middle of the runway. Also didn’t work. My current solution is loading it before the runway, and then playing it when they reach the middle, although this also isn’t working very well for me.

I am preloading all the animations using PreloadAsync, and these work fine when played with rigs in viewports etc. I have no idea what the issue is!

Looping the animation isn’t working either. In the code you can see that I am setting looped to true straight after the animation is loaded, but the animations - if they happen to be played - do not loop 100% of the time.

I would greatly appreciate any feedback for this, and any solutions that anyone may come across. I am aware that LoadAnimation is depreciated but I never was able to work out what the new system that I should use instead was (it was not on devleoper.roblox.com) so I never made the switch.

Thank you!

2 Likes

LoadAnimation is valid. The actual problem, and I dealt with this for a while, is that the in game script “animate” is overwriting pretty much everything. Even deleting doesn’t seem to do the trick. However, the trick that I and many animators probably use, is shown below:

-- tie this in with an event that tells these anims to start playing
for _, playingTracks in pairs(humanoid:GetPlayingAnimationTracks()) do
		playingTracks:Stop(0)
			end
	local animateScript = character:WaitForChild("Animate")
	animateScript.run.RunAnim.AnimationId = "http://www.roblox.com/asset/?id=MYIDHERE"        -- Run
	animateScript.walk.WalkAnim.AnimationId = "http://www.roblox.com/asset/?id=MYIDHERE"    -- Walk
	animateScript.jump.JumpAnim.AnimationId = "http://www.roblox.com/asset/?id=MYIDHERE"      -- Jump
	animateScript.idle.Animation1.AnimationId = "http://www.roblox.com/asset/?id=MYIDHERE"   -- Idle (Variation 1)
	animateScript.idle.Animation2.AnimationId = "http://www.roblox.com/asset/?id=MYIDHERE"   -- Idle (Variation 2)
    --Etc.
-- This will overwrite the built-in anims, and allow you to use the custom ones
--Tie this in with a event that tells the script to return to default anims (if you want)
for _, playingTracks in pairs(humanoid:GetPlayingAnimationTracks()) do
		playingTracks:Stop(0)
			end
	local animateScript = character:WaitForChild("Animate")
animateScript.run.RunAnim.AnimationId = "http://www.roblox.com/asset/?id=180426354"        -- Run
	animateScript.walk.WalkAnim.AnimationId = "http://www.roblox.com/asset/?id=180426354"    -- Walk
	animateScript.jump.JumpAnim.AnimationId = "http://www.roblox.com/asset/?id=507765000"      -- Jump
	animateScript.idle.Animation1.AnimationId = "http://www.roblox.com/asset/?id=180435571"   -- Idle (Variation 1)
	animateScript.idle.Animation2.AnimationId = "http://www.roblox.com/asset/?id=180435792"   -- Idle (Variation 2)
	animateScript.fall.FallAnim.AnimationId = "http://www.roblox.com/asset/?id=507767968"      -- Fall
	animateScript.climb.ClimbAnim.AnimationId = "http://www.roblox.com/asset/?id=507765644"
------end
--end
1 Like

Won’t the animations restart randomly? What if I were to disable the animate script, remove all animation tracks, play the animation using loadanimation, and then re-enable the animate script?

The Animate script is not the worst thing out there, but I agree, for anyone whose game depends on animations as a serious component of the experience… that script has to go! I have disabled the script. Since it still exists, Roblox does not try to hand me another copy.

Here is the basic rules I have established in my first life beyond animate:

  1. Set the priority and loop values IN THE ANIMATION before you save it to Roblox. That’s stuff you don’t need to mess with via code.
  2. Load all relevant animations in advance, and save them in a table that stores them by priority. This is very important, that you have easy access to animations that you can search through.
  3. All animations maintained on and by server. This is open to negotiation, but animation data in tables is not being passed to clients even when I try.

Then, for animating, since you have them split by priority:

  1. Play all Core animations and ignore them. That’s the point of Core. Always on, always overwritten.
  2. Cycle through random idle animations and ignore them.
  3. Rebuild Animate just to handle your movement animations. You need a few things that only Animate did: choose your animation based on the humanoid State, choose run or walk based on speed, throttle other movement based on speed.
  4. Action animations are for playing your game. At the beginning of the script, find these tracks and assign them to variables. This is the point you set your Animation event scripts. Never need to do it again.

If you do this correctly, all you ever need to do while scripting is to call Play and Stop (for action animations only).

That’s the “long haul” solution. I will now go back to the original post code and see if there’s a simple fix.

if Rig:FindFirstChild("Animate") then
    Rig.Animate:Destroy()
end

local AnimateScript = script.Animate:Clone()

for _, AnimationValue in pairs(Rig:WaitForChild("Animate"):GetChildren()) do
    AnimationValue.Parent = AnimateScript
end

I don’t think anything is going to load because of this step here… first you find and destroy Animate. Then you look for the now destroyed Animate (or a respawn Animate?) children. On my game, I tried destroying Animate just for kicks. It never came back.

Try switching the order of those conditional statements.

In the actual game these are switched, looks like I messed up the order when I was cleaning the code to post here.

How can this be applicable in the code example shown where a new rig is created and assigned an animation based on what the player has equipped? The humanoid is different and so is the animation.

That explains why certain animations aren’t looping. Why would there be a looped value if it doesn’t do anything? Lol.

And your final checklist about “for animating” I haven’t been able to make a lot of sense of. Would you be willing to give code examples with each step as they are pretty vague? What are core animations? How are they loaded and played? What are action animations? Does rebuilding Animate mean the animate script that is in the player’s character by default or does it have a different meaning? Please be more specific.

Whew, long response warning! haha!

Keep in mind that I still haven’t completed my journey building a new animate system. I started last week but I’ve learned quite a bit since then. I don’t have all the right answers, but I know a lot of the wrong ones!

How can this be applicable in the code example shown where a new rig is created and assigned an animation based on what the player has equipped?

You make the load process a function, and you call it after you confirm the Humanoid is loaded (the animator is automatically created if it isn’t there). I actually do the same thing you do, which is sort animations by gear. I use an asset table to monitor which animations are loaded. For example:

local charAssets = {"Human", "Torch", "Sword"}

I load Human animations, then Torch, the Sword, wait (see below) then sort them by priority,

The reason you need to load ASAP is because there is a small delay (up to 1 second) between the animation being loaded and being able to use it’s priority value. You can’t load animations on the fly in the current environment and get consistent results. I’ve reported it as a bug.

As far as switching between gear/animation sets… that’s trickier. When do you leave an animation playing, and when do you kill it? I’m currently working on this.

Why would there be a looped value if it doesn’t do anything?

The hardest part about animation is keeping track of which track is which. You can change the Looped value, but if it’s anything like Priority, it isn’t even loaded until several frames later. If you grab the wrong one, bad things happen. Etc. Very hard to debug. If you can define it in the animation itself, do so.

Animations are a pain! lol

And your final checklist about “for animating” I haven’t been able to make a lot of sense of.

There are 4 different priorities defined by Roblox: Core, Idle, Movement, and Action. Each one is overwritten by the next level, and tries to mix with its own level. I use core for stuff I want to always be present: blinking and still frame poses (Roblox likes to return to the T pose on custom avatars. This fixes that problem). Idle stuff is what your character does when you do nothing, like cross their arms and look over their shoulder. Movement is self explanatory, and Action is anything really important. In my game, it’s mostly combat and death sequences.

The Animate Script handles Idle and Movement (and emotes… no one has time for that!), and doesn’t really address Core and Action. But the way they handle these is horribly inefficient, and near impossible to edit. The script is a bloated 800 lines that can only handle 1 humanoid. My primary animate script is 190 lines and easily ran 180 NPCs at once. I use a 200 line version for players.

Does rebuilding Animate mean the animate script that is in the player’s character by default or does it have a different meaning?

Exactly! I started over, and I disabled the original. 6 copies of Animate running simultaneously in my game broke it. I now can handle hundreds of humanoids. :sunglasses:

I can provide a few good code examples, but everything is still WIP. For example, movement is a truncated version of Animate, and I only use run, walk, climb, and jump.

NOTE: the Animators are my actors, and the list of actors is my cast. A little drama club humor.

local function manageAnimation (actor)
	local hum = actor.Parent
	coroutine.wrap(idleSelection)(actor) -- Idle loop called here
	hum.StateChanged:Connect(function (oldState, newState) 
		local pose = newState.Name
		local oldpose = oldState.Name
		if cast[actor].Movement[oldpose] then 
			cast[actor].Movement[oldpose].Track:Stop()
		else 
--			print("Nothing to stop.")
		end
		if cast[actor].Movement[pose] then 
			cast[actor].Movement[pose].Track:Play()
		else 
--			print(pose.." animation not found.")
		end
	end)
	hum.Running:connect(function (speed)	
		local speedThreshold = hum.WalkSpeed
		if speed > speedThreshold then
			cast[actor].Movement["Running"].Track:Play()
			cast[actor].Movement["Walking"].Track:Stop()
		elseif speed <=1 then
			cast[actor].Movement["Walking"].Track:Stop()
			cast[actor].Movement["Running"].Track:Stop()
		else	
			cast[actor].Movement["Walking"].Track:Play()
			cast[actor].Movement["Running"].Track:Stop()
		end
	end)
	hum.Climbing:connect(function (speed)	
		local speedThreshold = hum.WalkSpeed *.7
		cast[actor].Movement["Climbing"].Track:AdjustSpeed(speed/speedThreshold)
	end)
end

Action animations are everything else… Sort them into variables, set up their events, and play them later. An example of loading an action animation for my script. I’m particularly proud of this one, because it uses an animation event from the first animation to trigger a second animation. The second animation persists (read “Looped”) after the non-looped animation ends:

 local function setSpecialMovement (actor)
 	local actionAnims = cast[actor].Action
 	for i, action in pairs(actionAnims) do
 		if action.Track.Name == "TorchInMouth" then
 			torchInMouthTrack = action.Track
 		elseif action.Track.Name == "ClimbPrep" then
 			climbPrepTrack = action.Track
  			climbPrepTrack:GetMarkerReachedSignal("TorchSwitch"):Connect(function () 
 				local test = torchPass:Invoke(char, torchPos)
 				if torchPos == "Head" then 
 					torchInMouthTrack:Play()
 				else
 					torchInMouthTrack:Stop()
 				end 
 			end)
 		end
 	end
 end

Did I answer it all? Enough? Any of it? LOL, let me know!