@kilopop Mine is not overly complicated… What I tried to do was extend skeleton to contain all the animation, images, etc to keep them in one structure. I also killed all that extra math and locked the animations into 30 or 60 FPS based on what the config.lua is set at… I figure if your animations can’t keep up you have bigger issues.
Finally I wrapped all the state events into one onEvent() but it should probably be renamed to onAnimation()
So I use it like this… Create a display object…
hero = display.newObject() physics.addBody(hero)
Then I create the spine as a metatable (or object) off of that display object
hero.spine = spine.new("spine/hero.json", { x=hero.x, y=hero.y, imageDir = "spine/heroParts", tetherObject = hero, mixTime = 0.25, }) hero.spine:play("idle",true)
Then I hide the original display object
hero.isVisible = false
I keep my spine runtimes in a package structure, so you’ll need to update the one spine = require()
Anything you can do to speed this up or improve the stability is appreciated.
-- Project: Spine Wrapper 0.3 -- -- Date: Mar 20, 2015 -- Updated: Apr 14, 2015 local spine = require "com.esoteric.spine-corona.spine" local M = {} M.delta = 1 / display.fps function M.new(spineJson, options) -- defaults options = options or {} local speed = options.speed or 1 local tetherObject = options.tetherObject or nil local offsetX, offsetY = options.tetherOffsetX or 0, options.tetherOffsetY or 0 -- Json loading local jsonData = spine.SkeletonJson.new() jsonData.scale = options.scale or 1 local skeletonData = jsonData:readSkeletonDataFile(spineJson) -- build our skeleton object local skeleton = spine.Skeleton.new(skeletonData) skeleton.delta = M.delta -- function to load from images or texture pack / shoebox if options.imageSheet and options.imageData then skeleton.imageData = require (options.imageData) skeleton.imageSheet = graphics.newImageSheet( options.imageSheet, skeleton.imageData.sheet ) end function skeleton:createImage(attachment) if options.imageDir then return display.newImage(options.imageDir .. "/" .. attachment.name .. ".png") elseif self.imageSheet and self.imageData then local frame = self.imageData:getFrameIndex(attachment.name) return display.newImage(self.imageSheet, frame) end end skeleton.count = 0 -- 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 (trackIndex) e.phase = "started" e.trackIndex = trackIndex e.name = state:getCurrent(trackIndex).animation.name if options.default==nil then -- don't fire started with default animation eventFunction(skeleton,e) end end state.onEnd = function (trackIndex) e.phase = "ended" e.trackIndex = trackIndex e.name = state:getCurrent(trackIndex).animation.name eventFunction(skeleton,e) end state.onComplete = function (trackIndex, loopCount) e.phase = "completed" e.trackIndex = trackIndex e.loopCount = loopCount e.name = state:getCurrent(trackIndex).animation.name eventFunction(skeleton,e) end state.onEvent = function (trackIndex, event) e.phase = event.data.name or "unknown" e.trackIndex = trackIndex e.loopCount = loopCount e.data = event.intValue or event.floatValue or event.stringValue or "" e.name = state:getCurrent(trackIndex).animation.name eventFunction(skeleton,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.destroy() Runtime:removeEventListener("enterFrame", skeleton.enterFrame) if skeleton.group then display.remove(skeleton.group) skeleton.group = nil end skeleton.imageSheet = nil skeleton = nil end function skeleton.enterFrame() local hasGroup = skeleton and skeleton.group and skeleton.group.numChildren local state = skeleton.state -- if we loose our group then destroy if hasGroup then if tetherObject and tetherObject.x and tetherObject.y then skeleton.group.rotation = tetherObject.rotation skeleton.group.x, skeleton.group.y = tetherObject.x + offsetX, tetherObject.y + offsetY else 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 else state:update(M.delta) -- in case we are missing state:apply(skeleton) skeleton:destroy() return false end return true end function skeleton.init() if tetherObject then skeleton.group.rotation = tetherObject.rotation skeleton.group.x, skeleton.group.y = tetherObject.x + offsetX, tetherObject.y + offsetY 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 -- \*\*\* 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 --print ("WARNING: Animation not found or already playing:",name) return false end self.isPaused = false 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", self.enterFrame) end return self.isPaused end -- resume the animation function skeleton:resume() if self.isPaused then --print ("resuming") self.isPaused = false timer.performWithDelay(M.delta, function () Runtime:addEventListener("enterFrame", self.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 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 return skeleton end return M