• Runtimes
  • List all Attachments for Slot?

I'm working with canvas/webgl, so, basic javascript here. I noticed in some of the examples it being possible to generate a dropdown list with all available animations. This was the code provided:

function setupUI () {
   var setupAnimationUI = function() {
      var animationList = $("#animationList");
      animationList.empty();
      var skeleton = skeletons[activeSkeleton].skeleton;
      var state = skeletons[activeSkeleton].state;
      var activeAnimation = state.tracks[0].animation.name;
      for (var i = 0; i < skeleton.data.animations.length; i++) {
         var name = skeleton.data.animations[i].name;
         var option = $("<option></option>");
         option.attr("value", name).text(name);
         if (name === activeAnimation) option.attr("selected", "selected");
         animationList.append(option);
      }

  animationList.change(function() {
     var state = skeletons[activeSkeleton].state;
     var skeleton = skeletons[activeSkeleton].skeleton;
     var animationName = $("#animationList option:selected").text();
     skeleton.setToSetupPose();
     state.setAnimation(0, animationName, true);         
  })
   }    
setupAnimationUI(); }

I can see we're iterating over skeleton.data.animations.length, grabbing the name with skeleton.data.animations.name, rendering out the html, and then swapping out the animation new option is selected.

However, I'm hoping for a way to generate a list of all possible attachments in a given slot. I actually already know how to swap out attachments with skeleton.setAttachment(slot, attachment), so recreating the latter half of the above example above won't be difficult for me.

So, what should I be iterating over, if I wanted to generate a list of all possible attachments under a slot named "head", for example? I could manually write out a list, but I just have so many attachments for each slot that I don't want to even begin to write them out by hand.

This will be a big help, even though some some of my items will inevitably operate in a skin-like fashion (sometimes an item will need multiple attachments over multiple slots). However, I feel limited by the existing skin system, so I'll be manually writing code to handle some situations. Here is a working (though miniature) example, where I've written out the list of attachments in each slot by hand: http://kyttias.tk/avatar_demo/

(If anyone is interested in the demo above and want a closer look, I'm providing this zip file open source! It includes all the json data, images, atlas files, and even the spine and psd I created the art in. On the surface, it looks similar to the existing "skins" demo in the webgl bundle on git, but there are absolutely no skins involved and it's purely individual attachments, sometimes linked by code. My reasoning is mysterious, I know, but this is just what fits my needs better.)

While I'm mostly just here hoping I can find a way to iterate over all possible attachments in a slot to generate a drop down list, I do have one other question. I've only looked at webgl for rendering to html5 canvas because it seemed to have the widest implementation of spine's current features (due to limitations in the other methods?) but I'm kind of wondering if I could just... maybe... not use atlas files? And if I need to use something other than webgl to render to canvas, that's fine, I don't need all of the newest features, nor even meshes, just simple rotations, translations, and scaling.

See, unrelated to the demo given above, I'd like to port over the avatars from a flash game to html5 and while you think 7180 attachments in 23 friggin' huge atlas pngs would be the better route, the atlas files actually (surprisingly) take up more room on disk than the individual items, anyway (in my extreme case). They also take a good 10 minutes to pack each time I need to make any edits. And, while I'm impressed spine loads all of this in about 5 seconds, my fast internet is mostly to thank here. I'd really prefer to only load the currently attached attachments, not massive atlas pngs, if possible. The majority of these attachment images will never ever change, but the atlas pngs will constantly be trying to rearrange the images in new ways each time they're repacked (which will be weekly as new items are added). They just can't be cached the way individual images can be. The largest of the atlas pngs is capping it at over 10MB


and total? Well, let's just say it's unreasonable to ask users to download 43MB of image data that can't be cached for very long. I'd really rather have users only load what NEEDS to be displayed. (And for my sanity, I'd rather not spend my time waiting for the texture packer to do it's thing without so much as a progress bar indicating it's actually doing anything, nor telling me when it's done.) So... thoughts?

I'm sorry this is a lot to respond to in one post. D;

