Browse Source

Added AnimationState to spine-lua.

NathanSweet 12 years ago
parent
commit
6de19dc914

+ 14 - 3
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)
 

+ 2 - 0
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

+ 2 - 0
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

+ 130 - 0
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

+ 54 - 0
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