Managing looping animations

Hello,

I’m out of ideas on how to manage looped animations in Spine. Most recent attempt causes the animations to change properly but the animation bones are not in the right position(they bounce around when changing) and the bones don’t always start at frame 1. If you have any insight on how to solve the problem then please let me know. I have a feeling its something to do with the animation time argument, but I’m lost. Thanks!

function update(e)

local currentTime = e.time / 800

local delta = currentTime - lastTime

lastTime = currentTime

animationTime = animationTime + delta

player:updateWorldTransform()

if(aniState == 1) then

aniHolder[1]:apply(player, animationTime, true)

elseif(aniState == 2) then

aniHolder[2]:apply(player, animationTime, true)

elseif(aniState == 3) then

aniHolder[3]:apply(player, animationTime, true)

end

if(playerStun == false and pGrounded[1] == false) then

aniState = 3

elseif(playerStun == false and pGrounded[1] == true) then

aniState = 1

end

if(playerStun == true) then

aniState = 2

end

end

It’s been awhile since I worked with Spine, but Here’s a look at part of my update function.

[lua]

local lastTime = 0

local function playAnimation(event)

    --print(“playAnimation Runtime is loaded”)

    local Index = MySpine.AnimationIndex

    skeleton.x = MySpine.PlayerDemoX

    skeleton.y = MySpine.PlayerDemoY

    skeleton.flipX = MySpine.flipX

    

    local currentTime = event.time / 1000

    local delta = currentTime - lastTime

    lastTime = currentTime

    

    MySpine.animationTime = MySpine.animationTime + delta

    walkAnimation:apply(skeleton, MySpine.animationTime, Animation[Index].Loop)

    skeleton:updateWorldTransform()

    

    

end

[/lua]

i think part of your trouble is you are calling

[lua] player:updateWorldTransform()[/lua]

before specifying your animation params.

Call the updateWorldTransform()  last.

Obviously you’ve also got to enable the Runtime Listener at some point to get it to play.  I realize you are doing this because your animation is playing all messed up.

Be careful not to add more than 1 Runtime Listener for the animation.

[lua]Runtime:addEventListener(“enterFrame”, playAnimation)[/lua]

I’m a newb too, so your mileage may vary.

Spine is an incredible tool, I never used sprites because I couldn’t make them and couldn’t afford customs.  Spine allowed me to add an animated hero replacing the static .png I was using in SuperiBot.  Blew me away, I was able to create the character and put it in my game in about 1 week.  :) 

GL, hope this helps.

Nail

Thanks for the reply Nail!

I tried, however the end result is the same.

if(aniState == 1) then

aniHolder[1].mix(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 2) then

aniHolder[2].mix(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 3) then

aniHolder[3].mix(player, animationTime, true)

player:updateWorldTransform()

end

I didn’t realize esoteric software had posted more documentation. I found something about “Mix” which seems to be exactly what I need. I’m going to also check into the “AnimationIndex” you use.

ps: I tried to find your game on google play. I couldn’t find it. Is that an iOS title?

Arrrgh!  not sure why it’s not working.

Yes, SuperiBot is iOS only, if you can get ahold of a device download the free “Lite” version.

Take a look at this YouTube video…

SuperiBot

Not sure what the “Mix” runtime does, maybe does what I created myself, because I’m constantly mixing different animations for my iBot.

There is one thing I call in my function to set up the animation that isn’t in my “playAnimation” update function that I believe you need.

[lua]MySpine.animationTime = 0[/lua]

animationTime needs to be reset before calling the update function with a runtime listener.

In my function that loads the next animation to play, I load the params for a new animation from my table , I’m doing something similar what you are doing, but instead of doing a “if/else” statement, I’ve got a table of all my animations setup and I reference the Index number to the animation I want to play from some function that calls for a new animation.

So, something happens in my game (jump for instance) and a function in my “main.lua” which has required “MySpine.lua”, sets the  animation index needed as MySpine.AnimationIndex = 3 for instance (this jumps LEFT and loops/ see table).

