Browse Source

spine-lua: keyable draw order, events, new AnimationState.

NathanSweet 12 years ago
parent
commit
a793d2bfa6

+ 1 - 0
.gitignore

@@ -49,6 +49,7 @@ spine-unity/*.sln
 *.cachefile
 *.cachefile
 Assembly-*.csproj
 Assembly-*.csproj
 Assembly-*.pidb
 Assembly-*.pidb
+AssetStoreTools*
 
 
 spine-tk2d/Assets/Spine/spine-csharp
 spine-tk2d/Assets/Spine/spine-csharp
 !spine-tk2d/Assets/Spine/spine-csharp/Place spine-csharp here.txt
 !spine-tk2d/Assets/Spine/spine-csharp/Place spine-csharp here.txt

+ 16 - 3
spine-corona/examples/spineboy.lua

@@ -26,9 +26,22 @@ stateData:setMix("jump", "walk", 0.4)
 
 
 -- AnimationState has a queue of animations and can apply them with crossfading.
 -- AnimationState has a queue of animations and can apply them with crossfading.
 local state = spine.AnimationState.new(stateData)
 local state = spine.AnimationState.new(stateData)
-state:setAnimation("walk", false)
-state:addAnimation("jump", false)
-state:addAnimation("walk", true)
+state:setAnimationByName(0, "drawOrder")
+state:addAnimationByName(0, "jump", false, 0)
+state:addAnimationByName(0, "walk", 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 lastTime = 0
 local animationTime = 0
 local animationTime = 0

+ 5 - 6
spine-corona/spine-corona/spine.lua

@@ -47,7 +47,9 @@ spine.AttachmentLoader = require "spine-lua.AttachmentLoader"
 spine.Animation = require "spine-lua.Animation"
 spine.Animation = require "spine-lua.Animation"
 spine.AnimationStateData = require "spine-lua.AnimationStateData"
 spine.AnimationStateData = require "spine-lua.AnimationStateData"
 spine.AnimationState = require "spine-lua.AnimationState"
 spine.AnimationState = require "spine-lua.AnimationState"
- 
+spine.EventData = require "spine-lua.EventData"
+spine.Event = require "spine-lua.Event"
+
 spine.utils.readFile = function (fileName, base)
 spine.utils.readFile = function (fileName, base)
 	if not base then base = system.ResourceDirectory end
 	if not base then base = system.ResourceDirectory end
 	local path = system.pathForFile(fileName, base)
 	local path = system.pathForFile(fileName, base)
@@ -116,11 +118,6 @@ function spine.Skeleton.new (skeletonData, group)
 					end
 					end
 					if slot.data.additiveBlending then image.blendMode = "add" end
 					if slot.data.additiveBlending then image.blendMode = "add" end
 					images[slot] = image
 					images[slot] = image
-					if i < self.group.numChildren then
-						self.group:insert(i, image)
-					else
-						self.group:insert(image)
-					end
 				end
 				end
 				-- Position image based on attachment and bone.
 				-- Position image based on attachment and bone.
 				if image ~= spine.Skeleton.failed then
 				if image ~= spine.Skeleton.failed then
@@ -179,6 +176,8 @@ function spine.Skeleton.new (skeletonData, group)
 						image.lastA = a / 255
 						image.lastA = a / 255
 						image.alpha = image.lastA
 						image.alpha = image.lastA
 					end
 					end
+					
+					self.group:insert(image)
 				end
 				end
 			end
 			end
 		end
 		end

+ 165 - 56
spine-lua/Animation.lua

@@ -41,23 +41,29 @@ function Animation.new (name, timelines, duration)
 		duration = duration
 		duration = duration
 	}
 	}
 
 
-	function self:apply (skeleton, time, loop)
+	function self:apply (skeleton, lastTime, time, loop, events)
 		if not skeleton then error("skeleton cannot be nil.", 2) end
 		if not skeleton then error("skeleton cannot be nil.", 2) end
 
 
-		if loop and duration > 0 then time = time % duration end
+		if loop and duration > 0 then
+			time = time % self.duration
+			lastTime = lastTime % self.duration
+		end
 
 
 		for i,timeline in ipairs(self.timelines) do
 		for i,timeline in ipairs(self.timelines) do
-			timeline:apply(skeleton, time, 1)
+			timeline:apply(skeleton, lastTime, time, events, 1)
 		end
 		end
 	end
 	end
 
 
-	function self:mix (skeleton, time, loop, alpha)
+	function self:mix (skeleton, lastTime, time, loop, events, alpha)
 		if not skeleton then error("skeleton cannot be nil.", 2) end
 		if not skeleton then error("skeleton cannot be nil.", 2) end
 
 
-		if loop and duration > 0 then time = time % duration end
+		if loop and duration > 0 then
+			time = time % self.duration
+			lastTime = lastTime % self.duration
+		end
 
 
 		for i,timeline in ipairs(self.timelines) do
 		for i,timeline in ipairs(self.timelines) do
-			timeline:apply(skeleton, time, alpha)
+			timeline:apply(skeleton, lastTime, time, events, alpha)
 		end
 		end
 	end
 	end
 
 
@@ -97,15 +103,15 @@ function Animation.CurveTimeline.new ()
 		curves = {}
 		curves = {}
 	}
 	}
 
 
-	function self:setLinear (keyframeIndex)
-		self.curves[keyframeIndex * 6] = LINEAR
+	function self:setLinear (frameIndex)
+		self.curves[frameIndex * 6] = LINEAR
 	end
 	end
 
 
-	function self:setStepped (keyframeIndex)
-		self.curves[keyframeIndex * 6] = STEPPED
+	function self:setStepped (frameIndex)
+		self.curves[frameIndex * 6] = STEPPED
 	end
 	end
 
 
-	function self:setCurve (keyframeIndex, cx1, cy1, cx2, cy2)
+	function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
 		local subdiv_step = 1 / BEZIER_SEGMENTS
 		local subdiv_step = 1 / BEZIER_SEGMENTS
 		local subdiv_step2 = subdiv_step * subdiv_step
 		local subdiv_step2 = subdiv_step * subdiv_step
 		local subdiv_step3 = subdiv_step2 * subdiv_step
 		local subdiv_step3 = subdiv_step2 * subdiv_step
@@ -117,7 +123,7 @@ function Animation.CurveTimeline.new ()
 		local tmp1y = -cy1 * 2 + cy2
 		local tmp1y = -cy1 * 2 + cy2
 		local tmp2x = (cx1 - cx2) * 3 + 1
 		local tmp2x = (cx1 - cx2) * 3 + 1
 		local tmp2y = (cy1 - cy2) * 3 + 1
 		local tmp2y = (cy1 - cy2) * 3 + 1
-		local i = keyframeIndex * 6
+		local i = frameIndex * 6
 		local curves = self.curves
 		local curves = self.curves
 		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3
 		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3
 		curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3
 		curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3
@@ -127,8 +133,8 @@ function Animation.CurveTimeline.new ()
 		curves[i + 5] = tmp2y * pre5
 		curves[i + 5] = tmp2y * pre5
 	end
 	end
 
 
-	function self:getCurvePercent (keyframeIndex, percent)
-		local curveIndex = keyframeIndex * 6
+	function self:getCurvePercent (frameIndex, percent)
+		local curveIndex = frameIndex * 6
 		local curves = self.curves
 		local curves = self.curves
 		local dfx = curves[curveIndex]
 		local dfx = curves[curveIndex]
 		if not dfx then return percent end -- linear
 		if not dfx then return percent end -- linear
@@ -175,17 +181,17 @@ function Animation.RotateTimeline.new ()
 		return self.frames[#self.frames - 1]
 		return self.frames[#self.frames - 1]
 	end
 	end
 
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return (#self.frames + 1) / 2
 		return (#self.frames + 1) / 2
 	end
 	end
 
 
-	function self:setKeyframe (keyframeIndex, time, value)
-		keyframeIndex = keyframeIndex * 2
-		self.frames[keyframeIndex] = time
-		self.frames[keyframeIndex + 1] = value
+	function self:setFrame (frameIndex, time, value)
+		frameIndex = frameIndex * 2
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + 1] = value
 	end
 	end
 
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 		if time < frames[0] then return end -- Time is before first frame.
 
 
@@ -245,18 +251,18 @@ function Animation.TranslateTimeline.new ()
 		return self.frames[#self.frames - 2]
 		return self.frames[#self.frames - 2]
 	end
 	end
 
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return (#self.frames + 1) / 3
 		return (#self.frames + 1) / 3
 	end
 	end
 
 
-	function self:setKeyframe (keyframeIndex, time, x, y)
-		keyframeIndex = keyframeIndex * 3
-		self.frames[keyframeIndex] = time
-		self.frames[keyframeIndex + 1] = x
-		self.frames[keyframeIndex + 2] = y
+	function self:setFrame (frameIndex, time, x, y)
+		frameIndex = frameIndex * 3
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + 1] = x
+		self.frames[frameIndex + 2] = y
 	end
 	end
 
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 		if time < frames[0] then return end -- Time is before first frame.
 
 
@@ -292,7 +298,7 @@ function Animation.ScaleTimeline.new ()
 
 
 	local self = Animation.TranslateTimeline.new()
 	local self = Animation.TranslateTimeline.new()
 
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 		if time < frames[0] then return end -- Time is before first frame.
 
 
@@ -336,20 +342,20 @@ function Animation.ColorTimeline.new ()
 		return self.frames[#self.frames - 4]
 		return self.frames[#self.frames - 4]
 	end
 	end
 
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return (#self.frames + 1) / 5
 		return (#self.frames + 1) / 5
 	end
 	end
 
 
-	function self:setKeyframe (keyframeIndex, time, r, g, b, a)
-		keyframeIndex = keyframeIndex * 5
-		self.frames[keyframeIndex] = time
-		self.frames[keyframeIndex + 1] = r
-		self.frames[keyframeIndex + 2] = g
-		self.frames[keyframeIndex + 3] = b
-		self.frames[keyframeIndex + 4] = a
+	function self:setFrame (frameIndex, time, r, g, b, a)
+		frameIndex = frameIndex * 5
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + 1] = r
+		self.frames[frameIndex + 2] = g
+		self.frames[frameIndex + 3] = b
+		self.frames[frameIndex + 4] = a
 	end
 	end
 
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 		if time < frames[0] then return end -- Time is before first frame.
 
 
@@ -391,25 +397,26 @@ end
 
 
 Animation.AttachmentTimeline = {}
 Animation.AttachmentTimeline = {}
 function Animation.AttachmentTimeline.new ()
 function Animation.AttachmentTimeline.new ()
-	local self = Animation.CurveTimeline.new()
-	self.frames = {}
-	self.attachmentNames = {}
-	self.slotName = nil
+	local self = {
+		frames = {},
+		attachmentNames = {},
+		slotName = nil
+	}
 
 
 	function self:getDuration ()
 	function self:getDuration ()
 		return self.frames[#self.frames]
 		return self.frames[#self.frames]
 	end
 	end
 
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return #self.frames + 1
 		return #self.frames + 1
 	end
 	end
 
 
-	function self:setKeyframe (keyframeIndex, time, attachmentName)
-		self.frames[keyframeIndex] = time
-		self.attachmentNames[keyframeIndex] = attachmentName
+	function self:setFrame (frameIndex, time, attachmentName)
+		self.frames[frameIndex] = time
+		self.attachmentNames[frameIndex] = attachmentName
 	end
 	end
 
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 		if time < frames[0] then return end -- Time is before first frame.
 
 
@@ -421,16 +428,118 @@ function Animation.AttachmentTimeline.new ()
 		end
 		end
 
 
 		local attachmentName = self.attachmentNames[frameIndex]
 		local attachmentName = self.attachmentNames[frameIndex]
-    local slot = skeleton.slotsByName[self.slotName]
-    if attachmentName then
-      if not slot.attachment then
-        slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
-      elseif slot.attachment.name ~= attachmentName then
-        slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
-      end
-    else
-      slot:setAttachment(nil)
-    end
+		local slot = skeleton.slotsByName[self.slotName]
+		if attachmentName then
+			if not slot.attachment then
+				slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
+			elseif slot.attachment.name ~= attachmentName then
+				slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
+			end
+		else
+			slot:setAttachment(nil)
+		end
+	end
+
+	return self
+end
+
+Animation.EventTimeline = {}
+function Animation.EventTimeline.new ()
+	local self = {
+		frames = {},
+		events = {}
+	}
+
+	function self:getDuration ()
+		return self.frames[#self.frames]
+	end
+
+	function self:getFrameCount ()
+		return #self.frames + 1
+	end
+
+	function self:setFrame (frameIndex, time, event)
+		self.frames[frameIndex] = time
+		self.events[frameIndex] = event
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
+		if not firedEvents then return end
+
+		local frames = self.frames
+		local frameCount = #frames
+		if lastTime >= frames[frameCount] then return end -- Last time is after last frame.
+		frameCount = frameCount + 1
+
+		if lastTime > time then -- Fire events after last time for looped animations.
+			self:apply(skeleton, lastTime, 999999, firedEvents, alpha)
+			lastTime = 0
+		end
+
+		local frameIndex
+		if lastTime <= frames[0] or frameCount == 1 then
+			frameIndex = 0
+		else
+			frameIndex = binarySearch(frames, lastTime, 1)
+			local frame = frames[frameIndex]
+			while frameIndex > 0 do -- Fire multiple events with the same frame.
+				if frames[frameIndex - 1] ~= frame then break end
+				frameIndex = frameIndex - 1
+			end
+		end
+		local events = self.events
+		while frameIndex < frameCount and time >= frames[frameIndex] do
+			table.insert(firedEvents, events[frameIndex])
+			frameIndex = frameIndex + 1
+		end
+	end
+
+	return self
+end
+
+Animation.DrawOrderTimeline = {}
+function Animation.DrawOrderTimeline.new ()
+	local self = {
+		frames = {},
+		drawOrders = {}
+	}
+
+	function self:getDuration ()
+		return self.frames[#self.frames]
+	end
+
+	function self:getFrameCount ()
+		return #self.frames + 1
+	end
+
+	function self:setFrame (frameIndex, time, drawOrder)
+		self.frames[frameIndex] = time
+		self.drawOrders[frameIndex] = drawOrder
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local frameIndex
+		if time >= frames[#frames] then -- Time is after last frame.
+			frameIndex = #frames
+		else
+			frameIndex = binarySearch(frames, time, 1) - 1
+		end
+
+		local drawOrder = skeleton.drawOrder
+		local slots = skeleton.slots
+		local drawOrderToSetupIndex = self.drawOrders[frameIndex]
+		if not drawOrderToSetupIndex then
+			for i,slot in ipairs(slots) do
+				drawOrder[i] = slots[i]
+			end
+		else
+			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
+				drawOrder[i] = skeleton.slots[setupIndex]
+			end
+		end
 	end
 	end
 
 
 	return self
 	return self

+ 167 - 69
spine-lua/AnimationState.lua

@@ -38,101 +38,199 @@ function AnimationState.new (data)
 
 
 	local self = {
 	local self = {
 		data = data,
 		data = data,
-		animation = nil,
-		previous = nil,
-		currentTime = 0,
-		previousTime = 0,
-		currentLoop = false,
-		previousLoop = false,
-		mixTime = 0,
-		mixDuration = 0,
-		queue = {}
+		tracks = {},
+		events = {},
+		onStart = nil, onEnd = nil, onComplete = nil, onEvent = nil,
+		timeScale = 1
 	}
 	}
 
 
-	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
+	local function setCurrent (index, entry)
+		local current = self.tracks[index]
+		if current then
+			current.previous = nil
+			
+			if current.onEnd then current.onEnd(index) end
+			if self.onEnd then self.onEnd(index) end
+			
+			entry.mixDuration = self.data:getMix(current.animation.name, entry.animation.name)
+			if entry.mixDuration > 0 then
+				entry.mixTime = 0
+				entry.previous = current
 			end
 			end
 		end
 		end
-		self.animation = animation
-		self.currentLoop = loop
-		self.currentTime = 0
+
+		self.tracks[index] = entry
+
+		if entry.onStart then entry.onStart(index) end
+		if self.onStart then self.onStart(index) end
 	end
 	end
 
 
 	function self:update (delta)
 	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)
+		delta = delta * self.timeScale
+		for i,current in pairs(self.tracks) do
+			if current then
+				local trackDelta = delta * current.timeScale
+				local time = current.time + trackDelta
+				local endTime = current.endTime
+				
+				current.time = time
+				if current.previous then
+					current.previous.time = current.previous.time + trackDelta
+					current.mixTime = current.mixTime + trackDelta
+				end
+				
+				-- Check if completed the animation or a loop iteration.
+				local complete
+				if current.loop then
+					complete = current.lastTime % endTime > time % endTime
+				else
+					complete = current.lastTime < endTime and time >= endTime
+				end
+				if complete then 
+					local count = math.floor(time / endTime)
+					if current.onComplete then current.onComplete(i, count) end
+					if self.onComplete then self.onComplete(i, count) end
+				end
+				
+				local next = current.next
+				if next then
+					if time - trackDelta > next.delay then setCurrent(i, next) end
+				else
+					-- End non-looping animation when it reaches its end time and there is no next entry.
+					if not current.loop and current.lastTime >= current.endTime then self:clearTrack(i) end
+				end
 			end
 			end
 		end
 		end
 	end
 	end
 	
 	
 	function self:apply(skeleton)
 	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
+		for i,current in pairs(self.tracks) do
+			if current then			
+				local time = current.time
+				local loop = current.loop
+				if not loop and time > current.endTime then time = current.endTime end
+				
+				local previous = current.previous
+				if not previous then
+					current.animation:apply(skeleton, current.lastTime, time, loop, self.events)
+				else
+					local previousTime = previous.time
+					if not previous.loop and previousTime > previous.endTime then previousTime = previous.endTime end
+					previous.animation:apply(skeleton, previousTime, previousTime, previous.loop, nil)
+					
+					local alpha = current.mixTime / current.mixDuration
+					if alpha >= 1 then
+						alpha = 1
+						current.previous = nil
+					end
+					current.animation:mix(skeleton, current.lastTime, time, loop, self.events, alpha)
+				end
+				
+				local eventCount = #self.events
+				for ii = 1, eventCount, 1 do
+					local event = self.events[ii]
+					if current.onEvent then current.onEvent(i, event) end
+					if self.onEvent then self.onEvent(i, event) end
+				end
+				for ii = 1, eventCount, 1 do
+					table.remove(self.events)
+				end
+
+				current.lastTime = current.time
 			end
 			end
-			self.animation:mix(skeleton, self.currentTime, self.currentLoop, alpha)
-		else
-			self.animation:apply(skeleton, self.currentTime, self.currentLoop)
 		end
 		end
 	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
+	function self:clearTracks ()
+		for i,current in pairs(self.tracks) do
+			self.clearTrack(i)
+		end
+		self.tracks = {}
+	end
+	
+	function self:clearTrack (trackIndex)
+		local current = self.tracks[trackIndex]
+		if not current then return end
+
+		if current.onEnd then current.onEnd(trackIndex) end
+		if self.onEnd then self.onEnd(trackIndex) end
+
+		self.tracks[trackIndex] = nil
+	end
+	
+	function self:setAnimationByName (trackIndex, animationName, loop)
+		local animation = self.data.skeletonData:findAnimation(animationName)
+		if not animation then error("Animation not found: " + animationName) end
+		return self:setAnimation(trackIndex, animation, loop)
+	end
+
+	-- Set the current animation. Any queued animations are cleared.
+	function self:setAnimation (trackIndex, animation, loop)
+		local entry = AnimationState.TrackEntry.new()
+		entry.animation = animation
+		entry.loop = loop
+		entry.endTime = animation.duration
+		setCurrent(trackIndex, entry)
+		return entry
+	end
+	
+	function self:addAnimationByName (trackIndex, animationName, loop, delay)
+		local animation = self.data.skeletonData:findAnimation(animationName)
+		if not animation then error("Animation not found: " + animationName) end
+		return self:addAnimation(trackIndex, animation, loop, delay)
+	end
+
+	-- Adds an animation to be played delay seconds after the current or last queued animation.
+	-- @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay.
+	function self:addAnimation (trackIndex, animation, loop, delay)
+		local entry = AnimationState.TrackEntry.new()
+		entry.animation = animation
+		entry.loop = loop
+		entry.endTime = animation.duration
+		
+		local last = self.tracks[trackIndex]
+		if last then
+			while (last.next) do
+				last = last.next
 			end
 			end
-			if (last) then
-				delay = last.duration - data:getMix(last.name, animationName) + delay
+			last.next = entry
+		else
+			self.tracks[trackIndex] = entry
+		end
+		
+		delay = delay or 0
+		if delay <= 0 then
+			if last then
+				delay = delay + last.endTime - self.data:getMix(last.animation.name, animation.name)
 			else
 			else
 				delay = 0
 				delay = 0
 			end
 			end
 		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)
+		entry.delay = delay
+		
+		return entry
 	end
 	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) 
+	-- May return nil.
+	function self:getCurrent (trackIndex)
+		return self.tracks[trackIndex]
 	end
 	end
 
 
-	function self:isComplete ()
-		return (not self.animation) or self.currentTime >= self.animation.duration
-	end
+	return self
+end
 
 
+AnimationState.TrackEntry = {}
+function AnimationState.TrackEntry.new (data)
+	local self = {
+		next = nil, previous = nil,
+		animation = nil,
+		loop = false,
+		delay = 0, time = 0, lastTime = 0, endTime = 0,
+		timeScale = 1,
+		mixTime = 0, mixDuration = 0,
+		onStart = nil, onEnd = nil, onComplete = nil, onEvent = nil
+	}
 	return self
 	return self
 end
 end
+
 return AnimationState
 return AnimationState

+ 13 - 13
spine-lua/AnimationStateData.lua

@@ -42,20 +42,20 @@ function AnimationStateData.new (skeletonData)
 		defaultMix = 0
 		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)
+	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]
 		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
+		if not first then return self.defaultMix end
+		local duration = first[toName]
+		if not duration then return defaultMix end
+		return duration
+	end
 
 
 	return self
 	return self
 end
 end

+ 4 - 1
spine-lua/Bone.lua

@@ -38,7 +38,10 @@ function Bone.new (data, parent)
 	
 	
 	local self = {
 	local self = {
 		data = data,
 		data = data,
-		parent = parent
+		parent = parent,
+		x = 0, y = 0,
+		rotation = 0,
+		scaleX = 1, scaleY = 1
 	}
 	}
 
 
 	function self:updateWorldTransform (flipX, flipY)
 	function self:updateWorldTransform (flipX, flipY)

+ 47 - 0
spine-lua/Event.lua

@@ -0,0 +1,47 @@
+------------------------------------------------------------------------------
+ -- Spine Runtime Software License - Version 1.0
+ -- 
+ -- Copyright (c) 2013, Esoteric Software
+ -- All rights reserved.
+ -- 
+ -- Redistribution and use in source and binary forms in whole or in part, with
+ -- or without modification, are permitted provided that the following conditions
+ -- are met:
+ -- 
+ -- 1. A Spine Essential, Professional, Enterprise, or Education License must
+ --    be purchased from Esoteric Software and the license must remain valid:
+ --    http://esotericsoftware.com/
+ -- 2. Redistributions of source code must retain this license, which is the
+ --    above copyright notice, this declaration of conditions and the following
+ --    disclaimer.
+ -- 3. Redistributions in binary form must reproduce this license, which is the
+ --    above copyright notice, this declaration 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 Event = {}
+function Event.new (data)
+	if not data then error("data cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		intValue = 0,
+		floatValue = 0,
+		stringValue = nil
+	}
+
+	return self
+end
+return Event

+ 47 - 0
spine-lua/EventData.lua

@@ -0,0 +1,47 @@
+------------------------------------------------------------------------------
+ -- Spine Runtime Software License - Version 1.0
+ -- 
+ -- Copyright (c) 2013, Esoteric Software
+ -- All rights reserved.
+ -- 
+ -- Redistribution and use in source and binary forms in whole or in part, with
+ -- or without modification, are permitted provided that the following conditions
+ -- are met:
+ -- 
+ -- 1. A Spine Essential, Professional, Enterprise, or Education License must
+ --    be purchased from Esoteric Software and the license must remain valid:
+ --    http://esotericsoftware.com/
+ -- 2. Redistributions of source code must retain this license, which is the
+ --    above copyright notice, this declaration of conditions and the following
+ --    disclaimer.
+ -- 3. Redistributions in binary form must reproduce this license, which is the
+ --    above copyright notice, this declaration 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 EventData = {}
+function EventData.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		intValue = 0,
+		floatValue = 0,
+		stringValue = nil
+	}
+
+	return self
+end
+return EventData

+ 11 - 2
spine-lua/SkeletonData.lua

@@ -36,8 +36,9 @@ function SkeletonData.new ()
 	local self = {
 	local self = {
 		bones = {},
 		bones = {},
 		slots = {},
 		slots = {},
-    slotNameIndices = {},
+		slotNameIndices = {},
 		skins = {},
 		skins = {},
+		events = {},
 		animations = {}
 		animations = {}
 	}
 	}
 
 
@@ -67,7 +68,7 @@ function SkeletonData.new ()
 
 
 	function self:findSlotIndex (slotName)
 	function self:findSlotIndex (slotName)
 		if not slotName then error("slotName cannot be nil.", 2) end
 		if not slotName then error("slotName cannot be nil.", 2) end
-		return slotNameIndices[slotName] or -1
+		return self.slotNameIndices[slotName] or -1
 	end
 	end
 
 
 	function self:findSkin (skinName)
 	function self:findSkin (skinName)
@@ -78,6 +79,14 @@ function SkeletonData.new ()
 		return nil
 		return nil
 	end
 	end
 
 
+	function self:findEvent (eventName)
+		if not eventName then error("eventName cannot be nil.", 2) end
+		for i,event in ipairs(self.events) do
+			if event.name == eventName then return event end
+		end
+		return nil
+	end
+
 	function self:findAnimation (animationName)
 	function self:findAnimation (animationName)
 		if not animationName then error("animationName cannot be nil.", 2) end
 		if not animationName then error("animationName cannot be nil.", 2) end
 		for i,animation in ipairs(self.animations) do
 		for i,animation in ipairs(self.animations) do

+ 95 - 15
spine-lua/SkeletonJson.lua

@@ -37,6 +37,9 @@ local SlotData = require "spine-lua.SlotData"
 local Skin = require "spine-lua.Skin"
 local Skin = require "spine-lua.Skin"
 local AttachmentLoader = require "spine-lua.AttachmentLoader"
 local AttachmentLoader = require "spine-lua.AttachmentLoader"
 local Animation = require "spine-lua.Animation"
 local Animation = require "spine-lua.Animation"
+local EventData = require "spine-lua.EventData"
+local Event = require "spine-lua.Event"
+
 local TIMELINE_SCALE = "scale"
 local TIMELINE_SCALE = "scale"
 local TIMELINE_ROTATE = "rotate"
 local TIMELINE_ROTATE = "rotate"
 local TIMELINE_TRANSLATE = "translate"
 local TIMELINE_TRANSLATE = "translate"
@@ -123,9 +126,8 @@ function SkeletonJson.new (attachmentLoader)
 		end
 		end
 
 
 		-- Skins.
 		-- Skins.
-		local map = root["skins"]
-		if map then
-			for skinName,skinMap in pairs(map) do
+		if root["skins"] then
+			for skinName,skinMap in pairs(root["skins"]) do
 				local skin = Skin.new(skinName)
 				local skin = Skin.new(skinName)
 				for slotName,slotMap in pairs(skinMap) do
 				for slotName,slotMap in pairs(skinMap) do
 					local slotIndex = skeletonData.slotNameIndices[slotName]
 					local slotIndex = skeletonData.slotNameIndices[slotName]
@@ -144,10 +146,20 @@ function SkeletonJson.new (attachmentLoader)
 			end
 			end
 		end
 		end
 
 
+		-- Events.
+		if root["events"] then
+			for eventName,eventMap in pairs(root["events"]) do
+				local eventData = EventData.new(eventName)
+				eventData.intValue = eventMap["int"] or 0
+				eventData.floatValue = eventMap["float"] or 0
+				eventData.stringValue = eventMap["string"]
+				table.insert(skeletonData.events, eventData)
+			end
+		end
+
 		-- Animations.
 		-- Animations.
-		map = root["animations"]
-		if map then
-			for animationName,animationMap in pairs(map) do
+		if root["animations"] then
+			for animationName,animationMap in pairs(root["animations"]) do
 				readAnimation(animationName, animationMap, skeletonData)
 				readAnimation(animationName, animationMap, skeletonData)
 			end
 			end
 		end
 		end
@@ -190,7 +202,7 @@ function SkeletonJson.new (attachmentLoader)
 						local keyframeIndex = 0
 						local keyframeIndex = 0
 						for i,valueMap in ipairs(values) do
 						for i,valueMap in ipairs(values) do
 							local time = valueMap["time"]
 							local time = valueMap["time"]
-							timeline:setKeyframe(keyframeIndex, time, valueMap["angle"])
+							timeline:setFrame(keyframeIndex, time, valueMap["angle"])
 							readCurve(timeline, keyframeIndex, valueMap)
 							readCurve(timeline, keyframeIndex, valueMap)
 							keyframeIndex = keyframeIndex + 1
 							keyframeIndex = keyframeIndex + 1
 						end
 						end
@@ -213,7 +225,7 @@ function SkeletonJson.new (attachmentLoader)
 							local time = valueMap["time"]
 							local time = valueMap["time"]
 							local x = (valueMap["x"] or 0) * timelineScale
 							local x = (valueMap["x"] or 0) * timelineScale
 							local y = (valueMap["y"] or 0) * timelineScale
 							local y = (valueMap["y"] or 0) * timelineScale
-							timeline:setKeyframe(keyframeIndex, time, x, y)
+							timeline:setFrame(keyframeIndex, time, x, y)
 							readCurve(timeline, keyframeIndex, valueMap)
 							readCurve(timeline, keyframeIndex, valueMap)
 							keyframeIndex = keyframeIndex + 1
 							keyframeIndex = keyframeIndex + 1
 						end
 						end
@@ -241,7 +253,7 @@ function SkeletonJson.new (attachmentLoader)
 						for i,valueMap in ipairs(values) do
 						for i,valueMap in ipairs(values) do
 							local time = valueMap["time"]
 							local time = valueMap["time"]
 							local color = valueMap["color"]
 							local color = valueMap["color"]
-							timeline:setKeyframe(
+							timeline:setFrame(
 								keyframeIndex, time, 
 								keyframeIndex, time, 
 								tonumber(color:sub(1, 2), 16),
 								tonumber(color:sub(1, 2), 16),
 								tonumber(color:sub(3, 4), 16),
 								tonumber(color:sub(3, 4), 16),
@@ -258,13 +270,13 @@ function SkeletonJson.new (attachmentLoader)
 						local timeline = Animation.AttachmentTimeline.new()
 						local timeline = Animation.AttachmentTimeline.new()
 						timeline.slotName = slotName
 						timeline.slotName = slotName
 
 
-						local keyframeIndex = 0
+						local frameIndex = 0
 						for i,valueMap in ipairs(values) do
 						for i,valueMap in ipairs(values) do
 							local time = valueMap["time"]
 							local time = valueMap["time"]
 							local attachmentName = valueMap["name"]
 							local attachmentName = valueMap["name"]
 							if not attachmentName then attachmentName = nil end
 							if not attachmentName then attachmentName = nil end
-							timeline:setKeyframe(keyframeIndex, time, attachmentName)
-							keyframeIndex = keyframeIndex + 1
+							timeline:setFrame(frameIndex, time, attachmentName)
+							frameIndex = frameIndex + 1
 						end
 						end
 						table.insert(timelines, timeline)
 						table.insert(timelines, timeline)
 						duration = math.max(duration, timeline:getDuration())
 						duration = math.max(duration, timeline:getDuration())
@@ -276,16 +288,84 @@ function SkeletonJson.new (attachmentLoader)
 			end
 			end
 		end
 		end
 
 
+		local events = map["events"]
+		if events then
+			local timeline = Animation.EventTimeline.new(#events)
+			local frameIndex = 0
+			for i,eventMap in ipairs(events) do
+				local eventData = skeletonData:findEvent(eventMap["name"])
+				if not eventData then error("Event not found: " + eventMap["name"]) end
+				local event = Event.new(eventData)
+				event.intValue = eventMap["int"] or eventData.intValue
+				event.floatValue = eventMap["float"] or eventData.floatValue
+				event.stringValue = eventMap["string"] or eventData.stringValue
+				timeline:setFrame(frameIndex, eventMap["time"], event)
+				frameIndex = frameIndex + 1
+			end
+			table.insert(timelines, timeline)
+			duration = math.max(duration, timeline:getDuration())
+		end
+
+		local drawOrderValues = map["draworder"]
+		if drawOrderValues then
+			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
+			local slotCount = #skeletonData.slots
+			local frameIndex = 0
+			for i,drawOrderMap in ipairs(drawOrderValues) do
+				local drawOrder = nil
+				if drawOrderMap["offsets"] then
+					drawOrder = {}
+					for ii = 1, slotCount do
+						drawOrder[ii] = -1
+					end
+					local offsets = drawOrderMap["offsets"]
+					local unchanged = {}
+					local originalIndex = 0
+					local unchangedIndex = 0
+					for ii,offsetMap in ipairs(offsets) do
+						local slotIndex = skeletonData:findSlotIndex(offsetMap["slot"])
+						if slotIndex == -1 then error("Slot not found: " + offsetMap["slot"]) end
+						-- Collect unchanged items.
+						while originalIndex ~= slotIndex do
+							unchanged[unchangedIndex] = originalIndex
+							unchangedIndex = unchangedIndex + 1
+							originalIndex = originalIndex + 1
+						end
+						-- Set changed items.
+						drawOrder[originalIndex + offsetMap["offset"]] = originalIndex
+						originalIndex = originalIndex + 1
+					end
+					-- Collect remaining unchanged items.
+					while originalIndex < slotCount do
+						unchanged[unchangedIndex] = originalIndex
+						unchangedIndex = unchangedIndex + 1
+						originalIndex = originalIndex + 1
+					end
+					-- Fill in unchanged items.
+					for ii = slotCount, 1, -1 do
+						if drawOrder[ii] == -1 then
+							drawOrder[ii] = unchanged[unchangedIndex]
+							unchangedIndex = unchangedIndex - 1
+						end
+					end
+				end
+				timeline:setFrame(frameIndex, drawOrderMap["time"], drawOrder)
+				frameIndex = frameIndex + 1
+			end
+			table.insert(timelines, timeline)
+			duration = math.max(duration, timeline:getDuration())
+		end
+
 		table.insert(skeletonData.animations, Animation.new(name, timelines, duration))
 		table.insert(skeletonData.animations, Animation.new(name, timelines, duration))
 	end
 	end
 
 
-	readCurve = function (timeline, keyframeIndex, valueMap)
+	readCurve = function (timeline, frameIndex, valueMap)
 		local curve = valueMap["curve"]
 		local curve = valueMap["curve"]
 		if not curve then return end
 		if not curve then return end
 		if curve == "stepped" then
 		if curve == "stepped" then
-			timeline:setStepped(keyframeIndex)
+			timeline:setStepped(frameIndex)
 		else
 		else
-			timeline:setCurve(keyframeIndex, curve[1], curve[2], curve[3], curve[4])
+			timeline:setCurve(frameIndex, curve[1], curve[2], curve[3], curve[4])
 		end
 		end
 	end
 	end
 
 

+ 15 - 22
spine-lua/utils.lua

@@ -36,36 +36,29 @@ local utils = {}
 function tablePrint (tt, indent, done)
 function tablePrint (tt, indent, done)
 	done = done or {}
 	done = done or {}
 	indent = indent or 0
 	indent = indent or 0
-	if type(tt) == "table" then
-		local sb = {}
-		for key, value in pairs (tt) do
-			table.insert(sb, string.rep (" ", indent)) -- indent it
-			if type (value) == "table" and not done [value] then
-				done [value] = true
-				table.insert(sb, "{\n");
-				table.insert(sb, tablePrint (value, indent + 2, done))
-				table.insert(sb, string.rep (" ", indent)) -- indent it
-				table.insert(sb, "}\n");
-			elseif "number" == type(key) then
-				table.insert(sb, string.format("\"%s\"\n", tostring(value)))
-			else
-				table.insert(sb, string.format(
-					"%s = \"%s\"\n", tostring (key), tostring(value)))
-			end
+	for key, value in pairs(tt) do
+		local spaces = string.rep (" ", indent)
+		if type(value) == "table" and not done [value] then
+			done [value] = true
+			print(spaces .. "{")
+			utils.print(value, indent + 2, done)
+			print(spaces .. "}")
+		else
+			io.write(spaces .. tostring(key) .. " = ")
+			utils.print(value, indent + 2, done)
 		end
 		end
-		return table.concat(sb)
-	else
-		return tt .. "\n"
 	end
 	end
 end
 end
 
 
-function utils.print (value)
+function utils.print (value, indent, done)
 	if "nil" == type(value) then
 	if "nil" == type(value) then
 		print(tostring(nil))
 		print(tostring(nil))
 	elseif "table" == type(value) then
 	elseif "table" == type(value) then
-		print(tablePrint(value))
+		print("{")
+		tablePrint(value, 2)
+		print("}")
 	elseif "string" == type(value) then
 	elseif "string" == type(value) then
-		print(value)
+		print("\"" .. value .. "\"")
 	else
 	else
 		print(tostring(value))
 		print(tostring(value))
 	end
 	end