• Unity
  • Hide/show attachment/slot via code?

  • Editado
Related Discussions
...

I'm sure this is simple, but I couldn't find it in the documentation or search in the forum/google.

How can I show an attachment, which is hidden in the animation? The code below is based on the MixAndMatch-code.
In the Spine editor, i need to enable both the slot and the attachment, so I guess it's good to know how to do both with code.

Thank you in advance!

var slotIndex = skeleton.FindSlotIndex(slot[i].spineSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
Attachment templateAttachment = templateSkin.GetAttachment(slotIndex, slot[i].spineAttachment);    // 1.1 Find the original/template attachment.
Attachment newAttachment = templateAttachment.GetRemappedClone(slot[i].sprite, sourceMaterial);    // 1.2 Get a clone of the original/template attachment. +          // Step 1.3 Apply the Sprite image to the clone.
if (newAttachment != null) {
   customSkin.SetAttachment(slotIndex, slot[i].spineAttachment, newAttachment);   // 1.4 Add the remapped clone to the new custom skin.
   slot[i].spineSlot.show() // NOT WORKING. :)
}

The documentation you need is here:
Runtime Skeletons - Spine Runtimes Guide: Changing attachments

At runtime there is no equivalent of disabling display of the slot, that is only in the editor. Instead the slot has an attachment, see Slot attachment. You can set the attachment to whatever you like, or set it to null to show nothing for that slot. As for where you get the attachment, attachments are stored in skins. Skeleton getAttachment is an easy way to get an attachment from the current or default skin. Otherwise you can use SkeletonData findSkin and then get the attachment from the skin, eg with Skin getAttachment.

    Thanks a lot Nate, that should help me figure it out! 🙂


    Hmm, what's the difference between SetAttachment to null, and RemoveAttachment?
    I'm using the mix and match, and want to hide attachment on a slot, so I'm making a custom skin, and in some cases it shouldn't have any attachments for a slot, so shouldn't i remove it instead of setting to null?

    When I do this:

    customSkin.SetAttachment(slotIndex, "", null);

    I get System.ArgumentNullException: attachment cannot be null.

    Is this different from SetAttachment on a skeleton? And what should I put in name (the second parameter), when I want to remove it?

    I tried something like this:

    foreach (var attachment in customSkin.GetAttachments()) {
       customSkin.RemoveAttachment(slotIndex, attachment.Attachment.Name);
    };
    
    

    Do I need to remove each attachment by name for a slot?

    Thank you in advance 🙂

    nicmar escribió

    Hmm, what's the difference between SetAttachment to null, and RemoveAttachment?

    Slot setAttachment sets the slots current attachment, which may be null. Skin removeAttachment removes an attachment from a skin. Skin setAttachment adds an attachment to a skin, and you cannot add null. A skin is just a collection of attachments while the slot's attachment is what will get rendered.

    I suggest reading through the spine-csharp code to understand the classes and how they work. It's not much code and very straightforward. There is no reason to poke at it as if it were a black box when the source is provided. 🙂 glares at Unity :wounded:

    Hey Nate,
    I've been looking a lot at the code, it's very nice that it's easy to see it, and even Unity's code is easily viewed decompiled in my IDE 🙂 But anyhow, I haven't been able to solve the problem.

    Just to explain what I'm trying to do, I'm making a script based on the MixAndMatch demo, to create a custom skin, and set attachments to different sprites. It works when I set sprites, but I can't figure out how to remove one.

    Here's what the spine file looks like, in the editor, I click the dot on "backpack_side" to hide that region, and that's what I want to do in code, as you say you can't disable the slot.

    In my default skin, my character has a slot called "Backpack". Now when equipping a weapon, I'm making a custom skin, that changes two slots:

    • weapon_shotgun (Change weapon)
    • Backpack (Change to another backpack)

    In the end of this post is the unchanged code from the MixAndMatch.cs example. What I changed is that I loop through a list of slots and what attachments and sprites to change.

    There is a commented line like this:

    // customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.

    I'm not sure why i should provide both the index and the key, as I just cached the index using skeleton.FindSlotIndex. Should that be both the index and the name "Backpack".

    And if I don't change it in the skin, where do I add a line to set an empty attachment for the slot? Also do I need to apply the changes or update an animation or something to make it work?

    I'm sorry for the long question, I'm really not used to how skins work, so that might also be the reason for the misunderstanding. But I'm really stuck and hope you can help... thanks again 🙂

    // This is an example script that shows you how to change images on your skeleton using UnityEngine.Sprites.
       public class MixAndMatch : MonoBehaviour {
    
      #region Inspector
      [SpineSkin]
      public string templateAttachmentsSkin = "base";
      public Material sourceMaterial; // This will be used as the basis for shader and material property settings.
    
      [Header("Visor")]
      public Sprite visorSprite;
      [SpineSlot] public string visorSlot;
      [SpineAttachment(slotField:"visorSlot", skinField:"baseSkinName")] public string visorKey = "goggles";
    
      [Header("Gun")]
      public Sprite gunSprite;
      [SpineSlot] public string gunSlot;
      [SpineAttachment(slotField:"gunSlot", skinField:"baseSkinName")] public string gunKey = "gun";
    
      [Header("Runtime Repack")]
      public bool repack = true;
      public BoundingBoxFollower bbFollower;
    
      [Header("Do not assign")]
      public Texture2D runtimeAtlas;
      public Material runtimeMaterial;
      #endregion
    
      Skin customSkin;
    
      void OnValidate () {
         if (sourceMaterial == null) {
            var skeletonAnimation = GetComponent<SkeletonAnimation>();
            if (skeletonAnimation != null)
               sourceMaterial = skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;
         }
      }
    
      IEnumerator Start () {
         yield return new WaitForSeconds(1f); // Delay for one second before applying. For testing.
         Apply();
      }
    
      void Apply () {
         var skeletonAnimation = GetComponent<SkeletonAnimation>();
         var skeleton = skeletonAnimation.Skeleton;
    
         // STEP 0: PREPARE SKINS
         // Let's prepare a new skin to be our custom skin with equips/customizations. We get a clone so our original skins are unaffected.
         customSkin = customSkin ?? new Skin("custom skin"); // This requires that all customizations are done with skin placeholders defined in Spine.
         //customSkin = customSkin ?? skeleton.UnshareSkin(true, false, skeletonAnimation.AnimationState); // use this if you are not customizing on the default skin.
         var templateSkin = skeleton.Data.FindSkin(templateAttachmentsSkin);
    
         // STEP 1: "EQUIP" ITEMS USING SPRITES
         // STEP 1.1 Find the original/template attachment.
         // Step 1.2 Get a clone of the original/template attachment.
         // Step 1.3 Apply the Sprite image to the clone.
         // Step 1.4 Add the remapped clone to the new custom skin.
    
         // Let's do this for the visor.
         int visorSlotIndex = skeleton.FindSlotIndex(visorSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
         Attachment templateAttachment = templateSkin.GetAttachment(visorSlotIndex, visorKey); // STEP 1.1
         Attachment newAttachment = templateAttachment.GetRemappedClone(visorSprite, sourceMaterial); // STEP 1.2 - 1.3
         customSkin.SetAttachment(visorSlotIndex, visorKey, newAttachment); // STEP 1.4
    
         // And now for the gun.
         int gunSlotIndex = skeleton.FindSlotIndex(gunSlot);
         Attachment templateGun = templateSkin.GetAttachment(gunSlotIndex, gunKey); // STEP 1.1
         Attachment newGun = templateGun.GetRemappedClone(gunSprite, sourceMaterial); // STEP 1.2 - 1.3
         if (newGun != null) customSkin.SetAttachment(gunSlotIndex, gunKey, newGun); // STEP 1.4
    
         // customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.
         // customSkin.Clear()
         // Use skin.Clear() To remove all customizations.
         // Customizations will fall back to the value in the default skin if it was defined there.
         // To prevent fallback from happening, make sure the key is not defined in the default skin.
    
         // STEP 3: APPLY AND CLEAN UP.
         // Recommended, preferably at level-load-time: REPACK THE CUSTOM SKIN TO MINIMIZE DRAW CALLS
         //             IMPORTANT NOTE: the GetRepackedSkin() operation is expensive - if multiple characters
         //             need to call it every few seconds the overhead will outweigh the draw call benefits.
         //
         //             Repacking requires that you set all source textures/sprites/atlases to be Read/Write enabled in the inspector.
         //             Combine all the attachment sources into one skin. Usually this means the default skin and the custom skin.
         //             call Skin.GetRepackedSkin to get a cloned skin with cloned attachments that all use one texture.
         if (repack)   {
            var repackedSkin = new Skin("repacked skin");
            repackedSkin.AddAttachments(skeleton.Data.DefaultSkin); // Include the "default" skin. (everything outside of skin placeholders)
            repackedSkin.AddAttachments(customSkin); // Include your new custom skin.
            // Note: materials and textures returned by GetRepackedSkin() behave like 'new Texture2D()' and need to be destroyed
            if (runtimeMaterial)
               Destroy(runtimeMaterial);
            if (runtimeAtlas)
               Destroy(runtimeAtlas);
            repackedSkin = repackedSkin.GetRepackedSkin("repacked skin", sourceMaterial, out runtimeMaterial, out runtimeAtlas); // Pack all the items in the skin.
            skeleton.SetSkin(repackedSkin); // Assign the repacked skin to your Skeleton.
            if (bbFollower != null) bbFollower.Initialize(true);
         } else {
            skeleton.SetSkin(customSkin); // Just use the custom skin directly.
         }
    
         skeleton.SetSlotsToSetupPose(); // Use the pose from setup pose.
         skeletonAnimation.Update(0); // Use the pose in the currently active animation.
    
         Resources.UnloadUnusedAssets();
      }
       }
    nicmar escribió

    I've been looking a lot at the code, it's very nice that it's easy to see it, and even Unity's code is easily viewed decompiled in my IDE 🙂

    That's only the not very interesting C# parts though, major parts are hidden in external C++ code. Typically questions arise in the parts which are neither documented (or incorrectly documented) nor lie in the C# layer.

    No need to decompile it, as Unity shared it officially here:
    https://github.com/Unity-Technologies/UnityCsReference

    nicmar escribió

    I'm not sure why i should provide both the index and the key, as I just cached the index using skeleton.FindSlotIndex. Should that be both the index and the name "Backpack".

    The index is the slot index. The name is the skin placeholder name. The slot may have the same name as the skin placeholder, but it doesn't have to.

    where do I add a line to set an empty attachment for the slot? Also do I need to apply the changes or update an animation or something to make it work?

    Use Slot attachment to change the current attachment. Changing the attachments in the skin does not affect the slot, not until something uses the skin to find an attachment and set it on the slot. The most common things that do that is Skeleton setToSetupPose, Skeleton setSlotsToSetupPose, and Skeleton setAttachment. That last one is used by animations that set attachments (AttachmentTimeline) and that is the main reason for a skin: it allows animations to set attachments using the skin placeholder names, while the actual attachments that are used come from however you configured the skin. Without a skin providing a layer of indirection, animations would set a specific attachment and there would be no way to reuse that same animation with a different attachment


    you'd have to modify the animation. If you are not using animations that set attachments, you can just set the attachments on your slots directly and don't need to use a skin at all.

    It finally worked! The issue was that the animation kept setting the attachments, but when I removed it from there, it was easy to control with code. Sorry for overlooking that and thanks again for your help 🙂

    3 años más tarde

    Nate
    I tried to set the attachment to null, but it will still display nearby. After debugging, I found that the attachment on the slots of the skeleton drawer has not changed. I don’t know if this is the reason for my current predicament.

    • Nate respondió a esto

      akeboshi Note this thread is from 3 years ago. If you apply an animation, it may change bones and attachment visibility. If you want to set those using code, you can either make sure you don't key changes to the things you set via code, or you can make your changes every frame after you have applied animations.

        Nate
        Oh, Nate, I made sure to replace the RegionAttachment with null at runtime, not animations, but the attachment of the slot didn't change at runtime, and I don't know what the problem is

        Nate
        When changing clothes, try to deal with it like this, but the effect cannot be achieved

        Oh, Nate, I made sure to replace the RegionAttachment with null at runtime, not animations, but the attachment of the slot didn't change at runtime, and I don't know what the problem is

        @akeboshi I'm not sure you understood what Nate meant in the above posting. If you have any animation running which keys the slot to a certain attachment, this animation will set the attachment and override your other code changes. That is, unless your code changes the attachment after the animation is applied each frame.

        So to resolve the issue, the solution would be to either

        • a) ensure that there are no keys in your animation which set the attachment at your problematic slot, or
        • b) to change to attachment via code every frame after the animation has been applied (after SkeletonAnimation.Update) before the skeleton Mesh is updated (before SkeletonAnimation.LateUpdate). You could use the SkeletonAnimation.UpdateComplete callback delegate for this (see the documentation section SkeletonAnimation Update Callbacks).

        The first solution under (a) is the easier and recommended one.

          Harald
          I understand what you mean. There is still a deviation between my previous thinking and the actual situation. Now I have to adjust the resources of the spine in editing mode and increase the skin occupation

          Glad it helped, thanks for getting back to us.