How do i interpolate between animations?

I have a simple idle and run animation for my character and I have it working where when the character is not moving he displays the idle animation, and when he is moving he displays the run animation. My problem is that it doesn’t fade (interpolate) between these animations, it just jumps from idle to running. Here is my current code: 
 

[lua]

    local skeletonData = json:readSkeletonDataFile(“examples/spineboy/skeleton.json”)

    local skeleton = spine.Skeleton.new(skeletonData)

    local stateData = spine.AnimationStateData.new(skeletonData)

    stateData:setMix(“idle”, “run”, 0.2)

    stateData:setMix(“run”, “idle”, 0.4)

    local state = spine.AnimationState.new(stateData)

    state:setAnimationByName(0, “idle”, true, 0)

    local state2 = spine.AnimationState.new(stateData)

    state2:setAnimationByName(0, “run”, true, 0)

    local function enterFrame( event )

        if ( vx == 0 ) then --If the player’s velocity is equal to 0…

            state:apply(skeleton)

        elseif ( vx ~= 0 ) then – if the player’s velocity doesn’t equal 0…

            state2:apply(skeleton)

        end

    end

[/lua]

Is there a better way to handle animations? I looked at the examples on GitHub and the Spineboy example was the only one that showed multiple animations being played, but they didn’t show any interpolating. I also looked at their documentation and the functions they show that are supposed to utilize interpolating don’t seem to work in Corona. Does anyone have any ideas or examples? Any help is appreciated! :slight_smile:

-Zac

Hi,

I think you’ll only need one animationState for each character. Then you can queue animations using addAnimation/addAnimationByName. (I think this might also work with setAnimation functions too now, after the animationState got a pretty big rewrite not to long ago, but you might want to test this yourself) This should play one animation and create a smooth transition to the next one queued. Using your example code it might look like:

 [lua]

  1. local skeletonData = json:readSkeletonDataFile(“examples/spineboy/skeleton.json”)

  2.  

  3. local skeleton = spine.Skeleton.new(skeletonData)

  4.  

  5. local stateData = spine.AnimationStateData.new(skeletonData)

  6. stateData:setMix(“idle”, “run”, 0.2)

  7. stateData:setMix(“run”, “idle”, 0.4)

  8.  

  9.  

  10. local state = spine.AnimationState.new(stateData)

  11.  

  12. local function enterFrame( event )

  13.  

  14. if ( vx == 0 ) then --If the player’s velocity is equal to 0…

  15. state:setAnimationByName(0, “idle”, true, 0)

1. --or state:addAnimationByName(0, "idle", true, 0)
  1. elseif ( vx ~= 0 ) then – if the player’s velocity doesn’t equal 0…

  2. state:setAnimationByName(0, “run”, true, 0)

  3. –or state:addAnimationByName(0, “run”, true, 0)

  4. end

  5.     state:apply(skeleton)

  6. end

  7. [/lua]

Oh my, copy pasting and editing code in here is not cool. Sorry about the abysmal formatting… Or am I just doing it wrong?

 

Anyway I think this might get your desired result.

 

Sorry for the delayed response, but that almost works. I can use the functions you gave above to change the current animation, but it doesn’t work in the enterFrame listener. Although, if I set the animation to “run” right after the creation of the state then it works. I get this error:
 

2013-12-08 22:52:03.846 Corona Simulator[30497:e03] Runtime error

…c/Documents/code/miners/spine-lua/AnimationState.lua:56: attempt to compare number with nil

stack traceback:

    [C]: ?

    …c/Documents/code/miners/spine-lua/AnimationState.lua:56: in function ‘setCurrent’

    …c/Documents/code/miners/spine-lua/AnimationState.lua:172: in function <…c/Documents/code/miners/spine-lua/AnimationState.lua:167>

    (tail call): ?

    …sers/Zac/Documents/code/miners/examples/spineboy.lua:115: in function ‘enterFrame’

    /Users/Zac/Documents/code/miners/game.lua:329: in function ‘onEnterFrame’

    /Users/Zac/Documents/code/miners/gui.lua:116: in function </Users/Zac/Documents/code/miners/gui.lua:113>

    ?: in function <?:218>

