New native Spine 4.2 plugin

I just published the Native Spine plugin to the Marketplace.

The plugin is now in a way more stable stage, currently supporting Mac, Windows and iOS.

It is still in development and not yet ready for production builds.

How to use it

In your build.settings add:

['plugin.spine'] = {publisherId = 'com.studycat'}

Then in your code:

    local spine = require("plugin.spine")

    local spineObject = spine.create({
        skeletonFile = system.pathForFile(path .. ".skel", system.ResourceDirectory),
        atlasFile = system.pathForFile(path .. ".atlas", system.ResourceDirectory),
        scale = 1.0,
        listener = function(e)
            -- renamed spine events to a Solar2D style:
            -- phases: "began", "completed", "ended", "cancelled", "disposed" and "event"
        end
    })

Available methods

-- display methods
o.parent = displayGroup
o.x = number
o.y = number
o.width = number
o.height = number
o.contentWidth = number
o.contentHeight = number
o.rotation = number
o.xScale = number
o.yScale = number
o.alpha = number
o.isVisible = boolean
o:scale(x, y)
o:toFront()
o:toBack()
o:contentToLocal(x, y)
o:localToContent(x, y)
o:removeSelf()

-- spine methods
o.isActive = boolean -- read only
o:update(dt) -- In seconds, but will change to ms
o:setAnimation(trackIndex, animation, loop)
o:setDefaultMix(time) -- also seconds
o:setMix(from, to, time)
o:setToSetupPose()
o:addAnimation(trackIndex, animation, loop, delay)
o:findAnimation(animationName) -- returns true if found
o:getAllAnimations() -- returns a table of all animations
o:getAllSkins() -- returns a table of all skins
o:setSkin(skinName)
o:setAttachment(slotName, attachmentName)
o:stop()

Performance

The plugin is just way more efficient than the Lua version since it runs on C++. Some raw tests gave me these results by running 200 instances of our heaviest Animation.

Plugin

  • Size: 1.5 MB → png + skel + atlas [Although json is still supported]
  • Loading time: 2,592.5 ms → 13 ms / instance
  • Frame rate: 21 fps
  • RAM: 1.01 GB → 5.2 MB / instance

Lua Runtime

  • Size: 5.3 MB → png + json + atlas
  • Loading time: 72,930.1 ms → 364.6 ms / instance
  • Frame rate: 3 fps
  • RAM: 19.3 GB → 98.8 MB / instance

Discussion

  1. Should I expose the raw update methods? Currently I’m handling all these inside a the update() call I guess exposing the raw could be useful for anyone who wants to customize the update cycle seeking even better performance.
spineObject:updateState(deltaTime)
spineObject:apply() -- this one triggers the events
spineObject:updateSkeleton(deltaTime) -- for the physics feature
spineObject:updateWorldTransform() -- the heavy load happens here
spineObject:render()
  1. Android and inverse kinematics are in the oven, what else is missing?

Resources

The plugin is open-source and you can find it at:

There is a lot of updates and improvements to be made, all contributions and suggestions are welcome. Soon I will also add a proper documentation too.

7 Likes

Not sure on what’s missing but I’m all for customization and fine tuning.

2 Likes

Thanks @bgmadclown.

Did not publish this yet, but I did the following changes:

  1. Added Atlas and spine skeleton load methods, so they can be reused when creating new skeletons:
local atlas = Spine.loadAtlas(atlasPath)
local skeletonData = Spine.loadSkeletonData(skeletonPath, atlas, scale)

local o = Spine.create(skeletonData [,listener])

And for updates, I broke the update into 2:

o:updateState(dt) -- in ms, this is the one who triggers the events.
o:draw() -- most of the load happens here, don't call this if the object is off screen.

I realized the other calls are useless if we are not exposing the skeleton and these also have only very edge case uses.

I also fixed some issues regarding their display object behavior and added new functionalities that I will share on future posts.

Current state of the plugin is unstable, I will publish the changes once these changes get consolidated and the plugin recovers the stability. All these changes are accessible in the plugin repo anyways.

2 Likes

Forgot to mention that physics are already working and making use of them have 0 impact on the performance. That’s something the esotericsoftware team have total merit of, they did a great job building these Runtimes.

These are used as:

o:physicsTranslate(dx, dy) -- called on every touch event.
1 Like

Great work, thanks for taking care of the Spine Engine for Solar2d.
I want to try it out in our project!

