• RuntimesUnity
  • Clearing attachements with Mix and Match Script.

Hi,
We're trying to use the mix and match example script using attachments, but can't get clearing the attachment to work.

In our example, we can change the equipment and set attachments without problem. But we can't remove by setting the slot's attachment to null. The second image is what we'd like to achieve (hiding the shoes completely)


Here's the code we are using, we equip the shoe which works but when we try clearing the shoe we are stuck with the red shoe (which we just equipped).

using UnityEngine;
using Spine;
using Spine.Unity;
using Spine.Unity.AttachmentTools;
using NightGarden.Core.Systems;
using System;
using System.Collections.Generic;

[Serializable]
public class Clearable
{
    [SpineSlot] public string SpineSlot;
    [SpineAttachment(slotField = "SpineSlot")] public string SpineAttachment = "";
}

[Serializable]
public class Equipable
{
    public Sprite SourceSprite;
    [SpineSlot] public string SpineSlot;
    [SpineAttachment(slotField: "SpineSlot")] public string SpineAttachment = "";
}

[RequireComponent(typeof(SkeletonAnimation))]
public class MixAndMatchEquip : BaseBehaviour
{
    #region -- Inspector --
    [SpineSkin] public string CharacterSkin = "";
    [SpineSkin] public string TemplateAttachmentsSkin = "";
    public List<Clearable> Clearables = new List<Clearable>();
    public List<Equipable> Equipables = new List<Equipable>();

    public Material SourceMaterial;
    [SerializeField] private Material RuntimeMaterial = default;
    [SerializeField] private Texture2D RuntimeAtlas = default;
    #endregion

    #region -- Private Member Vars --
    private Skin _characterSkin;
    private SkeletonAnimation _skeletonAnimation;
    #endregion

    #region -- Private Methods --
    private void Awake()
    {
        _skeletonAnimation = GetComponent<SkeletonAnimation>();
    }

    private void OnValidate()
    {
        if (SourceMaterial == null)
        {
            SkeletonAnimation skeletonAnimation = GetComponent<SkeletonAnimation>();
            if (skeletonAnimation != null)
            {
                SourceMaterial = skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;
            }
        }
    }
    #endregion

    #region -- Protected Methods --
    protected override void Start()
    {
        base.Start();
        Generate();
    }
    #endregion

    #region -- Public Methods --
    public void Generate()
    {
        Skeleton skeleton = _skeletonAnimation.Skeleton;
        if (SourceMaterial == null)
        {
            if (_skeletonAnimation != null)
            {
                SourceMaterial = _skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;
            }
        }

        _characterSkin = _characterSkin ?? new Skin("Custom Skin");
        _characterSkin.AddSkin(skeleton.Data.FindSkin(CharacterSkin));

        Skin templateSkin = skeleton.Data.FindSkin(TemplateAttachmentsSkin);


        foreach(var equipable in Equipables)
        {
            int slotIndex = skeleton.Data.FindSlot(equipable.SpineSlot).Index;
            Attachment templateAttachement = templateSkin.GetAttachment(slotIndex, equipable.SpineAttachment);
            Attachment newAttachment = templateAttachement.GetRemappedClone(equipable.SourceSprite, SourceMaterial, pivotShiftsMeshUVCoords: false);
            if (newAttachment != null)
            {
                _characterSkin.SetAttachment(slotIndex, equipable.SpineAttachment, newAttachment);
            }
        }

        foreach (var clearable in Clearables)
        {
            int slotIndex = skeleton.Data.FindSlot(clearable.SpineSlot).Index;
            //templateSkin.RemoveAttachment(slotIndex, clearable.SpineAttachment);
            Slot slot = skeleton.FindSlot(clearable.SpineSlot);
            slot.Attachment = null;
        }

        skeleton.SetSkin(_characterSkin);

        skeleton.SetSlotsToSetupPose(); // Use the pose from setup pose.
        _skeletonAnimation.Update(0); // Use the pose in the currently active animation.
    }

    public void OptimizeSkin()
    {
        Skin previousSkin = _skeletonAnimation.Skeleton.Skin;

        if (RuntimeMaterial != null)
        {
            Destroy(RuntimeMaterial);
        }

        if (RuntimeAtlas != null)
        {
            Destroy(RuntimeAtlas);
        }

        Skin repackedSkin = previousSkin.GetRepackedSkin("Repacked skin", _skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial, out RuntimeMaterial, out RuntimeAtlas);

        previousSkin.Clear();
        _skeletonAnimation.Skeleton.Skin = repackedSkin;
        _skeletonAnimation.Skeleton.SetSlotsToSetupPose();
        _skeletonAnimation.AnimationState.Apply(_skeletonAnimation.Skeleton);

        AtlasUtilities.ClearCache();
        Resources.UnloadUnusedAssets();
    }
    #endregion

}

Cheers!

  • Изменено

Hi Mags you sound very cool. Try RemoveAttachment followed with SetSlotsToSetUpPose, you could use your cached skin, or just get the skin from your SkeletonAnimation.

    public void ClearAttachments()
    {
        Skeleton skeleton = _skeletonAnimation.Skeleton;
        var skin = _skeletonAnimation.Skeleton.Skin;
        //var skin = _characterSkin;
        foreach (var clearable in Clearables)
        {
            int slotIndex = skeleton.Data.FindSlot(clearable.SpineSlot).Index;
            skin.RemoveAttachment(slotIndex, clearable.SpineAttachment); // To remove an item.
        }
        skeleton.SetSlotsToSetupPose(); // Use the pose from setup pose.
    }

@Mags The problem in your original code is the order of calls here:

        foreach (var clearable in Clearables)
        {
            int slotIndex = skeleton.Data.FindSlot(clearable.SpineSlot).Index;
            //templateSkin.RemoveAttachment(slotIndex, clearable.SpineAttachment);
            Slot slot = skeleton.FindSlot(clearable.SpineSlot);
            slot.Attachment = null;
        }

        skeleton.SetSkin(_characterSkin);

Your call to skeleton.SetSkin() sets the attachments of the _characterSkin at the slots of your skeleton after you have cleared it beforehand. So moving the call to SetSkin() before the slot.Attachment = null; block should resolve your issue.

While you could also call skin.RemoveAttachment, note that there is still the default skin which provides common attachments which won't be cleared along with it. Please see the documentation here for more info.

Hi @Harald @KevinWillis
Thanks so much for your help.
I added a removal function that just sets the attachment to null, and that is done after the initial SetSkin call, and it is working thank you!

@Mags Glad to hear you've figured it out, thanks for getting back to us!