Do you know what this means?

Hi,

Can’t really say I know what’s going on here. It seems like the call to getMix returns nil, but as far as I can see, that should not even be possible due to how AnimationStateData is set up. Have you gotten any further since posting? :stuck_out_tongue:

How and where are you calling the update function of animationState?

Sorry for the late reply, I haven’t been able to code very much this week. Here is what I have so far, it still get’s the error and I still can’t figure out the cause:

[lua]

local M = {}

function M.new(scale) 

    – This example shows simple usage of displaying a skeleton with queued animations.

    local spine = require “spine-corona.spine”

    M.physicsBody = nil

    local animSpeed = 1

    local json = spine.SkeletonJson.new()

    json.scale = scale

    local skeletonData = json:readSkeletonDataFile(“examples/spineboy/skeleton.json”)

    

    local skeleton = spine.Skeleton.new(skeletonData)

    function skeleton:createImage (attachment)

        – Customize where images are loaded.

        return display.newImage(“examples/spineboy/images/” … attachment.name … “.png”)

    end

    M.physicsBody = display.newRect( -45, -250, json.scale * 170, json.scale * 500)

    M.physicsBody.damage = 5

    M.physicsBody.alpha = 0.01

    physics.addBody(M.physicsBody, “dynamic”, {bounce = 0.2, density = 2.5, friction = 1})

    M.physicsBody.isFixedRotation = true

    function M.addToGroup(group) 

        group:insert(skeleton.group)

        group:insert(M.physicsBody)

    end

    function M.getPhysicsBody() 

        return M.physicsBody

    end

    function M.setPlayerXScale(x) 

        skeleton.group.scaleX = x

    end

    function M.getSkeleton()

        return skeleton

    end

    function M.setAnimSpeed( speed )

        animSpeed = speed

    end

    skeleton.group.x = 0

    skeleton.group.y = 0

    skeleton.flipX = false

    skeleton.flipY = false

    skeleton.debug = true – Omit or set to false to not draw debug lines on top of the images.

    skeleton.debugAabb = true

    skeleton:setToSetupPose()

    local bounds = spine.SkeletonBounds.new()

    – AnimationStateData defines crossfade durations between animations.

    local stateData = spine.AnimationStateData.new(skeletonData)

    stateData:setMix(“idle”, “run”, 0.2)

    stateData:setMix(“run”, “idle”, 0.4)

    – AnimationState has a queue of animations and can apply them with crossfading.

    local state = spine.AnimationState.new(stateData)

    state:setAnimationByName(0, “run”, true, 0)

    – local state2 = spine.AnimationState.new(stateData)

    – state2:setAnimationByName(0, “run”, true, 0)

    state.onStart = function (trackIndex)

        print(trackIndex…" start: "…state:getCurrent(trackIndex).animation.name)

    end

    state.onEnd = function (trackIndex)

        print(trackIndex…" end: "…state:getCurrent(trackIndex).animation.name)

    end

    state.onComplete = function (trackIndex, loopCount)

        print(trackIndex…" complete: “…state:getCurrent(trackIndex).animation.name…”, "…loopCount)

    end

    state.onEvent = function (trackIndex, event)

        print(trackIndex…" event: “…state:getCurrent(trackIndex).animation.name…”, “…event.data.name…”, “…event.intValue…”, “…event.floatValue…”, ‘"…(event.stringValue or “”)…"’")

    end

    local lastTime = 0

    local touchX = 999999

    local touchY = 999999

    local headSlot = skeleton:findSlot(“head”)

    local hip = skeleton:findBone(“hip”)

    local rep = true

    function M.enterFrame (event) 

        – Compute time in seconds since last frame.

        local currentTime = event.time / 1000

        local delta = currentTime - lastTime

        lastTime = currentTime

        – Update the state with the delta time, apply it, and update the world transforms.

        --state2:update(delta * animSpeed)

        skeleton.group.x = M.physicsBody.x

        skeleton.group.y = M.physicsBody.y + (M.physicsBody.height / 2)

        local vx, vy = M.physicsBody:getLinearVelocity()

        if (vx == 0) then

            M.setAnimSpeed(  1) – 140

            state:setAnimationByName(0, “idle”, true, 0)

        else 

            M.setAnimSpeed(  math.abs(vx) /300) – 140

            state:setAnimationByName(0, “run”, true, 0)

        end

        – Bounding box hit detection.

        bounds:update(skeleton, true)

        state:update(delta * animSpeed)

        state:apply(skeleton)

        skeleton:updateWorldTransform()

    end