Here’s what my table looks like that resides in MySpine.lua.

[lua]

local Animation = {}

Animation[1] = {}

Animation[1].Skeleton = “data/skeleton-Jump1.json”  --default animation/pose

Animation[1].flipX = true

Animation[1].Loop = true

Animation[2] = {}

Animation[2].Skeleton = “data/skeleton-Jump1.json”  --default animation/pose

Animation[2].flipX = false

Animation[2].Loop = true

Animation[3] = {}

Animation[3].Skeleton = “data/skeleton-Jump2.json”

Animation[3].flipX = true

Animation[3].Loop = true

Animation[4] = {}

Animation[4].Skeleton = “data/skeleton-Jump2.json”

Animation[4].flipX = false

Animation[4].Loop = true

Animation[5] = {}

Animation[5].Skeleton = “data/skeleton-Walk.json”

Animation[5].flipX = true

Animation[5].Loop = true

Animation[6] = {}

Animation[6].Skeleton = “data/skeleton-Walk.json”

Animation[6].flipX = false

Animation[6].Loop = true

Animation[7] = {}

Animation[7].Skeleton = “data/skeleton-Standing.json”

Animation[7].flipX = true

Animation[7].Loop = false

[/lua]

You can see in my update function “playAnimation”, I set the Index to the MySpine.AnimationIndex that has been called.

[lua]

local lastTime = 0

–local animationTime = 0

function playAnimation(event)

    --print(“playAnimation Runtime is loaded”)

    local Index = MySpine.AnimationIndex

    skeleton.x = MySpine.PlayerDemoX

    skeleton.y = MySpine.PlayerDemoY

    --skeleton.flipX = MySpine.flipX

    skeleton.flipX = MySpine.flipX

    

    local currentTime = event.time / 1000

    local delta = currentTime - lastTime

    lastTime = currentTime

    

    MySpine.animationTime = MySpine.animationTime + delta

    walkAnimation:apply(skeleton, MySpine.animationTime, Animation[Index].Loop)

    skeleton:updateWorldTransform()

    

    

end

[/lua]

You can see how I commented out reseting the animationTime = 0 above the function.  It needs to be reset at the start of each new animation and I moved it to that function. 

Here’s how I load a new animation to the “start” of a level.  It creates the display group to show the animation. The display group holding the SuperiBot Spine animation gets destroyed when the level is completed or changed.

I left the commented lines in as some either didn’t work as expected or were moved to other functions.

[lua]

function MySpine.loadAnimation()  --called when new Level loads

    if AnimationPlaying == true then

        AnimationPlaying = false

        Runtime:removeEventListener(“enterFrame”, playAnimation)

    end

         

            MySpine.EnableRocket = false

            MySpine.Shooting = false

            MySpine.StashGunAndRun = false

            

    local Index = MySpine.AnimationIndex

   – json.scale = 1.2

    print(“loadAnimation() has been called”)

     

    MySpine.PlayerGroup = display.newGroup()

    MySpine.PlayerGroup.isVisible = true

    

    skeleton = spine.Skeleton.new(skeletonData, MySpine.PlayerGroup)

    skeleton:setToBindPose()

    skeleton.x = MySpine.PlayerDemoX

    skeleton.y = MySpine.PlayerDemoY

    

    – iBot SIZE determined in loadDemoMap.lockMapDemoStep2()  !!!

    --skeleton.xScale = .8

    --skeleton.yScale = .8

    

    skeleton:rotate(MySpine.PlayerDemoRotate)

    

    skeleton.flipX = false

    skeleton.flipY = false

    skeleton.debug = false

    --camera:add(MySpine.PlayerGroup, 1, false)

    print(“MySpine.PlayerGroup = display.newGroup() Called>>>>>>>>>>>>>>”)

    

    print(“loadAnimation() has been called Flag2”)

    

    --[[  if AnimationPlaying == true then

        –    AnimationPlaying = false

        Runtime:removeEventListener(“enterFrame”, playAnimation)

    end  --]]

    

    AnimationPlaying = true

     

    --MySpine.PlayerGroup.isVisible = true

    --MySpine.PlayerGroup:toFront()

    

    --walkAnimation = json:readAnimationFile(skeletonData, “data/skeleton-Jump2.json”)

    MySpine.flipX = Animation[Index].flipX

    

    MySpine.animationTime = 0  --reset it here

    --skeleton.flipX = Animation[Index].flipX

    walkAnimation = json:readAnimationFile(skeletonData, Animation[Index].Skeleton)

    

    MySpine.PlayerGroup:toFront()

    

    Runtime:addEventListener(“enterFrame”, playAnimation)

    --end

    print(“loadAnimation() has been called Flag3”)

    

