浏览代码

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

NathanSweet 12 年之前
父节点
当前提交
a793d2bfa6

+ 1 - 0
.gitignore

@@ -49,6 +49,7 @@ spine-unity/*.sln
 *.cachefile
 Assembly-*.csproj
 Assembly-*.pidb
+AssetStoreTools*
 
 spine-tk2d/Assets/Spine/spine-csharp
 !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.
 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 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.AnimationStateData = require "spine-lua.AnimationStateData"
 spine.AnimationState = require "spine-lua.AnimationState"
- 
+spine.EventData = require "spine-lua.EventData"
+spine.Event = require "spine-lua.Event"
+
 spine.utils.readFile = function (fileName, base)
 	if not base then base = system.ResourceDirectory end
 	local path = system.pathForFile(fileName, base)
@@ -116,11 +118,6 @@ function spine.Skeleton.new (skeletonData, group)
 					end
 					if slot.data.additiveBlending then image.blendMode = "add" end
 					images[slot] = image
-					if i < self.group.numChildren then
-						self.group:insert(i, image)
-					else
-						self.group:insert(image)
-					end
 				end
 				-- Position image based on attachment and bone.
 				if image ~= spine.Skeleton.failed then
@@ -179,6 +176,8 @@ function spine.Skeleton.new (skeletonData, group)
 						image.lastA = a / 255
 						image.alpha = image.lastA
 					end
+					
+					self.group:insert(image)
 				end
 			end
 		end

+ 165 - 56
spine-lua/Animation.lua

@@ -41,23 +41,29 @@ function Animation.new (name, timelines, 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 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
-			timeline:apply(skeleton, time, 1)
+			timeline:apply(skeleton, lastTime, time, events, 1)
 		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 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
-			timeline:apply(skeleton, time, alpha)
+			timeline:apply(skeleton, lastTime, time, events, alpha)
 		end
 	end
 
@@ -97,15 +103,15 @@ function Animation.CurveTimeline.new ()
 		curves = {}
 	}
 
-	function self:setLinear (keyframeIndex)
-		self.curves[keyframeIndex * 6] = LINEAR
+	function self:setLinear (frameIndex)
+		self.curves[frameIndex * 6] = LINEAR
 	end
 
-	function self:setStepped (keyframeIndex)
-		self.curves[keyframeIndex * 6] = STEPPED
+	function self:setStepped (frameIndex)
+		self.curves[frameIndex * 6] = STEPPED
 	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_step2 = subdiv_step * subdiv_step
 		local subdiv_step3 = subdiv_step2 * subdiv_step
@@ -117,7 +123,7 @@ function Animation.CurveTimeline.new ()
 		local tmp1y = -cy1 * 2 + cy2
 		local tmp2x = (cx1 - cx2) * 3 + 1
 		local tmp2y = (cy1 - cy2) * 3 + 1
-		local i = keyframeIndex * 6
+		local i = frameIndex * 6
 		local curves = self.curves
 		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * 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
 	end
 
-	function self:getCurvePercent (keyframeIndex, percent)
-		local curveIndex = keyframeIndex * 6
+	function self:getCurvePercent (frameIndex, percent)
+		local curveIndex = frameIndex * 6
 		local curves = self.curves
 		local dfx = curves[curveIndex]
 		if not dfx then return percent end -- linear
@@ -175,17 +181,17 @@ function Animation.RotateTimeline.new ()
 		return self.frames[#self.frames - 1]
 	end
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return (#self.frames + 1) / 2
 	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
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		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]
 	end
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return (#self.frames + 1) / 3
 	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
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		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()
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		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]
 	end
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return (#self.frames + 1) / 5
 	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
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 
@@ -391,25 +397,26 @@ end
 
 Animation.AttachmentTimeline = {}
 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 ()
 		return self.frames[#self.frames]
 	end
 
-	function self:getKeyframeCount ()
+	function self:getFrameCount ()
 		return #self.frames + 1
 	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
 
-	function self:apply (skeleton, time, alpha)
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 
@@ -421,16 +428,118 @@ function Animation.AttachmentTimeline.new ()
 		end
 
 		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
 
 	return self

+ 167 - 69
spine-lua/AnimationState.lua

@@ -38,101 +38,199 @@ function AnimationState.new (data)
 
 	local self = {
 		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
-		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
 
 	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
 	
 	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
-			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
+	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
-			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
 				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)
+		entry.delay = delay
+		
+		return entry
 	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
 
-	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
 end
+
 return AnimationState

+ 13 - 13
spine-lua/AnimationStateData.lua

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

+ 4 - 1
spine-lua/Bone.lua

@@ -38,7 +38,10 @@ function Bone.new (data, parent)
 	
 	local self = {
 		data = data,
-		parent = parent
+		parent = parent,
+		x = 0, y = 0,
+		rotation = 0,
+		scaleX = 1, scaleY = 1
 	}
 
 	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 = {
 		bones = {},
 		slots = {},
-    slotNameIndices = {},
+		slotNameIndices = {},
 		skins = {},
+		events = {},
 		animations = {}
 	}
 
@@ -67,7 +68,7 @@ function SkeletonData.new ()
 
 	function self:findSlotIndex (slotName)
 		if not slotName then error("slotName cannot be nil.", 2) end
-		return slotNameIndices[slotName] or -1
+		return self.slotNameIndices[slotName] or -1
 	end
 
 	function self:findSkin (skinName)
@@ -78,6 +79,14 @@ function SkeletonData.new ()
 		return nil
 	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)
 		if not animationName then error("animationName cannot be nil.", 2) end
 		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 AttachmentLoader = require "spine-lua.AttachmentLoader"
 local Animation = require "spine-lua.Animation"
+local EventData = require "spine-lua.EventData"
+local Event = require "spine-lua.Event"
+
 local TIMELINE_SCALE = "scale"
 local TIMELINE_ROTATE = "rotate"
 local TIMELINE_TRANSLATE = "translate"
@@ -123,9 +126,8 @@ function SkeletonJson.new (attachmentLoader)
 		end
 
 		-- 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)
 				for slotName,slotMap in pairs(skinMap) do
 					local slotIndex = skeletonData.slotNameIndices[slotName]
@@ -144,10 +146,20 @@ function SkeletonJson.new (attachmentLoader)
 			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.
-		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)
 			end
 		end
@@ -190,7 +202,7 @@ function SkeletonJson.new (attachmentLoader)
 						local keyframeIndex = 0
 						for i,valueMap in ipairs(values) do
 							local time = valueMap["time"]
-							timeline:setKeyframe(keyframeIndex, time, valueMap["angle"])
+							timeline:setFrame(keyframeIndex, time, valueMap["angle"])
 							readCurve(timeline, keyframeIndex, valueMap)
 							keyframeIndex = keyframeIndex + 1
 						end
@@ -213,7 +225,7 @@ function SkeletonJson.new (attachmentLoader)
 							local time = valueMap["time"]
 							local x = (valueMap["x"] 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)
 							keyframeIndex = keyframeIndex + 1
 						end
@@ -241,7 +253,7 @@ function SkeletonJson.new (attachmentLoader)
 						for i,valueMap in ipairs(values) do
 							local time = valueMap["time"]
 							local color = valueMap["color"]
-							timeline:setKeyframe(
+							timeline:setFrame(
 								keyframeIndex, time, 
 								tonumber(color:sub(1, 2), 16),
 								tonumber(color:sub(3, 4), 16),
@@ -258,13 +270,13 @@ function SkeletonJson.new (attachmentLoader)
 						local timeline = Animation.AttachmentTimeline.new()
 						timeline.slotName = slotName
 
-						local keyframeIndex = 0
+						local frameIndex = 0
 						for i,valueMap in ipairs(values) do
 							local time = valueMap["time"]
 							local attachmentName = valueMap["name"]
 							if not attachmentName then attachmentName = nil end
-							timeline:setKeyframe(keyframeIndex, time, attachmentName)
-							keyframeIndex = keyframeIndex + 1
+							timeline:setFrame(frameIndex, time, attachmentName)
+							frameIndex = frameIndex + 1
 						end
 						table.insert(timelines, timeline)
 						duration = math.max(duration, timeline:getDuration())
@@ -276,16 +288,84 @@ function SkeletonJson.new (attachmentLoader)
 			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))
 	end
 
-	readCurve = function (timeline, keyframeIndex, valueMap)
+	readCurve = function (timeline, frameIndex, valueMap)
 		local curve = valueMap["curve"]
 		if not curve then return end
 		if curve == "stepped" then
-			timeline:setStepped(keyframeIndex)
+			timeline:setStepped(frameIndex)
 		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
 

+ 15 - 22
spine-lua/utils.lua

@@ -36,36 +36,29 @@ local utils = {}
 function tablePrint (tt, indent, done)
 	done = done or {}
 	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
-		return table.concat(sb)
-	else
-		return tt .. "\n"
 	end
 end
 
-function utils.print (value)
+function utils.print (value, indent, done)
 	if "nil" == type(value) then
 		print(tostring(nil))
 	elseif "table" == type(value) then
-		print(tablePrint(value))
+		print("{")
+		tablePrint(value, 2)
+		print("}")
 	elseif "string" == type(value) then
-		print(value)
+		print("\"" .. value .. "\"")
 	else
 		print(tostring(value))
 	end