end

return M

[/lua]

Hi,

I think you’ll only need one animationState for each character. Then you can queue animations using addAnimation/addAnimationByName. (I think this might also work with setAnimation functions too now, after the animationState got a pretty big rewrite not to long ago, but you might want to test this yourself) This should play one animation and create a smooth transition to the next one queued. Using your example code it might look like:

 [lua]

  1. local skeletonData = json:readSkeletonDataFile(“examples/spineboy/skeleton.json”)

  2.  

  3. local skeleton = spine.Skeleton.new(skeletonData)

  4.  

  5. local stateData = spine.AnimationStateData.new(skeletonData)

  6. stateData:setMix(“idle”, “run”, 0.2)

  7. stateData:setMix(“run”, “idle”, 0.4)

  8.  

  9.  

  10. local state = spine.AnimationState.new(stateData)

  11.  

  12. local function enterFrame( event )

  13.  

  14. if ( vx == 0 ) then --If the player’s velocity is equal to 0…

  15. state:setAnimationByName(0, “idle”, true, 0)

1. --or state:addAnimationByName(0, "idle", true, 0)
  1. elseif ( vx ~= 0 ) then – if the player’s velocity doesn’t equal 0…

  2. state:setAnimationByName(0, “run”, true, 0)

  3. –or state:addAnimationByName(0, “run”, true, 0)

  4. end

  5.     state:apply(skeleton)

  6. end

  7. [/lua]

Oh my, copy pasting and editing code in here is not cool. Sorry about the abysmal formatting… Or am I just doing it wrong?

 

Anyway I think this might get your desired result.

 

Sorry for the delayed response, but that almost works. I can use the functions you gave above to change the current animation, but it doesn’t work in the enterFrame listener. Although, if I set the animation to “run” right after the creation of the state then it works. I get this error:
 

2013-12-08 22:52:03.846 Corona Simulator[30497:e03] Runtime error

…c/Documents/code/miners/spine-lua/AnimationState.lua:56: attempt to compare number with nil

stack traceback:

    [C]: ?

    …c/Documents/code/miners/spine-lua/AnimationState.lua:56: in function ‘setCurrent’

    …c/Documents/code/miners/spine-lua/AnimationState.lua:172: in function <…c/Documents/code/miners/spine-lua/AnimationState.lua:167>

    (tail call): ?

    …sers/Zac/Documents/code/miners/examples/spineboy.lua:115: in function ‘enterFrame’

    /Users/Zac/Documents/code/miners/game.lua:329: in function ‘onEnterFrame’

    /Users/Zac/Documents/code/miners/gui.lua:116: in function </Users/Zac/Documents/code/miners/gui.lua:113>

    ?: in function <?:218>

Do you know what this means?

Hi,

Can’t really say I know what’s going on here. It seems like the call to getMix returns nil, but as far as I can see, that should not even be possible due to how AnimationStateData is set up. Have you gotten any further since posting? :stuck_out_tongue:

How and where are you calling the update function of animationState?

Sorry for the late reply, I haven’t been able to code very much this week. Here is what I have so far, it still get’s the error and I still can’t figure out the cause:

[lua]

local M = {}