end

[/lua]

Now here’s how I load the subsequent animations to be played since the display group has been created.

Realize Ima newb and the code isn’t optimized or perfect, but it works.

[lua]

function MySpine.callAnimation()  --called to load 2nd or more animation

    

    if AnimationPlaying == true then

        AnimationPlaying = false

        Runtime:removeEventListener(“enterFrame”, playAnimation)

    end

    

    local Index = MySpine.AnimationIndex

    MySpine.flipX = Animation[Index].flipX

    

    print(“callAnimation() has been called”)

    

    if MySpine.Shooting == true then  --code to stand up from shooting

        MySpine.Shooting = false

        if MySpine.flipX == true then

            

            MySpine.AnimationIndex = 13 --sets Index for NEXT walk animation

            Index = 13

            

        elseif MySpine.flipX == false then

            MySpine.AnimationIndex = 14  --sets Index for NEXT walk animation

            Index = 14

            

        end

    end

    

    --print(“callAnimation() has been called Flag2”)

    --if AnimationPlaying == true then

    –    AnimationPlaying = false

    –    Runtime:removeEventListener(“enterFrame”, playAnimation)

    – end

    

    AnimationPlaying = true

    MySpine.PlayerGroup.isVisible = true   

    --MySpine.PlayerGroup:toFront()

    

    --skeleton.x = MySpine.PlayerDemoX

    --skeleton.y = MySpine.PlayerDemoY

    

    MySpine.animationTime = 0   --reset it here!

    

    --walkAnimation = json:readAnimationFile(skeletonData, “data/skeleton-Jump2.json”)

    

    --skeleton.flipX = Animation[Index].flipX

    walkAnimation = json:readAnimationFile(skeletonData, Animation[Index].Skeleton)

    

    

    Runtime:addEventListener(“enterFrame”, playAnimation)

    --end

    --print(“callAnimation() has been called Flag3”)

    

end

[/lua]

Lots to digest here, but should get you going.  Loading the animations from a table is the way to go IMO.

Let me know if this get you going.  If not I’ll post more of my MySpine.lua.

Hope this helps, 

Nail

Wow! I owe you one Nail. Thank you. The two culprits… animationTime = 0 and setToSetupPose()

I never even thought about the setToSetupPose().

I always called it once and setup then forgot about it.

function resetAni()

Runtime:removeEventListener(“enterFrame”, aniPlayer)

animationTime = 0

player:setToSetupPose()

Runtime:addEventListener(“enterFrame”, aniPlayer)

aniPlaying = true

end

function aniPlayer(e)

local currentTime = e.time / 1000

local delta = currentTime - lastTime

lastTime = currentTime

animationTime = animationTime + delta

if(aniState == 1) then

runAni:apply(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 2) then

stunAni:apply(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 3) then

flyAni:apply(player, animationTime, true)

player:updateWorldTransform()

end

end

whenever I need the animation to change I call it directly when the player is stunned or whatever

if(aniPlaying == true) then

aniPlaying = false

aniState = 2

resetAni()

end

S - NICE!

Glad  you could wade through my spaghetti and make your animations play.

I like your coded solution to calling the animations.

BTW, did you take the time to watch the video?

As a Spine programmer you may see something  interesting and ponder it.

Dynamically changing the animation with user input … aiming the canon.

The possibilities to dynamically change animations with code is an exciting new world to explore!  If there were only 28 hour days.

