Created
March 3, 2021 15:19
-
-
Save superqix/55f9344471717aa1b036e2a9d0c2ce72 to your computer and use it in GitHub Desktop.
Spine Wrapper 0.4 for Esoteric Spine for Solar2d / CoronaSDK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Project: Spine Wrapper 0.4 | |
-- | |
-- Date: Mar 20, 2015 | |
-- Updated: Apr 18, 2018 | |
local spine = require "com.spine-corona.spine" | |
local json = require "json" | |
--local print = function(...) end -- uncomment me to get rid of prints | |
local M = {} | |
local delta = 1 / display.fps | |
function M.new(filename, path, options) | |
-- defaults | |
options = options or {} | |
path = path or "/" | |
local speed = options.speed or 1 | |
local tetherObject = options.tetherObject or nil | |
local offsetX, offsetY = options.tetherOffsetX or 0, options.tetherOffsetY or 0 | |
-- imageLoader | |
local function imageLoader(image) | |
local paint = { type = "image", filename = path .. image } | |
return paint | |
end | |
-- load the atlas | |
local atlas = spine.TextureAtlas.new(spine.utils.readFile(path .. filename..".atlas"), imageLoader) | |
-- json loading | |
local jsonData = spine.SkeletonJson.new(spine.AtlasAttachmentLoader.new(atlas)) | |
jsonData.scale = options.scale or 1 | |
local skeletonData = jsonData:readSkeletonDataFile(path .. filename..".json") | |
-- build our skeleton object | |
local skeleton = spine.Skeleton.new(skeletonData) | |
skeleton.flipY = true | |
skeleton.delta = delta | |
-- dump animation data | |
local animations = skeleton.data.animations | |
for i = 1, #animations do | |
print ("Animation found:", animations[i].name) | |
end | |
function skeleton.animationExists(name) | |
for i = 1, #animations do | |
if name == animations[i].name then return true end | |
end | |
return false | |
end | |
-- initial animation/pose setup | |
skeleton:setToSetupPose() | |
skeleton.stateData = spine.AnimationStateData.new(skeletonData) | |
local stateData = skeleton.stateData | |
-- set global mix duration | |
if #animations > 1 then | |
for i = 1, #animations do | |
for j = 1, #animations do | |
if not (i==j) then | |
stateData:setMix(animations[i].name, animations[j].name, options.mixTime or 0.5) | |
end | |
end | |
end | |
end | |
-- setup animation queue | |
skeleton.state = spine.AnimationState.new(stateData) | |
local state = skeleton.state | |
-- Events: | |
-- let's combine these events in a more corona-like fashion | |
if options.onEvent then | |
local e = {} | |
local eventFunction = options.onEvent | |
state.onStart = function (target) | |
e.phase = "started" | |
e.target = target | |
e.name = target.animation.name | |
--if not options.default then -- don't fire started with default animation | |
eventFunction(e) | |
--end | |
end | |
state.onEnd = function (target) | |
e.phase = "ended" | |
e.target = target | |
e.name = target.animation.name | |
eventFunction(e) | |
end | |
state.onComplete = function (target) | |
e.phase = "completed" | |
e.target = target | |
e.name = target.animation.name | |
eventFunction(e) | |
end | |
state.onDispose = function (target) | |
e.phase = "disposed" | |
e.target = target | |
e.name = target.animation.name | |
eventFunction(e) | |
end | |
state.onEvent = function (target, event) | |
e.phase = event.data.name or "unknown" | |
e.target = target | |
e.name = target.animation.name | |
e.data = event.data.name | |
e.intValue = event.intValue | |
e.floatValue = event.floatValue | |
eventFunction(e) | |
end | |
end | |
local function offScreen(object) | |
local bounds = object.contentBounds | |
local sox, soy = display.screenOriginX, display.screenOriginY | |
if bounds.xMax < sox then return true end | |
if bounds.yMax < soy then return true end | |
if bounds.xMin > display.actualContentWidth - sox then return true end | |
if bounds.yMin > display.actualContentHeight - soy then return true end | |
return false | |
end | |
function skeleton.enterFrame() | |
local hasGroup = skeleton and skeleton.group and skeleton.group.numChildren | |
local state = skeleton and skeleton.state | |
-- if we loose our group then destroy | |
if hasGroup and state then | |
if tetherObject and tetherObject.x and tetherObject.y then | |
skeleton.group.rotation = tetherObject.rotation | |
skeleton.group.x = tetherObject.x + (skeleton.offsetX or 0) + offsetX | |
skeleton.group.y = tetherObject.y + (skeleton.offsetY or 0) + offsetY | |
elseif skeleton.hadTetherObject then | |
skeleton:destroy() | |
return false | |
end | |
-- offscreen | |
if offScreen(skeleton.group) then return end | |
-- Update the state with the delta time, apply it, and update the world transforms. | |
state:update(skeleton.delta) | |
state:apply(skeleton) | |
if skeleton then skeleton:updateWorldTransform() end | |
elseif state then | |
state:update(delta) -- in case we are missing | |
state:apply(skeleton) | |
skeleton:destroy() | |
return false | |
end | |
return true | |
end | |
local enterFrame = skeleton.enterFrame | |
function skeleton.init() | |
if tetherObject then | |
skeleton.group.rotation = tetherObject.rotation | |
skeleton.group.x, skeleton.group.y = tetherObject.x + offsetX, tetherObject.y + offsetY | |
skeleton.hadTetherObject = true | |
end | |
-- Update the state with the delta time, apply it, and update the world transforms. | |
state:update(skeleton.delta) | |
state:apply(skeleton) | |
skeleton:updateWorldTransform() | |
return true | |
end | |
function skeleton.destroy() | |
Runtime:removeEventListener("enterFrame", enterFrame) | |
if skeleton.group then | |
display.remove(skeleton.group) | |
skeleton.group = nil | |
end | |
skeleton.imageSheet = nil | |
skeleton = nil | |
end | |
function skeleton.group:finalize() | |
Runtime:removeEventListener("enterFrame", enterFrame) | |
end | |
-- *** simple wrapper functions *** | |
-- what's playing | |
function skeleton:currentAnimation(trackIndex) | |
if not self.state then return end | |
local currentState = self.state:getCurrent(trackIndex or 0) | |
if not currentState then return end | |
return currentState.animation.name | |
end | |
-- play an animation | |
function skeleton:play(name, loop, trackIndex) | |
if self.isPaused then | |
self:resume() | |
end | |
if self.animationExists(name) and self:currentAnimation() ~= name then | |
self.state:setAnimationByName(trackIndex or 0, name or self.data.animations[1].name, loop or false) | |
else | |
if not self.animationExists(name) then | |
print ("WARNING: Animation not found:",name) | |
else | |
print ("WARNING: Animation already playing:",name) | |
end | |
return false | |
end | |
self.isPaused = false | |
return true | |
end | |
-- add an animation to the queue | |
function skeleton:add(name, loop, delay, trackIndex) | |
if self.animationExists(name) and self:currentAnimation() ~= name then | |
self.state:addAnimationByName(trackIndex or 0, name or self.data.animations[1].name, loop or false, delay) | |
else | |
--print ("WARNING: Animation not found or already playing:",name) | |
end | |
end | |
-- pause the animation | |
function skeleton:pause() | |
if not self.isPaused then | |
--print("pausing") | |
self.isPaused = true | |
Runtime:removeEventListener("enterFrame", enterFrame) | |
end | |
return self.isPaused | |
end | |
-- resume the animation | |
function skeleton:resume() | |
if self.isPaused then | |
--print ("resuming") | |
self.isPaused = false | |
timer.performWithDelay(delta, function () | |
Runtime:addEventListener("enterFrame", enterFrame) | |
end) | |
end | |
return self.isPaused | |
end | |
-- set default placement | |
if skeleton.group then | |
skeleton.group.x = options.x or 0 | |
skeleton.group.y = options.y or 0 | |
if options.anchored then | |
skeleton.group.anchorChildren = true | |
--skeleton.group.anchorX,skeleton.group.anchorY = 0.5,0.5 | |
end | |
skeleton.group.owner = skeleton | |
end | |
-- kick off the first frame to load the parts | |
skeleton.init() | |
-- start paused | |
skeleton.isPaused = true | |
-- unless we choose a default animation | |
if options.default then | |
skeleton:play(options.default, options.loop) | |
end | |
skeleton.group:addEventListener("finalize") | |
return skeleton | |
end | |
return M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment