• Runtimes
  • Asset management with Spine on the Web

We're making a game using Pixi and Spine, utilizing the pixi-spine library (which is built off the spine-ts library). The game is quite simply a character creator.

Something we've been chewing on: asset management. Part of our game involves having a character that can have many hundreds if not thousands of different, say, hats, tshirts, shoes, etc.

One strategy involved color - we've already found a solution in export all assets as white and simply tinting them. Done, custom avatar coloring.

Now we need to figure out a good way to have pages upon pages of possible hats, tshirts, whatever.

One solution: Having a spine character with a "tshirt" slot and "hat" slot with hundreds of attachments. Works, simply setAttachmentByName('hat', 'hatWithSkull') or whatever, fairly straightforward. However, our designers are telling us it's obnoxious to create a bunch of new attachments, that they have to manually reposition the attachment by hand for every new attachment, I guess it's not as simple as a texture swap? (if this isn't the case, I'm happy for advice on this front). Furthermore, this results in, one way or another, a monolithic character.png, character,atlas, character.json. We're trying to look into splitting this up, but we can't figure out any way of paginating - all solutions we've found, split or otherwise, require ALL assets available from the get-go (the creation of the Spine asset, before it's rendered onto the page).

Another solution: Arbitrary texture swaps, using hackTexture or code like the following:

this.gerald.gerald.skeleton.findSlot('hat').sprites['cool/hatRed'].texture =
        PIXI.Texture.from('/hatBlue.png');

This seems... okay. It allows us to pull a given png in whenever we need it (i.e. make the above an async response to the request of fetching the hatBlue.png). However, the designers are saying this is also relatively obnoxious to ensure is placed correctly, and is a mild burden in the same vein upon the engineers. It also further restricts the variability of the assets - they must all generally be reskins, or we have to make a lot of hatBig, hatSmall, hatBigWithFeather type slots. Still, as far as we can tell, this is the only solution that doesn't require a monolith (one way or the other) atlas file + png + json.

Another solution we've just started investigating came from this post: http://esotericsoftware.com/forum/Animations-synchro-in-the-previews-and-in-the-engine-11563?p=51901&hilit=spine+skins+view#p51901

Basically, a character with tons of different skins/skin placeholders, which refer to different attachments/slots. The designers are loving this because they say they don't have to re-place assets manually when they add new ones, but as far as we can tell this doesn't solve the monolith problem.

The only solution we've come up with outside arbitrary texture replacement involves creating many-skinned (or attachment'd) separate character spine assets, but with only "hat" or "tshirt" textures/attachments. Then, creating these invisibly, and stealing their attachments off using methods like findSlot('hat').setAttachment(attachmentFromInvisibleOtherSpine). This I guess works but feels hacky.

So, what sort of workflows have other found to work when potentially needed tens of thousands of assets to be swapped in/out on a whim, needing pagination for assets, etc?

Related Discussions
...

Your designers are right 🙂 The "tons of different skins/skin placeholders" approach is the best at design time. Having a single character JSON with all animations and skin definitions should also be fine. What needs "unmonolithing" is the texture atlas. And that's where you won't be able to come up with anything better than texture replacement.

You already identified the simplest version: replace individual attachments' PIXI.Texture (not well versed in PIXI, just going by your code above). Of course that means you have a gazillion texture switches while rendering a single skeleton. Depending on the number of switches, this might be OK. It definitely makes loading all these pieces on the demand relatively trivial.

The other option is to pack a new atlas at runtime from the list of images that's currently required. That would reduce your draw calls, but add the complexity of another moving piece (texture packer, not part of spine-ts, would have to be ported from some implementation, e.g. the runtime texture packer of libGDX https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java).

You can make a single "reference" skin using attachment images that are large enough (have enough whitespace) to contain any image. You place only these in Spine, then you can use texture replacement to put in any image at runtime. You'd write code to duplicate an attachment and change the texture, described here:
Runtime Skins - Spine Runtimes Guide: Creating attachments

The artists use the reference skin attachment images so they know how to build the art, eg where the knee rotates or where the handle of the sword is. That way all the images are the same size, so they can be swapped rather than needing to position each one manually. The whitespace in the images can be stripped when packed into an atlas, so images with a lot of whitespace around the edges is not a problem.

This setup is just a matter of coming up with conventions to remove the tedious work. Now your skeleton setup is simple and making art is very easy. You'll still need to pack the images into a big atlas or do runtime packing, as badlogic described.

4 jours plus tard

Cheers all, this is very helpful. We'll probably go down the arbitrary texture route (unless we can get hot enough servers to let users download our massive sprite)