Thanks for appreciating my time to help.

Nail

I’m very tempted to jump on to the Spine bandwagon. Thanks for all the code you have shared. It will be priceless when the time comes. Most appreciated. 

Nail,

I checked out your video the other day, its looking good and smooth. Job well done! Spine and a bit of programming does open lots of doors for neat things. Its those little details that add up to make a great game.

ksan,

If you have any questions don’t hesitate to ask. I definitely recommend spine.

-Wesley

It’s been awhile since I worked with Spine, but Here’s a look at part of my update function.

[lua]

local lastTime = 0

local function playAnimation(event)

    --print(“playAnimation Runtime is loaded”)

    local Index = MySpine.AnimationIndex

    skeleton.x = MySpine.PlayerDemoX

    skeleton.y = MySpine.PlayerDemoY

    skeleton.flipX = MySpine.flipX

    

    local currentTime = event.time / 1000

    local delta = currentTime - lastTime

    lastTime = currentTime

    

    MySpine.animationTime = MySpine.animationTime + delta

    walkAnimation:apply(skeleton, MySpine.animationTime, Animation[Index].Loop)

    skeleton:updateWorldTransform()

    

    

end

[/lua]

i think part of your trouble is you are calling

[lua] player:updateWorldTransform()[/lua]

before specifying your animation params.

Call the updateWorldTransform()  last.

Obviously you’ve also got to enable the Runtime Listener at some point to get it to play.  I realize you are doing this because your animation is playing all messed up.

Be careful not to add more than 1 Runtime Listener for the animation.

[lua]Runtime:addEventListener(“enterFrame”, playAnimation)[/lua]

I’m a newb too, so your mileage may vary.

Spine is an incredible tool, I never used sprites because I couldn’t make them and couldn’t afford customs.  Spine allowed me to add an animated hero replacing the static .png I was using in SuperiBot.  Blew me away, I was able to create the character and put it in my game in about 1 week.  :) 

GL, hope this helps.

Nail

Thanks for the reply Nail!

I tried, however the end result is the same.

if(aniState == 1) then

aniHolder[1].mix(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 2) then

aniHolder[2].mix(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 3) then

aniHolder[3].mix(player, animationTime, true)

player:updateWorldTransform()

end

I didn’t realize esoteric software had posted more documentation. I found something about “Mix” which seems to be exactly what I need. I’m going to also check into the “AnimationIndex” you use.

ps: I tried to find your game on google play. I couldn’t find it. Is that an iOS title?

Arrrgh!  not sure why it’s not working.

Yes, SuperiBot is iOS only, if you can get ahold of a device download the free “Lite” version.

Take a look at this YouTube video…

SuperiBot

Not sure what the “Mix” runtime does, maybe does what I created myself, because I’m constantly mixing different animations for my iBot.

There is one thing I call in my function to set up the animation that isn’t in my “playAnimation” update function that I believe you need.

[lua]MySpine.animationTime = 0[/lua]

animationTime needs to be reset before calling the update function with a runtime listener.

In my function that loads the next animation to play, I load the params for a new animation from my table , I’m doing something similar what you are doing, but instead of doing a “if/else” statement, I’ve got a table of all my animations setup and I reference the Index number to the animation I want to play from some function that calls for a new animation.

So, something happens in my game (jump for instance) and a function in my “main.lua” which has required “MySpine.lua”, sets the  animation index needed as MySpine.AnimationIndex = 3 for instance (this jumps LEFT and loops/ see table).

Here’s what my table looks like that resides in MySpine.lua.

[lua]

local Animation = {}

Animation[1] = {}

Animation[1].Skeleton = “data/skeleton-Jump1.json”  --default animation/pose

Animation[1].flipX = true

Animation[1].Loop = true

Animation[2] = {}

Animation[2].Skeleton = “data/skeleton-Jump1.json”  --default animation/pose

Animation[2].flipX = false

Animation[2].Loop = true

Animation[3] = {}

Animation[3].Skeleton = “data/skeleton-Jump2.json”

