Skip to content

Instantly share code, notes, and snippets.

@superqix
Created March 3, 2021 15:19
Show Gist options
  • Save superqix/55f9344471717aa1b036e2a9d0c2ce72 to your computer and use it in GitHub Desktop.
Save superqix/55f9344471717aa1b036e2a9d0c2ce72 to your computer and use it in GitHub Desktop.
Spine Wrapper 0.4 for Esoteric Spine for Solar2d / CoronaSDK
-- 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