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

This commit is contained in:
NathanSweet 2013-10-13 14:31:07 +02:00
parent 2005404321
commit a793d2bfa6
12 changed files with 587 additions and 188 deletions

1
.gitignore vendored
View File

@ -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

View File

@ -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:setAnimationByName(0, "drawOrder")
state:addAnimation("jump", false) state:addAnimationByName(0, "jump", false, 0)
state:addAnimation("walk", true) 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

View File

@ -47,6 +47,8 @@ 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
@ -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

View File

@ -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) function self:setLinear (frameIndex)
self.curves[keyframeIndex * 6] = LINEAR self.curves[frameIndex * 6] = LINEAR
end end
function self:setStepped (keyframeIndex) function self:setStepped (frameIndex)
self.curves[keyframeIndex * 6] = STEPPED 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) function self:getCurvePercent (frameIndex, percent)
local curveIndex = keyframeIndex * 6 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) function self:setFrame (frameIndex, time, value)
keyframeIndex = keyframeIndex * 2 frameIndex = frameIndex * 2
self.frames[keyframeIndex] = time self.frames[frameIndex] = time
self.frames[keyframeIndex + 1] = value 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) function self:setFrame (frameIndex, time, x, y)
keyframeIndex = keyframeIndex * 3 frameIndex = frameIndex * 3
self.frames[keyframeIndex] = time self.frames[frameIndex] = time
self.frames[keyframeIndex + 1] = x self.frames[frameIndex + 1] = x
self.frames[keyframeIndex + 2] = y 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) function self:setFrame (frameIndex, time, r, g, b, a)
keyframeIndex = keyframeIndex * 5 frameIndex = frameIndex * 5
self.frames[keyframeIndex] = time self.frames[frameIndex] = time
self.frames[keyframeIndex + 1] = r self.frames[frameIndex + 1] = r
self.frames[keyframeIndex + 2] = g self.frames[frameIndex + 2] = g
self.frames[keyframeIndex + 3] = b self.frames[frameIndex + 3] = b
self.frames[keyframeIndex + 4] = a 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() local self = {
self.frames = {} frames = {},
self.attachmentNames = {} attachmentNames = {},
self.slotName = nil 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) function self:setFrame (frameIndex, time, attachmentName)
self.frames[keyframeIndex] = time self.frames[frameIndex] = time
self.attachmentNames[keyframeIndex] = attachmentName 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] local slot = skeleton.slotsByName[self.slotName]
if attachmentName then if attachmentName then
if not slot.attachment then if not slot.attachment then
slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName)) slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
elseif slot.attachment.name ~= attachmentName then elseif slot.attachment.name ~= attachmentName then
slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName)) slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
end end
else else
slot:setAttachment(nil) slot:setAttachment(nil)
end 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

View File

