diff --git a/spine-corona/main.lua b/spine-corona/main.lua index 9df37d83d..6ee9ebb8f 100644 --- a/spine-corona/main.lua +++ b/spine-corona/main.lua @@ -22,6 +22,17 @@ skeleton.debug = true -- Omit or set to false to not draw debug lines on top of if name == "goblins" then skeleton:setSkin("goblingirl") end skeleton:setToSetupPose() +-- AnimationStateData defines crossfade durations between animations. +local stateData = spine.AnimationStateData.new(skeletonData) +stateData:setMix("walk", "jump", 0.2) +stateData:setMix("jump", "walk", 0.4) + +-- AnimationState has a queue of animations and can apply them with crossfading. +local state = spine.AnimationState.new(stateData) +state:setAnimation("walk", false) +state:addAnimation("jump", false) +state:addAnimation("walk", true) + local lastTime = 0 local animationTime = 0 Runtime:addEventListener("enterFrame", function (event) @@ -30,9 +41,9 @@ Runtime:addEventListener("enterFrame", function (event) local delta = currentTime - lastTime lastTime = currentTime - -- Accumulate time and pose skeleton using animation. - animationTime = animationTime + delta - walkAnimation:apply(skeleton, animationTime, true) + -- Update the state with the delta time, apply it, and update the world transforms. + state:update(delta) + state:apply(skeleton) skeleton:updateWorldTransform() end) diff --git a/spine-corona/spine-corona/spine.lua b/spine-corona/spine-corona/spine.lua index bc565e2e8..4ab2243a6 100644 --- a/spine-corona/spine-corona/spine.lua +++ b/spine-corona/spine-corona/spine.lua @@ -41,6 +41,8 @@ spine.Bone = require "spine-lua.Bone" spine.Slot = require "spine-lua.Slot" spine.AttachmentLoader = require "spine-lua.AttachmentLoader" spine.Animation = require "spine-lua.Animation" +spine.AnimationStateData = require "spine-lua.AnimationStateData" +spine.AnimationState = require "spine-lua.AnimationState" spine.utils.readFile = function (fileName, base) if not base then base = system.ResourceDirectory end diff --git a/spine-love/spine-love/spine.lua b/spine-love/spine-love/spine.lua index da56e00e6..0161b6cbf 100644 --- a/spine-love/spine-love/spine.lua +++ b/spine-love/spine-love/spine.lua @@ -38,6 +38,8 @@ spine.Bone = require "spine-lua.Bone" spine.Slot = require "spine-lua.Slot" spine.AttachmentLoader = require "spine-lua.AttachmentLoader" spine.Animation = require "spine-lua.Animation" +spine.AnimationStateData = require "spine-lua.AnimationStateData" +spine.AnimationState = require "spine-lua.AnimationState" spine.utils.readFile = function (fileName, base) local path = fileName diff --git a/spine-lua/AnimationState.lua b/spine-lua/AnimationState.lua new file mode 100644 index 000000000..49f86e32b --- /dev/null +++ b/spine-lua/AnimationState.lua @@ -0,0 +1,130 @@ + ------------------------------------------------------------------------------- + -- Copyright (c) 2013, Esoteric Software + -- All rights reserved. + -- + -- Redistribution and use in source and binary forms, with or without + -- modification, are permitted provided that the following conditions are met: + -- + -- 1. Redistributions of source code must retain the above copyright notice, this + -- list of conditions and the following disclaimer. + -- 2. Redistributions in binary form must reproduce the above copyright notice, + -- this list of conditions and the following disclaimer in the documentation + -- and/or other materials provided with the distribution. + -- + -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + -- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + -- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ------------------------------------------------------------------------------ + +local AnimationState = {} + +function AnimationState.new (data) + if not data then error("data cannot be nil", 2) end + + local self = { + data = data, + animation = nil, + previous = nil, + currentTime = 0, + previousTime = 0, + currentLoop = false, + previousLoop = false, + mixTime = 0, + mixDuration = 0, + queue = {} + } + + local function setAnimationInternal (animation, loop) + self.previous = nil + if (animation and self.animation) then + self.mixDuration = data:getMix(self.animation.name, animation.name) + if (self.mixDuration > 0) then + self.mixTime = 0 + self.previous = self.animation + self.previousTime = self.currentTime + self.previousLoop = self.currentLoop + end + end + self.animation = animation + self.currentLoop = loop + self.currentTime = 0 + end + + function self:update (delta) + self.currentTime = self.currentTime + delta + self.previousTime = self.previousTime + delta + self.mixTime = self.mixTime + delta + + if (#self.queue > 0) then + local entry = self.queue[1] + if (self.currentTime >= entry.delay) then + setAnimationInternal(entry.animation, entry.loop) + table.remove(self.queue, 1) + end + end + end + + function self:apply(skeleton) + if (not self.animation) then return end + if (self.previous) then + self.previous:apply(skeleton, self.previousTime, self.previousLoop) + local alpha = self.mixTime / self.mixDuration + if (alpha >= 1) then + alpha = 1 + self.previous = nil + end + self.animation:mix(skeleton, self.currentTime, self.currentLoop, alpha) + else + self.animation:apply(skeleton, self.currentTime, self.currentLoop) + end + end + + -- Queues an animation to be played after a delay. The delay starts when the last queued animation (if any) begins. + -- The delay may be <= 0 to use duration of the previous animation minus any mix duration plus the negative delay. + function self:addAnimationWithDelay (animationName, loop, delay) + if (delay <= 0) then + -- Find the animation that is queued before this one. + local last + if (#self.queue == 0) then + last = self.animation + else + last = self.queue[#self.queue].animation + end + if (last) then + delay = last.duration - data:getMix(last.name, animationName) + delay + else + delay = 0 + end + end + local animation = nil + if animationName then animation = data.skeletonData:findAnimation(animationName) end + table.insert(self.queue, {animation = animation, loop = loop, delay = delay}) + end + + -- Queues an animation to be played after the last queued animation (if any). + function self:addAnimation (animationName, loop) + self:addAnimationWithDelay(animationName, loop, 0) + end + + -- Clears the animation queue and sets the current animation. + function self:setAnimation (animationName, loop) + self.queue = {} + local animation = nil + if animationName then animation = data.skeletonData:findAnimation(animationName) end + setAnimationInternal(animation, loop) + end + + function self:isComplete () + return (not self.animation) or self.currentTime >= self.animation.duration + end + + return self +end +return AnimationState diff --git a/spine-lua/AnimationStateData.lua b/spine-lua/AnimationStateData.lua new file mode 100644 index 000000000..29b2a3d97 --- /dev/null +++ b/spine-lua/AnimationStateData.lua @@ -0,0 +1,54 @@ + ------------------------------------------------------------------------------- + -- Copyright (c) 2013, Esoteric Software + -- All rights reserved. + -- + -- Redistribution and use in source and binary forms, with or without + -- modification, are permitted provided that the following conditions are met: + -- + -- 1. Redistributions of source code must retain the above copyright notice, this + -- list of conditions and the following disclaimer. + -- 2. Redistributions in binary form must reproduce the above copyright notice, + -- this list of conditions and the following disclaimer in the documentation + -- and/or other materials provided with the distribution. + -- + -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + -- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + -- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ------------------------------------------------------------------------------ + +local AnimationStateData = {} + +function AnimationStateData.new (skeletonData) + if not skeletonData then error("skeletonData cannot be nil", 2) end + + local self = { + animationToMixTime = {}, + skeletonData = skeletonData, + defaultMix = 0 + } + + function self:setMix (fromName, toName, duration) + if (not self.animationToMixTime[fromName]) then + self.animationToMixTime[fromName] = {} + end + self.animationToMixTime[fromName][toName] = duration + end + + function self:getMix (fromName, toName) + local first = self.animationToMixTime[fromName] + if (not first) then return self.defaultMix end + local duration = first[toName] + if (duration == nil) then return defaultMix end + return duration + end + + return self +end +return AnimationStateData