Animation[3].flipX = true

Animation[3].Loop = true

Animation[4] = {}

Animation[4].Skeleton = “data/skeleton-Jump2.json”

Animation[4].flipX = false

Animation[4].Loop = true

Animation[5] = {}

Animation[5].Skeleton = “data/skeleton-Walk.json”

Animation[5].flipX = true

Animation[5].Loop = true

Animation[6] = {}

Animation[6].Skeleton = “data/skeleton-Walk.json”

Animation[6].flipX = false

Animation[6].Loop = true

Animation[7] = {}

Animation[7].Skeleton = “data/skeleton-Standing.json”

Animation[7].flipX = true

Animation[7].Loop = false

[/lua]

You can see in my update function “playAnimation”, I set the Index to the MySpine.AnimationIndex that has been called.

[lua]

local lastTime = 0

–local animationTime = 0

function playAnimation(event)

    --print(“playAnimation Runtime is loaded”)

    local Index = MySpine.AnimationIndex

    skeleton.x = MySpine.PlayerDemoX

    skeleton.y = MySpine.PlayerDemoY

    --skeleton.flipX = MySpine.flipX

    skeleton.flipX = MySpine.flipX

    

    local currentTime = event.time / 1000

    local delta = currentTime - lastTime

    lastTime = currentTime

    

    MySpine.animationTime = MySpine.animationTime + delta

    walkAnimation:apply(skeleton, MySpine.animationTime, Animation[Index].Loop)

    skeleton:updateWorldTransform()

    

    

end

[/lua]

You can see how I commented out reseting the animationTime = 0 above the function.  It needs to be reset at the start of each new animation and I moved it to that function. 

Here’s how I load a new animation to the “start” of a level.  It creates the display group to show the animation. The display group holding the SuperiBot Spine animation gets destroyed when the level is completed or changed.

I left the commented lines in as some either didn’t work as expected or were moved to other functions.

[lua]

function MySpine.loadAnimation()  --called when new Level loads

    if AnimationPlaying == true then

        AnimationPlaying = false

        Runtime:removeEventListener(“enterFrame”, playAnimation)

    end

         

            MySpine.EnableRocket = false

            MySpine.Shooting = false

            MySpine.StashGunAndRun = false

            

    local Index = MySpine.AnimationIndex

   – json.scale = 1.2

    print(“loadAnimation() has been called”)

     

    MySpine.PlayerGroup = display.newGroup()

    MySpine.PlayerGroup.isVisible = true

    

    skeleton = spine.Skeleton.new(skeletonData, MySpine.PlayerGroup)

    skeleton:setToBindPose()

    skeleton.x = MySpine.PlayerDemoX

    skeleton.y = MySpine.PlayerDemoY

    

    – iBot SIZE determined in loadDemoMap.lockMapDemoStep2()  !!!

    --skeleton.xScale = .8

    --skeleton.yScale = .8

    

    skeleton:rotate(MySpine.PlayerDemoRotate)

    

    skeleton.flipX = false

    skeleton.flipY = false

    skeleton.debug = false

    --camera:add(MySpine.PlayerGroup, 1, false)

    print(“MySpine.PlayerGroup = display.newGroup() Called>>>>>>>>>>>>>>”)

    

    print(“loadAnimation() has been called Flag2”)

    

    --[[  if AnimationPlaying == true then

        –    AnimationPlaying = false

        Runtime:removeEventListener(“enterFrame”, playAnimation)

    end  --]]

    

    AnimationPlaying = true

     

    --MySpine.PlayerGroup.isVisible = true

    --MySpine.PlayerGroup:toFront()

    

    --walkAnimation = json:readAnimationFile(skeletonData, “data/skeleton-Jump2.json”)

    MySpine.flipX = Animation[Index].flipX

    

    MySpine.animationTime = 0  --reset it here

    --skeleton.flipX = Animation[Index].flipX

    walkAnimation = json:readAnimationFile(skeletonData, Animation[Index].Skeleton)

    

    MySpine.PlayerGroup:toFront()

    

    Runtime:addEventListener(“enterFrame”, playAnimation)

    --end

    print(“loadAnimation() has been called Flag3”)

    

