【Unity】实现UI Spine动态换皮肤(SkeletonGraphic)
针对UI的Spine进行动态局部换皮肤行为,换的是Spine插槽的附件贴图。
using System.Collections.Generic;
using Spine;
using Spine.Unity;
using UnityEngine;
using Spine.Unity.AttachmentTools;public class UISpineChangeSkin : MonoBehaviour
{private SkeletonGraphic skeletonAnimation;private SkeletonDataAsset skeletonDataAsset;private Material sourceMaterial;private string skinName = "default";[SpineSkin] private string templateSkinName;//模板皮肤名称(初始)private Spine.Skin equipsSkin;private Dictionary<int, Dictionary<string, bool>> slotIndexAttachDict;public void Init(){skeletonAnimation = GetComponent<SkeletonGraphic>();skeletonDataAsset = skeletonAnimation.SkeletonDataAsset;sourceMaterial = skeletonDataAsset.atlasAssets[0].PrimaryMaterial;ReSetSkin();}public void Destroy(bool ignore = false){if (slotIndexAttachDict != null && slotIndexAttachDict.Count > 0){foreach (var v in slotIndexAttachDict){int slotIndex = v.Key;foreach (var p in v.Value){string attachmentName = p.Key;var lastAttachment = equipsSkin.GetAttachment(slotIndex, attachmentName);var mat = lastAttachment.GetMaterial();if (mat != null){if (mat.mainTexture != null){GameObject.Destroy(mat.mainTexture);}GameObject.Destroy(mat);}equipsSkin.RemoveAttachment(slotIndex, attachmentName);}}}if (!ignore){equipsSkin.Clear();equipsSkin = null;}}private void ReSetSkin(){var skeleton = skeletonAnimation.Skeleton;var skin = skeleton.Skin;if (skin == null)templateSkinName = "default";elsetemplateSkinName = skin.Name;equipsSkin = new Skin("Equips");var skeletonData = skeletonAnimation.Skeleton.Data;var tSkin = skeletonData.FindSkin(templateSkinName);if (tSkin != null)equipsSkin.AddAttachments(tSkin);skeletonAnimation.Skeleton.Skin = equipsSkin;RefreshSkeletonAttachments();}/// <summary>/// 刷新皮肤/// </summary>void RefreshSkeletonAttachments(){skeletonAnimation.Skeleton.SetSlotsToSetupPose();skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton);}public void ChangeImage(Sprite sprite, string slotName, string attachmentName){SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);int slotIndex = skeletonData.FindSlot(slotName).Index;Attachment attachment = GenerateAttachmentFromEquipAsset(sprite, slotIndex, skinName, attachmentName);var lastAttachment = equipsSkin.GetAttachment(slotIndex, attachmentName);if (lastAttachment != null){if (slotIndexAttachDict != null && slotIndexAttachDict.ContainsKey(slotIndex) &&slotIndexAttachDict[slotIndex].ContainsKey(attachmentName)&& slotIndexAttachDict[slotIndex][attachmentName]){var mat = lastAttachment.GetMaterial();if (mat != null){if (mat.mainTexture != null){GameObject.Destroy(mat.mainTexture);}GameObject.Destroy(mat);}equipsSkin.RemoveAttachment(slotIndex, attachmentName);}else{if (slotIndexAttachDict == null)slotIndexAttachDict = new Dictionary<int, Dictionary<string, bool>>();if (!slotIndexAttachDict.ContainsKey(slotIndex))slotIndexAttachDict.Add(slotIndex, new Dictionary<string, bool>());if (!slotIndexAttachDict[slotIndex].ContainsKey(attachmentName))slotIndexAttachDict[slotIndex].Add(attachmentName, true);equipsSkin.RemoveAttachment(slotIndex, attachmentName);}}equipsSkin.SetAttachment(slotIndex, slotName, attachment);skeletonAnimation.Skeleton.SetSkin(equipsSkin);RefreshSkeletonAttachments();}private Attachment GenerateAttachmentFromEquipAsset(Sprite sprite, int slotIndex, string templateSkinName, string templateAttachmentName){Attachment attachment = null;if (attachment == null){SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);Skin templateSkin = skeletonData.FindSkin(templateSkinName);Attachment templateAttachment = templateSkin.GetAttachment(slotIndex, templateAttachmentName);attachment = templateAttachment.GetRemappedClone(sprite, sourceMaterial, premultiplyAlpha: true);}return attachment;}}用法:
//初始化Spine
skeletonGraphic.Initialize(true);
//监听Spine事件
skeletonGraphic.AnimationState.Complete += ...;uiSpineChangeSkin.Init();//初始化换皮组件uiSpineChangeSkin.ChangeImage(sprite, "插槽名称", "附件名称"); //更换Spine图像uiSpineChangeSkin.Destroy();//销毁换皮组件//注销Spine事件
skeletonGraphic.AnimationState.Complete -= ...;//*重要* 卸载Spine缓存资源(大量使用GetRemappedClone导致内存泄露问题)
AtlasUtilities.ClearCache();注意这里的调用顺序,skeletonGraphic.Initialize(true) 调用后会恢复成原本的皮肤,所以一定要在最前面进行。而AtlasUtilities.ClearCache();是释放缓存的,基本都在游戏结束调用 或者 内存不足时强制调用 (切记频繁调用 有性能开销)
