Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

Managing looping animations
Started by solitarysoftworks Jul 12 2013 02:59 PM

7 replies to this topic
[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

solitarysoftworks

[GLOBAL: userInfoPane.html]
solitarysoftworks
  • Observer

  • 8 posts
  • Corona SDK

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


[TOPIC: post.html]
#2

xnailbender

[GLOBAL: userInfoPane.html]
xnailbender
  • Contributor

  • 362 posts
  • Corona SDK

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

 

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
 
i think part of your trouble is you are calling
player:updateWorldTransform()
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.
 
Runtime:addEventListener("enterFrame", playAnimation)
 
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


[TOPIC: post.html]
#3

solitarysoftworks

[GLOBAL: userInfoPane.html]
solitarysoftworks
  • Observer

  • 8 posts
  • Corona SDK

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?



[TOPIC: post.html]
#4

xnailbender

[GLOBAL: userInfoPane.html]
xnailbender
  • Contributor

  • 362 posts
  • Corona SDK

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.

 

MySpine.animationTime = 0

 

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.

 

 
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
 
You can see in my update function "playAnimation", I set the Index to the MySpine.AnimationIndex that has been called.
 

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
 
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.
 

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

 

 

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.

 

 
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
 
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


[TOPIC: post.html]
#5

solitarysoftworks

[GLOBAL: userInfoPane.html]
solitarysoftworks
  • Observer

  • 8 posts
  • Corona SDK

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


[TOPIC: post.html]
#6

xnailbender

[GLOBAL: userInfoPane.html]
xnailbender
  • Contributor

  • 362 posts
  • Corona SDK

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



[TOPIC: post.html]
#7

ksan

[GLOBAL: userInfoPane.html]
ksan
  • Corona Geek

  • 2,795 posts
  • Corona SDK

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. 



[TOPIC: post.html]
#8

solitarysoftworks

[GLOBAL: userInfoPane.html]
solitarysoftworks
  • Observer

  • 8 posts
  • Corona SDK

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




[topic_controls]
 
[/topic_controls]