Related Discussions
...
  • Modificato

You can programmatically grab any attachment (or create a new attachment) and stick it in any slot. Likely you want to show what attachments were defined in Spine for a slot. Attachments go into skins, even attachments which are not in a skin in Spine get put into a "default" skin. So what you want is to take a skin and find the attachments for a slot. Here is the spine-ts code to do that:

let dictionary = skin.attachments[slotIndex];
for (let key in dictionary) {
   let attachment:Attachment = dictionary[key];
   // do something with attachment
}

The skin can be skeletonData.defaultSkin, one of skeletonData.skins, or a skin you've created programmatically.

Note you can use skins in Spine to group attachments, eg for an item that has attachments in multiple slots. Then at runtime you can combine skins into a new skin, which you actually use on your skeleton. You can't yet preview combinations of skins in Spine, but that should come over the next couple months (v3.6).

You don't have to use Spine's texture atlas. Instead of using AtlasAttachmentLoader you can write your own. You can even have your loader not load any texture regions at all. Then later, when you know what attachments your skeletons will have, you could pack an atlas programmatically (the packing algorithm is up to you to implement) with just the images you need. You'd then set the regions on the attachments you are using. This is quite advanced though, and is an optimization you can tackle once your app is working.

BTW, if your atlases take 10 minutes to pack, you might consider checking the Fast option. How much worse this packs varies based on the input images, but it is a LOT faster.

I'm really excited to hear about the skin combination viewer! I've heard whispers of such a thing for a while and I've been looking forward to it!

edit: To followup about skin.attachments[slotIndex], I was a little tired when I asked, so what I was wanting was to traverse basic json, I guess. x'D What I ended up with is this:

var slots = skeletons[activeSkeleton].skeleton.skin.attachments;
for (var i = 0; i < slots.length; i++) {
   var slot = slots[i];
      for (var attachment in slot) {
      console.log(attachment);
      }
}

This gets me all the names of all the attachments in all the slots.

I'd like to get the name of the slot in there some way, too? So...

console.log(attachment+" belongs on "+slotName); // example output: "white_shoe belongs on foot"

I know this is off in the land of basic json traversal now, but... help? D; Is this even possible? I know I can do this:

console.log(attachment+" belongs on slot["+i+"]"); // example output: "white_shoe belongs on slot[10]"

And from there I could go "well, if i equals 10, then output the word foot"


but is this data really not stored in the tree?

Again, the end result of all this is to be able to dynamically generate a form so I can call the function skeleton.setAttachment(slot, attachment) with this information. I'm so close!

This image shows the relationships and some of the fields:

Immagine rimossa a causa della mancanza di supporto per HTTPS. | Mostra Comunque

You have the slot object, so you can get the name from it's SlotData:

console.log(attachment + " belongs on " + slot.data.name);

:happy:

Also, you don't need skeleton.setAttachment, which is for setting the slot's attachment by looking up the attachment by name in the skeleton's skin and defaultSkin. You want simply slot.setAttachment(attachment).

Unfortunately, I get an error of "Cannot read property 'name' of undefined" with slot.data.name:

var slots = skeletons[activeSkeleton].skeleton.skin.attachments;
for (var i = 0; i < slots.length; i++) {
   var slot = slots[i];
   for (var attachment in slot) {
      console.log(attachment+" belongs on slot["+i+"]: "+slot.data.name);
   }
}

Or, to be more specific:

console.log(attachment+" belongs on slot["+i+"]"); // white_shoe belongs on slot[10]
console.log(attachment+" belongs on slot["+i+"]: "+slot.data.name); // "Cannot read property 'name' of undefined"
console.log(attachment+" belongs on slot["+i+"]: "+slot.data); // white_shoe belongs on slot[10]: undefined
console.log(attachment+" belongs on slot["+i+"]: "+slot.name); // white_shoe belongs on slot[10]: undefined
console.log(attachment+" belongs on slot["+i+"]: "+slot); // white_shoe belongs on slot[10]: [object Object]