@ -38,101 +38,199 @@ function AnimationState.new (data)
local self = { local self = {
data = data, data = data,
animation = nil, tracks = {},
previous = nil, events = {},
currentTime = 0, onStart = nil, onEnd = nil, onComplete = nil, onEvent = nil,
previousTime = 0, timeScale = 1
currentLoop = false,
previousLoop = false,
mixTime = 0,
mixDuration = 0,
queue = {}
} }
local function setAnimationInternal (animation, loop) local function setCurrent (index, entry)
self.previous = nil local current = self.tracks[index]
if (animation and self.animation) then if current then
self.mixDuration = data:getMix(self.animation.name, animation.name) current.previous = nil
if (self.mixDuration > 0) then
self.mixTime = 0 if current.onEnd then current.onEnd(index) end
self.previous = self.animation if self.onEnd then self.onEnd(index) end
self.previousTime = self.currentTime
self.previousLoop = self.currentLoop 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.tracks[index] = entry
self.currentTime = 0
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 delta = delta * self.timeScale
self.previousTime = self.previousTime + delta for i,current in pairs(self.tracks) do
self.mixTime = self.mixTime + delta if current then
local trackDelta = delta * current.timeScale
local time = current.time + trackDelta
local endTime = current.endTime
if (#self.queue > 0) then current.time = time
local entry = self.queue[1] if current.previous then
if (self.currentTime >= entry.delay) then current.previous.time = current.previous.time + trackDelta
setAnimationInternal(entry.animation, entry.loop) current.mixTime = current.mixTime + trackDelta
table.remove(self.queue, 1) 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 for i,current in pairs(self.tracks) do
if (self.previous) then if current then
self.previous:apply(skeleton, self.previousTime, self.previousLoop) local time = current.time
local alpha = self.mixTime / self.mixDuration local loop = current.loop
if (alpha >= 1) then if not loop and time > current.endTime then time = current.endTime end
alpha = 1
self.previous = nil 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. function self:clearTracks ()
-- The delay may be <= 0 to use duration of the previous animation minus any mix duration plus the negative delay. for i,current in pairs(self.tracks) do
function self:addAnimationWithDelay (animationName, loop, delay) self.clearTrack(i)
if (delay <= 0) then end
-- Find the animation that is queued before this one. self.tracks = {}
local last end
if (#self.queue == 0) then
last = self.animation function self:clearTrack (trackIndex)
else local current = self.tracks[trackIndex]
last = self.queue[#self.queue].animation 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 last.next = entry
delay = last.duration - data:getMix(last.name, animationName) + delay 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 entry.delay = delay
if animationName then animation = data.skeletonData:findAnimation(animationName) end
table.insert(self.queue, {animation = animation, loop = loop, delay = delay}) return entry
end end
-- Queues an animation to be played after the last queued animation (if any). -- May return nil.
function self:addAnimation (animationName, loop) function self:getCurrent (trackIndex)
self:addAnimationWithDelay(animationName, loop, 0) return self.tracks[trackIndex]
end
-- Clears the animation queue and sets the current animation.
function self:setAnimation (animationName, loop)
self.queue = {}
local animation = nil
if animationName then animation = data.skeletonData:findAnimation(animationName) end
setAnimationInternal(animation, loop)
end
function self:isComplete ()
return (not self.animation) or self.currentTime >= self.animation.duration
end end
return self return self
end 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 return AnimationState

View File

@ -42,20 +42,20 @@ function AnimationStateData.new (skeletonData)
defaultMix = 0 defaultMix = 0
} }
function self:setMix (fromName, toName, duration) function self:setMix (fromName, toName, duration)
if (not self.animationToMixTime[fromName]) then if not self.animationToMixTime[fromName] then
self.animationToMixTime[fromName] = {} self.animationToMixTime[fromName] = {}
end end
self.animationToMixTime[fromName][toName] = duration self.animationToMixTime[fromName][toName] = duration
end end
function self:getMix (fromName, toName) function self:getMix (fromName, toName)
local first = self.animationToMixTime[fromName] local first = self.animationToMixTime[fromName]
if (not first) then return self.defaultMix end if not first then return self.defaultMix end
local duration = first[toName] local duration = first[toName]
if (duration == nil) then return defaultMix end if not duration then return defaultMix end
return duration return duration
end end
return self return self
end end

View File

@ -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
spine-lua/Event.lua Normal file
View File

@ -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
spine-lua/EventData.lua Normal file
View File

@ -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

View File

@ -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

View File

@ -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 root["skins"] then
if map then for skinName,skinMap in pairs(root["skins"]) do
for skinName,skinMap in pairs(map) 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 root["animations"] then
if map then for animationName,animationMap in pairs(root["animations"]) do
for animationName,animationMap in pairs(map) 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) timeline:setFrame(frameIndex, time, attachmentName)
keyframeIndex = keyframeIndex + 1 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

View File

@ -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 for key, value in pairs(tt) do
local sb = {} local spaces = string.rep (" ", indent)
for key, value in pairs (tt) do if type(value) == "table" and not done [value] then
table.insert(sb, string.rep (" ", indent)) -- indent it done [value] = true
if type (value) == "table" and not done [value] then print(spaces .. "{")
done [value] = true utils.print(value, indent + 2, done)
table.insert(sb, "{\n"); print(spaces .. "}")
table.insert(sb, tablePrint (value, indent + 2, done)) else
table.insert(sb, string.rep (" ", indent)) -- indent it io.write(spaces .. tostring(key) .. " = ")
table.insert(sb, "}\n"); utils.print(value, indent + 2, done)
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
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