end

[/lua]

Now here’s how I load the subsequent animations to be played since the display group has been created.

Realize Ima newb and the code isn’t optimized or perfect, but it works.

[lua]

function MySpine.callAnimation()  --called to load 2nd or more animation

    

    if AnimationPlaying == true then

        AnimationPlaying = false

        Runtime:removeEventListener(“enterFrame”, playAnimation)

    end

    

    local Index = MySpine.AnimationIndex

    MySpine.flipX = Animation[Index].flipX

    

    print(“callAnimation() has been called”)

    

    if MySpine.Shooting == true then  --code to stand up from shooting

        MySpine.Shooting = false

        if MySpine.flipX == true then

            

            MySpine.AnimationIndex = 13 --sets Index for NEXT walk animation

            Index = 13

            

        elseif MySpine.flipX == false then

            MySpine.AnimationIndex = 14  --sets Index for NEXT walk animation

            Index = 14

            

        end

    end

    

    --print(“callAnimation() has been called Flag2”)

    --if AnimationPlaying == true then

    –    AnimationPlaying = false

    –    Runtime:removeEventListener(“enterFrame”, playAnimation)

    – end

    

    AnimationPlaying = true

    MySpine.PlayerGroup.isVisible = true   

    --MySpine.PlayerGroup:toFront()

    

    --skeleton.x = MySpine.PlayerDemoX

    --skeleton.y = MySpine.PlayerDemoY

    

    MySpine.animationTime = 0   --reset it here!

    

    --walkAnimation = json:readAnimationFile(skeletonData, “data/skeleton-Jump2.json”)

    

    --skeleton.flipX = Animation[Index].flipX

    walkAnimation = json:readAnimationFile(skeletonData, Animation[Index].Skeleton)

    

    

    Runtime:addEventListener(“enterFrame”, playAnimation)

    --end

    --print(“callAnimation() has been called Flag3”)

    

end

[/lua]

Lots to digest here, but should get you going.  Loading the animations from a table is the way to go IMO.

Let me know if this get you going.  If not I’ll post more of my MySpine.lua.

Hope this helps, 

Nail

Wow! I owe you one Nail. Thank you. The two culprits… animationTime = 0 and setToSetupPose()

I never even thought about the setToSetupPose().

I always called it once and setup then forgot about it.

function resetAni()

Runtime:removeEventListener(“enterFrame”, aniPlayer)

animationTime = 0

player:setToSetupPose()

Runtime:addEventListener(“enterFrame”, aniPlayer)

aniPlaying = true

end

function aniPlayer(e)

local currentTime = e.time / 1000

local delta = currentTime - lastTime

lastTime = currentTime

animationTime = animationTime + delta

if(aniState == 1) then

runAni:apply(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 2) then

stunAni:apply(player, animationTime, true)

player:updateWorldTransform()

elseif(aniState == 3) then

flyAni:apply(player, animationTime, true)

player:updateWorldTransform()

end

end

whenever I need the animation to change I call it directly when the player is stunned or whatever

if(aniPlaying == true) then

aniPlaying = false

aniState = 2

resetAni()

end

S - NICE!

Glad  you could wade through my spaghetti and make your animations play.

I like your coded solution to calling the animations.

BTW, did you take the time to watch the video?

As a Spine programmer you may see something  interesting and ponder it.

Dynamically changing the animation with user input … aiming the canon.

The possibilities to dynamically change animations with code is an exciting new world to explore!  If there were only 28 hour days.

Thanks for appreciating my time to help.

Nail

I’m very tempted to jump on to the Spine bandwagon. Thanks for all the code you have shared. It will be priceless when the time comes. Most appreciated. 

Nail,

I checked out your video the other day, its looking good and smooth. Job well done! Spine and a bit of programming does open lots of doors for neat things. Its those little details that add up to make a great game.

ksan,

If you have any questions don’t hesitate to ask. I definitely recommend spine.

-Wesley