And this is probably just due to me looping over this as array counting how many objects are inside. (Working with json is tough when I'm tired.)

Ah, your variable slot isn't a slot object. slots is also misnamed (and settings slots = x.attachments is weird). For spine-ts/JavaScript, skin.attachments is an array who, for each slot index, has an entry which is an object. The object has skin placeholder names as keys and attachments as values. Here's some untested code:

var skeleton = skeletons[activeSkeleton].skeleton;
var skinAttachments = skeleton.skin.attachments;
for (var i = 0; i < slotAttachments.length; i++) {
   var slotAttachments = skinAttachments[i];
   if (!slotAttachments) continue; // skin may not have an entry for every slot.
   var slot = skeleton.slots[i];
   for (var placeholderName in slotAttachments) {
      var attachment = slotAttachments[placeholderName];
      console.log(attachment.name + " belongs on slot["+i+"]: " + slot.data.name);
   }
}

Note placeholderName and attachment.name are not necessarily the same.

Thank you so much! My final result is this:

var setup_slotListUI = function() {
   var skeleton = skeletons[activeSkeleton].skeleton;
   var skinAttachments = skeleton.skin.attachments;
   for (var i = 0; i < skinAttachments.length; i++) {
         var slotAttachments = skinAttachments[i];
         if (!slotAttachments) continue;
         var slot = skeleton.slots[i];
         var list = $('<select class="slot" id="'+slot.data.name+'"></select>');
         for (var j in slotAttachments) {
            var attachment = slotAttachments[j];
            // console.log(attachment.name + " belongs on slot["+i+"]: " + slot.data.name);
            var option = $("<option></option>");
         option.attr("value", name).text(attachment.name);
         list.append(option);
         }
         $("#lists").append("</br>"+slot.data.name+": "); 
      $("#lists").append(list); 
   }

   $(".slot").change(function() {
      var slot = this.id;
      var attachment = $(this).find(":selected").text();
      skeleton.setAttachment(slot, attachment);
   })
}    
setup_slotListUI();

Which gets me exactly what I want! This constructs a <select> form element for each slot, and sets the slot's name to the id of the select element. All <select> elements also have a class of ".slot" so that I can detect when they change, grab the id of that <select> element, as well as the name of select attachment so that I can equip it.

Oh, how could I grab the name of the attachment a slot has displayed at startup? If I had this information, I could make sure that when I render the list that the appropriate option will appear as selected in these form elements, in cases where the first attachment of a slot isn't necessarily the default one.

Thank you again for your help so far! Building this will definitely help me showcase my work to others and debug any visual problems I might have. There are many bits of clothing I have that I'm adding in through just modifying the json file, so building this lets my friends act as a second pair of eyes. Now they can tell me exactly which attachments need shifted! (I also hope any bit of this thread might be of use to anyone in the future.) <3

-

edit: I'm also noticing that, after I've set new attachments, if I switch the animation, the attachments are all reset to the default in each slot. (Again, I'm not using anything other than the "default" skin.) I wonder why this is happening? Here is what I've got - http://kyttias.tk/chi_avatar/ - set a gender and skin tone and then change the animation - it resets to slot defaults! D; (Also, that load time? That's why I need to be able to cache my images and why I'd rather not use atlas files at all, and only relative paths to what's currently being used.)

Also, some animations will need to have certain slot attachments changed dynamically with code. There will be multiple eye shapes/styles and while I do need to cue the 'aggressive' expression during the battle animation, it'll be like "okay, on frame 5, I need eye_style1_aggressive" ... or style2, or style3 or so on and so forth, depending on which one the player wants to be using. And in the future, eye color, as well. Variables, man. x'D

SkeletonData has a list of SlotDatas, which each have an attachment field that is the name of the attachment to show in the setup pose.

If you key the slot attachments, then animations will change which attachments are visible. Otherwise I assume you call setToSetupPose or setSlotsToSetupPose.

Use skins to allow animations to change attachments while still having the attachments be configurable. Read this section. You can generate a skin at runtime and populate it with the attachments you want to use.