diff --git a/.gitignore b/.gitignore index bfc13b0a5..a0fc434bf 100644 --- a/.gitignore +++ b/.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 diff --git a/spine-corona/examples/spineboy.lua b/spine-corona/examples/spineboy.lua index 2b2b3550b..425159803 100644 --- a/spine-corona/examples/spineboy.lua +++ b/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 diff --git a/spine-corona/spine-corona/spine.lua b/spine-corona/spine-corona/spine.lua index f42c76e7f..acad55767 100644 --- a/spine-corona/spine-corona/spine.lua +++ b/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 diff --git a/spine-lua/Animation.lua b/spine-lua/Animation.lua index a9c603bbe..4e1c90e5c 100644 --- a/spine-lua/Animation.lua +++ b/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 diff --git a/spine-lua/AnimationState.lua b/spine-lua/AnimationState.lua index 189c62e0f..a333ed4dc 100644 --- a/spine-lua/AnimationState.lua +++ b/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) - end - - function self:isComplete () - return (not self.animation) or self.currentTime >= self.animation.duration + -- May return nil. + function self:getCurrent (trackIndex) + return self.tracks[trackIndex] 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 diff --git a/spine-lua/AnimationStateData.lua b/spine-lua/AnimationStateData.lua index f3b003acf..7793d10ba 100644 --- a/spine-lua/AnimationStateData.lua +++ b/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 diff --git a/spine-lua/Bone.lua b/spine-lua/Bone.lua index 12eafbbe0..cb3141d6e 100644 --- a/spine-lua/Bone.lua +++ b/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) diff --git a/spine-lua/Event.lua b/spine-lua/Event.lua new file mode 100644 index 000000000..e838b8016 --- /dev/null +++ b/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 diff --git a/spine-lua/EventData.lua b/spine-lua/EventData.lua new file mode 100644 index 000000000..9421a7d78 --- /dev/null +++ b/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 diff --git a/spine-lua/SkeletonData.lua b/spine-lua/SkeletonData.lua index bd79fb7a2..c0f0e2b30 100644 --- a/spine-lua/SkeletonData.lua +++ b/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 diff --git a/spine-lua/SkeletonJson.lua b/spine-lua/SkeletonJson.lua index 131a01a9f..c7e9e33ae 100644 --- a/spine-lua/SkeletonJson.lua +++ b/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 diff --git a/spine-lua/utils.lua b/spine-lua/utils.lua index f45da656f..1ed086444 100644 --- a/spine-lua/utils.lua +++ b/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