2 Likes

Thanks.
Great work!!!
We sincerely look forward to supporting Android. :heart:

1 Like

Thanks @vb66r55,

Android will come 'til next week.

I just published a new version with all the new updates!

I’m gonna upload a proper documentation along some sample projects soon, but for those who are already using it, this is a quick guide:

Creating a new Skeleton

Atlases and skeletonDatas can be reused across multiple skeletons, so you can load them once and create as many skeletons as you want.
By doing so, the loading time is much faster and the memory usage is lower.

local Spine = require("plugin.spine")

local absPath = system.pathForFile("spineboy", system.ResourceDirectory)
local atlas = SpinePlugin.loadAtlas(absPath) -- I'm considering implementing a Spine.loadAtlas(path, baseDir) instead

local skeletonData = SpinePlugin.loadSkeletonData(absPath, atlas)

local o = Spine.create(skeletonData, listener)

Skeleton attributes and methods

In addition to all the regular displayObject ones:

o.timeScale = 1 -- Speed of the animation
o.isActive = true -- If the skeleton is playing any animation
o.slots = {} -- Array of slots (read-only)
o.bones = {} -- Array of bones (read-only)
o.ikConstraints = {} -- Array of ikConstraints (read-only)

o:updateState(dt) -- Updates the skeleton state > triggers events
o:draw()
o:removeSelf()
o:setFillColor(r [,g[,b,[a])]])
o:setDefaultMix(ms)
o:setMix(from, to, ms)
o:physicsRotate(degrees)
o:physicsTranslate(x, y)
o:setToSetupPose()
o:setAnimation(trackIndex, name, loop)
o:addAnimation(trackIndex, name, loop, delay)
o:findAnimation(name)
o:getCurrentAnimation()
o:getAnimations()

o:getSkins()
o:setSkin(name)

o:clearTracks() -- stops all animations

o:setAttachment(slotName, attachmentName)
o:getAttachments(slotName)

-- In progress: injects a displayObject into the spine, use the listener to track the movement of the slot
-- currently not layering properly
o:inject(slotName, displayObject, listener) -- 
o:eject(displayObject)

Bones

It has almost all the properties of the original spine. Read and write.

bone.name-- read-only
bone.parent -- parent bone, read-only
bone.x = 100
bone.y = 100
bone.worldX = 100
bone.worldY = 100
bone.rotation = 90
bone.xScale = 2 -- not scaleX to match solar style
bone.xScale = 2 -- not scaleY to match solar style
bone.shearX = 45
bone.shearY = 45
bone.appliedRotation = 90
bone.appliedX = 100
bone.worldRotation = 90
bone.worldScaleX = 2
bone.worldScaleY = 2
bone.a = 1
bone.b = 1
bone.c = 1
bone.d = 1

Slots

slot.name -- read-only
slot.bone -- read-only
slot.r = 1
slot.g = 1
slot.b = 1
slot.alpha = 1 
slot.attachment = "attachmentName"

IK Constraints

ik.name -- read-only
ik.bones -- array of bones
ik.isActive = true
ik.mix = 0.3
ik.stretch = true
ik.softness = 0
ik.bendDirection = 1
ik.compress = false
ik.order = 0
ik.target = bone

Events

local function listener(e)
    print(e.name, e.phase, e.animation, e.trackIndex, e.looping)

    if e.name == "spine" then -- default spine events
        if e.phase == "began" then
        elseif e.phase == "ended" then
        elseif e.phase == "completed" then
        elseif e.phase == "interrupted" then
        elseif e.phase == "disposed" then
        end
    else -- custom events
        if e.name == "customEventName" then -- doesn't have phases
        end
    end
    
end)

spine.create(skeletonData, listener)
3 Likes

The next version could still change some of these things in the lua side of the code, but that should be the last one. I don’t want to break anyone’s code with these updates.

Things that are pending are:

  • Rendering improvements: Which will double the speed of the draw() call.
  • Android support
  • Spine injections
  • Spine splits

Feedback is always welcome.

2 Likes

I just realized there were some critical bugs, I pushed a fix.

Also added some short samples: here spinePlugin/Corona at main · depilz/spinePlugin · GitHub

3 Likes

Just published Android.

It is working fine on simulator, but having some odd crashes on my old Samsung S9. I wonder if that’s because it is Android or because it is old.

Either case I will look for the fix.

1 Like

Getting the same on another Android, I will investigate.

1 Like