function M.new(scale) 

    – This example shows simple usage of displaying a skeleton with queued animations.

    local spine = require “spine-corona.spine”

    M.physicsBody = nil

    local animSpeed = 1

    local json = spine.SkeletonJson.new()

    json.scale = scale

    local skeletonData = json:readSkeletonDataFile(“examples/spineboy/skeleton.json”)

    

    local skeleton = spine.Skeleton.new(skeletonData)

    function skeleton:createImage (attachment)

        – Customize where images are loaded.

        return display.newImage(“examples/spineboy/images/” … attachment.name … “.png”)

    end

    M.physicsBody = display.newRect( -45, -250, json.scale * 170, json.scale * 500)

    M.physicsBody.damage = 5

    M.physicsBody.alpha = 0.01

    physics.addBody(M.physicsBody, “dynamic”, {bounce = 0.2, density = 2.5, friction = 1})

    M.physicsBody.isFixedRotation = true

    function M.addToGroup(group) 

        group:insert(skeleton.group)

        group:insert(M.physicsBody)

    end

    function M.getPhysicsBody() 

        return M.physicsBody

    end

    function M.setPlayerXScale(x) 

        skeleton.group.scaleX = x

    end

    function M.getSkeleton()

        return skeleton

    end

    function M.setAnimSpeed( speed )

        animSpeed = speed

    end

    skeleton.group.x = 0

    skeleton.group.y = 0

    skeleton.flipX = false

    skeleton.flipY = false

    skeleton.debug = true – Omit or set to false to not draw debug lines on top of the images.

    skeleton.debugAabb = true

    skeleton:setToSetupPose()

    local bounds = spine.SkeletonBounds.new()

    – AnimationStateData defines crossfade durations between animations.

    local stateData = spine.AnimationStateData.new(skeletonData)

    stateData:setMix(“idle”, “run”, 0.2)

    stateData:setMix(“run”, “idle”, 0.4)

    – AnimationState has a queue of animations and can apply them with crossfading.

    local state = spine.AnimationState.new(stateData)

    state:setAnimationByName(0, “run”, true, 0)

    – local state2 = spine.AnimationState.new(stateData)

    – state2:setAnimationByName(0, “run”, true, 0)

    state.onStart = function (trackIndex)

        print(trackIndex…" start: "…state:getCurrent(trackIndex).animation.name)

    end

    state.onEnd = function (trackIndex)

        print(trackIndex…" end: "…state:getCurrent(trackIndex).animation.name)

    end

    state.onComplete = function (trackIndex, loopCount)

        print(trackIndex…" complete: “…state:getCurrent(trackIndex).animation.name…”, "…loopCount)

    end

    state.onEvent = function (trackIndex, event)

        print(trackIndex…" event: “…state:getCurrent(trackIndex).animation.name…”, “…event.data.name…”, “…event.intValue…”, “…event.floatValue…”, ‘"…(event.stringValue or “”)…"’")

    end

    local lastTime = 0

    local touchX = 999999

    local touchY = 999999

    local headSlot = skeleton:findSlot(“head”)

    local hip = skeleton:findBone(“hip”)

    local rep = true

    function M.enterFrame (event) 

        – Compute time in seconds since last frame.

        local currentTime = event.time / 1000

        local delta = currentTime - lastTime

        lastTime = currentTime

        – Update the state with the delta time, apply it, and update the world transforms.

        --state2:update(delta * animSpeed)

        skeleton.group.x = M.physicsBody.x

        skeleton.group.y = M.physicsBody.y + (M.physicsBody.height / 2)

        local vx, vy = M.physicsBody:getLinearVelocity()

        if (vx == 0) then

            M.setAnimSpeed(  1) – 140

            state:setAnimationByName(0, “idle”, true, 0)

        else 

            M.setAnimSpeed(  math.abs(vx) /300) – 140

            state:setAnimationByName(0, “run”, true, 0)

        end

        – Bounding box hit detection.

        bounds:update(skeleton, true)

        state:update(delta * animSpeed)

        state:apply(skeleton)

        skeleton:updateWorldTransform()

    end

end

return M

[/lua]