Fainder

  • Feb 19, 2024
  • Joined Jun 22, 2017
  • @pixilestudios thanks for the additional information! The problem is that some of the attachments are in the skins that you fetch with skeletonData.FindSkin(equippedCharacter.characterSpineSkinName), while others are in the default skin.

    For example, this is from @Fainder's screenshot of the Spine editor side setup:

    body_spring-dress_red and body_spring-dress_yellow are not part of a skin. Instead, they are direct attachments in that specific slot. These direct attachments are stored in the default skin.

    Let me try to illustrate this with a simpler example:

    This is the (simplified) skeleton of some character. It has legs with customizable pants, a torso with customizable shirt, and a head that's not customizable. In the example above, the blue skin is active. Each skin placeholder thus shows the attachment assigned to it while the blue skin was active.

    Here's the same skeleton with the red skin active:

    The head attachment stays where it is, as it is not in a skin placeholder, but directly attached to a slot. However, attachments of the pants and shirt placeholder have changed to the ones assigned while the red skin was active.

    This is one way to setup customizable characters, where each skin represents a set of attachments for one specific set of full clothing. There are other ways to organize skins for customizable characters. We can get into that in follow up posts.

    Also note the images node which shows all the images used in the skeleton. When you create an atlas for this skeleton with the default settings, all those images from different skins will get packed into a single atlas. When the image of an attachment is resolved while loading a skin, it's name is used as a unique identifier within the atlas to locate the image. That's why we have shirt-red and shirt-blue as image names.

    Here's the exported JSON which shows you which skin contains what attachment (i've replaced some parts of the JSON with ...).

    {
    "skeleton": { ... },
    "bones": [ ... ],
    "slots": [
       { "name": "shirt", "bone": "torso", "attachment": "shirt" },
       { "name": "head", "bone": "head", "attachment": "head" },
       { "name": "pants", "bone": "legs", "attachment": "pants" }
    ],
    "skins": {
       "default": {
          "head": {
             "head": {
                "name": "head",
                ...            
    } } }, "blue": { "pants": { "pants": { "name": "pants-blue", ... } }, "shirt": { "shirt": { "name": "shirt-blue", "type": "mesh", ... } } }, "red": { "pants": { "pants": { "name": "pants-red", ... } }, "shirt": { "shirt": { "name": "shirt-red", "type": "mesh", ... } } } }, "animations": { ... } }

    Note how the skins section actually has 3 skins: default, red, blue. In each skin, we find attachments. To get to an attachment, we need: the skin name, the slot name, the attachment name, and finally the name of the image in the atlas (or some other place to fetch the actual image from). E.g. default -> head -> head -> head.

    You can also see, that the concept of skin placeholder has completely vanished in the exported JSON. Instead, we see what slots the skeleton has, and what attachments are on each slot (see the slots section in the JSON). The attachments are only referenced by name there. The skins in the skins section in the JSON are each merely a set of mappings from slot name to attachment name to image name in the atlas!

    Note how the red and blue skins do not contain an attachment for the head slot! That's because that head attachment is not in a skin placeholder, but directly attached to its slot head. Since it is not in a skin placeholder, it goes directly into the default skin. But that is only an editor side distinction really. At runtime, the attachments are fetched from the active skin (or the default skin, if no active skin is set, or the attachment can't be found in the active skin. See the Skeleton#GetAttachment method).

    Now, in your setup, you are trying to fetch a default skin attachment from a non-default skin. This is similar to trying to find the head attachment in red or blue skin, and will result in null being returned. You have to query default skin for the head attachment.

    How do you get to the default skin? Same as you do for any other skin:

    Spine.Skin defaultSkin = skeletonData.FindSkin("default");
    

    Coming back to your first problem: all your t-shirts are inside the default skin. When you try to fetch them from the current character skin, which is not the default skin, null will be returned. But since they are in the default skin, and since when attachments are resolved the default skin will be the fallback lookup, you do not need to add this default skin attachments to your custom skin!

    Which leads me to you general skin setup:

     Loading Image

    This is not good. From those screenshots, I can see that you have different item categories, e.g. t-shirt, suit. But instead of having a single skin placeholder for each item category (like shirt and pants in my example above), you use one skin placeholder per item (body_suite_black, body_suit_blue, etc.).

    Also, for some item categories, you aren't using skin placeholders at all! E.g. all the body_T-shirt-xxx items are directly attached to the slot, and all go into the default skin (which is why fetching them from a non-default skin returns null btw).

    There are different ways how to organize your skins and skin placeholders. Let me assume the following:

    • You have full outfits, e.g. a red outfit, a santa outfit, etc. These outfits consists of multiple attachments, e.g. one for the shirt, one for the pants, one for a hat (which might not exist in some outfits).

    • You want the user to be able to freely mix items from multiple outfits into a single new outfit.

    As a first step, think about all the item categories a custom can possibly be composed off. An outfit might consist of the following:

    • Shoes (left & right)

    • Pants (left & right, possible in 3 sections for the lower leg, upper leg, and hip area)

    • Shirt (again multiple sections, e.g. torso, left upper arm, left lower arm, right upper arm, right lower arm)

    • ...

    For each of these items, you create one or more skin placeholders (depending on the number of sections an item can be composed of). Note the absence of concrete identifiers like red, or pirate in these skin placeholder names. Per item (section) there is only one skin placeholder!

    Next, you create a folder, one for each outfit. In this folder, you place the images to be used for the item (section) attachments for this outfit. E.g. for a red outfit, you'd have an image for the left and right red shoe, images for the left & right sections for the red pants, images for the sections of the red shirt, and so on. The images will have paths like red-outfit/shoe-left.

    Next, create a new skin for the outfit and make it active. Drag the images to their corresponding skin placeholder. Here's a trivial example:

    Note that a skin does not necessarily need to have attachments for all skin placeholders! E.g. a male bathing suite might not have any shoes or shirt. In that case, the skin placeholders are empty.

    And that's it! Of course, you can also use meshes, and even linked meshes if you use mesh free-form deformations in your animations and want to share that FFD animation across different skin attachments.

    But the important part is that you have one skin placeholder per item category (and possibly subsections), and a nicely organized folder hierarchy for the source images of attachments. You can also just pack that entire image folder with sub-directories into a single atlas (provided you don't have thousands of huge source images, which requires a much more complicated on-demand-loading solution for atlases and skins).

    At runtime, you present the user with all possible items from all available skins. To get to all those items, walk through all the skins and their respective attachments and display them in a customization UI. For each selected item, you note down the skin it comes from, the slot it goes onto, and the attachment name. With that information in hand for all selected items, you fetch the attachments from the respective skins and combine them into a single skin, like you already do.

    The setup above is outfit centric. You can also turn it on its head and make it item category centric. Instead of having one image folder per outfit (red, pirate, ...), you have one image folder per item category (shoes, pants, ...). But the basic principles still apply.

    In summary:

    1. Refamiliarize yourself how skins, skin placeholders, and attachments work both in the editor, as well as at runtime.

    2. Based on that understanding, reconsider and possibly rework your current skin and skin placeholder setup.

    If you are early enough in the development process, fixing your current setup will pay off immensely later on down the line. On our end, we will try to add more documentation about workflows for customization on both the editor and runtime side.

  • @Fainder, Spine should be clear about why an animation can't be imported. Can you send the Spine files so we can see the problem?

    Spine only enforces name uniqueness where required. Eg, attachments within a slot must have the same name, but under a placeholder they don't. It isn't clear what you mean by "duplicate names". Maybe you can point out what you mean in your project?