When working with animations I noticed I was writing a few variables to track which animation was playing, if an animation was playing at all and the a count of (roughly) how long the animation was playing. So I thought I'd encapsulate it in a metatable. Figured it could be useful for others. Put this in a script file in your project. It does not have to be included on a gameobject. This is also on the Community Hub: /open 21541 The Code (Example below): Code: -------------------------------------------------------------------- -- AnimationSet class (meta-table) -- -- By: AnnoyedGuy -- --Note: Use this however you want! My only request, is please add -- -- improvements/fixes to the Community Hub. Also, saying -- -- thanks might be nice. -- -------------------------------------------------------------------- --HowTo: To use this, create a variable on your Behavior:Awake. -- -- like: -- --self.animations = AnimationSet:new(self.gameObject:GetComponent("ModelRenderer")) -- -- to add animations you fill out the args: -- --self.animations:Add(friendlyName, assetPath, start_frame, end_frame, loop) -- -- then when you want to play an animation: -- --self.animations:Play("friendlyName") -- -- you can then check if the animation is still playing: -- --self.animations:IsPlaying() -- -------------------------------------------------------------------- AnimationSet = { } AnimationSet_mt = { __index = AnimationSet } function AnimationSet:new(a_model) return setmetatable( { model = a_model, current="none", timer=0, anims = {} }, AnimationSet_mt) end function AnimationSet:Add(friendlyName, assetPath, start_frame, end_frame, loop) -- Prep the table self.anims[friendlyName] = { } -- Get the asset table and check if it exists. Pass off the error to the designer. local asset = CraftStudio.FindAsset(assetPath) if (asset == nil) then print("ERROR: Animation does not exist: "..assetPath) return end -- Store variables needed self.anims[friendlyName].asset = asset self.anims[friendlyName].startFrame = start_frame self.anims[friendlyName].endFrame = end_frame -- Multiply by 2 as an approximation of frames per sec, pending real animation timing from CS self.anims[friendlyName].length = (end_frame-start_frame)*2 self.anims[friendlyName].looping = loop self.anims[friendlyName].isPlaying = false end function AnimationSet:IsPlaying() if (self.current) then return self.anims[self.current].isPlaying else return false end end function AnimationSet:Play(name) -- Check if it's already playing the animation, avoid restarting at the first frame if (self.current == name) then return end self.current = name -- Check on animation existence if (self.anims[self.current] == nil) then print("ERROR: Animation is not added: '"..self.current.."' use :Add(friendlyName, assetPath, start_time, end_time, loop[optional])") return end -- Start up animation and when to start self.model:SetAnimation(self.anims[self.current].asset) self.model:SetAnimationTime(self.anims[self.current].startFrame) -- If this is not a looping animation... self.model:StartAnimationPlayback(self.anims[self.current].looping) if (not self.anims[self.current].looping) then -- Set the timer self.timer = self.anims[self.current].length if (self.timer == 0) then -- zero length, stay at the same frame self.model:StopAnimationPlayback() self.model:SetAnimationTime(self.anims[self.current].startFrame) end else self.timer = self.anims[self.current].length end -- This animation is now playing self.anims[self.current].isPlaying = true end function AnimationSet:Update() if (self.timer > 0) then self.timer = self.timer - 1 if (self.timer == 0) then self.model:StopAnimationPlayback() self.anims[self.current].isPlaying = false end end end Example of usage: Throw this onto a gameobject with model that can animation. Code: function Behavior:Awake() self.animations = AnimationSet:new(self.gameObject:GetComponent( "ModelRenderer" )) -- Arguments are: name, assetName, start frame, end frame, looping -- Idle is non looping and stays on 0 self.animations:Add("Idle","Creatures/NPCs/idle", 0, 0, false) -- Attack is non-looping and is a one shot of 30 frames self.animations:Add("Attack1","Creatures/NPCs/strike1",0,30,false) -- Starting animation self.animations:Play("Idle") end function Behavior:Update() -- If animation is idle if self.animations.current == "Idle" then if CraftStudio.Input.WasButtonJustPressed("attack1") then print("Attack") self.animations:Play("Attack1") end -- Otherwise if it's an attack elseif self.animations.current == "Attack1" then -- If it finished playing if not self.animations:IsPlaying() then -- Go back to idle self.animations:Play("Idle") end end -- Do update for timed animations self.animations:Update() end
Yep, very nice AnimationSet:Update() doesn't seem to work proper with looped animations. I would expect that an object calls this function regardless of its state and the animation playing. But with your default, even looping animations stop after one cycle because the timer runs down. I modified it like this, the timer still runs down and then starts again. What I'm not sure about is whether the restarting should happens in the next call of :Update() or if this is right. Anyway, I suggest altering :Update() in any way that won't make looped animations stop. Code: function AnimationSet:Update() if (self.timer > 0) then self.timer = self.timer - 1 if (self.timer == 0 and not self.anims[self.current].looping) then self.model:StopAnimationPlayback() self.anims[self.current].isPlaying = false elseif (self.timer == 0) then self.timer = self.anims[self.current].length end end end
Dan doesn't check the forums regularly, but you are correct about this. We noticed this bug a few days ago. :] good eye!
Im having some issues changing the code, I wanted to make it so if W key is down the animation will play and when I let go of it then it would go straight to the idle animation and not finish up the rest of it. Here is my code... Code: -------------------------------------------------------------------- --HowTo: To use this, create a variable on your Behavior:Awake. -- -- like: -- --self.animations = AnimationSet:new(self.gameObject:GetComponent("ModelRenderer")) -- -- to add animations you fill out the args: -- --self.animations:Add(friendlyName, assetPath, start_frame, end_frame, loop) -- -- then when you want to play an animation: -- --self.animations:Play("friendlyName") -- -- you can then check if the animation is still playing: -- --self.animations:IsPlaying() -- -------------------------------------------------------------------- local walking = false; local moveSpeed = 0.2; AnimationSet = { } AnimationSet_mt = { __index = AnimationSet } function AnimationSet:new(a_model) return setmetatable( { model = a_model, current="none", timer=0, anims = {} }, AnimationSet_mt) end function AnimationSet:Add(friendlyName, assetPath, start_frame, end_frame, loop) -- Prep the table self.anims[friendlyName] = { } -- Get the asset table and check if it exists. Pass off the error to the designer. local asset = CraftStudio.FindAsset(assetPath) if (asset == nil) then print("ERROR: Animation does not exist: "..assetPath) return end -- Store variables needed self.anims[friendlyName].asset = asset self.anims[friendlyName].startFrame = start_frame self.anims[friendlyName].endFrame = end_frame -- Multiply by 2 as an approximation of frames per sec, pending real animation timing from CS self.anims[friendlyName].length = (end_frame-start_frame)*2 self.anims[friendlyName].looping = loop self.anims[friendlyName].isPlaying = false end function AnimationSet:IsPlaying() if (self.current) then return self.anims[self.current].isPlaying else return false end end function AnimationSet:Play(name) -- Check if it's already playing the animation, avoid restarting at the first frame if (self.current == name) then return end self.current = name -- Check on animation existence if (self.anims[self.current] == nil) then print("ERROR: Animation is not added: '"..self.current.."' use :Add(friendlyName, assetPath, start_time, end_time, loop[optional])") return end -- Start up animation and when to start self.model:SetAnimation(self.anims[self.current].asset) self.model:SetAnimationTime(self.anims[self.current].startFrame) -- If this is not a looping animation... self.model:StartAnimationPlayback(self.anims[self.current].looping) if (not self.anims[self.current].looping) then -- Set the timer self.timer = self.anims[self.current].length if (self.timer == 0) then -- zero length, stay at the same frame self.model:StopAnimationPlayback() self.model:SetAnimationTime(self.anims[self.current].startFrame) end else self.timer = self.anims[self.current].length end -- This animation is now playing self.anims[self.current].isPlaying = true end function AnimationSet:Update() if (self.timer > 0) then self.timer = self.timer - 1 if (self.timer == 0) then self.model:StopAnimationPlayback() self.anims[self.current].isPlaying = false end end end function Behavior:Awake() self.animations = AnimationSet:new(self.gameObject:GetComponent( "ModelRenderer" )) -- Arguments are: name, assetName, start frame, end frame, looping -- Idle is non looping and stays on 0 self.animations:Add("Idle","Idle",0,0,true) -- Attack is non-looping and is a one shot of 30 frames self.animations:Add("Punch","Punch",0,10,false) -- Starting animation self.animations:Add("Walk","Walk",0,40,false) -- Starting animation self.animations:Play("Idle") end function Behavior:Update() -- If animation is idle if self.animations.current == "Idle" then if CraftStudio.Input.WasButtonJustPressed("Fire") then print("Attack") self.animations:Play("Punch") end -- Otherwise if it's an attack elseif self.animations.current == "Punch" then -- If it finished playing if not self.animations:IsPlaying() then -- Go back to idle self.animations:Play("Idle") end end if self.animations.current == "Idle" then if CraftStudio.Input.IsButtonDown("W") then print("W") self.animations:Play("Walk") end elseif self.animations.current == "Walk" then if not self.animations:IsPlaying() then self.animations:Play("Idle") end end -- Do update for timed animations self.animations:Update() en
Well, just in case... you probably want Walk to be a looping animation? Code: self.animations:Add("Walk","Walk",0,40,true) To answer your question, you might want to change this: Code: if self.animations.current == "Idle" then if CraftStudio.Input.IsButtonDown("W") then print("W") self.animations:Play("Walk") end elseif self.animations.current == "Walk" then if not self.animations:IsPlaying() then self.animations:Play("Idle") end end To this: Code: if (CraftStudio.Input.IsButtonDown("W") and self.animations.current == "Idle") then self.animations:Play("Walk") elseif self.animations.current == "Walk" then self.animations:Play("Idle") end I'm pretty sure that should work: if you are providing input and the animations is Idle (I.E. not "Punch"), Play walk. Otherwise if the animation is "Walk" (and no input is provided, else part), play "Idle". I'm not sure what kind of game you are doing, so this might not apply, but: The way we do it in Curse of the Iron Keep is the character has a speed. So the check is actually on his speed and the input is handled earlier in the routine and allows for gravity and collisions to override the speed and thus make him Idle if he's colliding with something. Hope this helps! EDIT: Funky font tag from my careless copying! Oops, fixed!
Well, I don't think it's quite good enough to be useful to everyone. It's also not completely accurate on the frame timing (because I haven't needed it to be yet). There were a couple bugs and a few new features I've added since that version.
Sorry, I was wrong. You'll still have to do the button check again. It's been a long day... Code: if (CraftStudio.Input.IsButtonDown("W") and self.animations.current == "Idle") then self.animations:Play("Walk") elseif (not CraftStudio.Input.IsButtonDown("W") and self.animations.current == "Walk") then self.animations:Play("Idle") end