1.Spine元素主要包含皮肤(Skin)、骨骼(Bone)、插槽(Slot)、附件(Attachment)、及附件下的图片。
2.而皮肤(Skin)包含了插槽信息、附件信息,如果我们有两套相同构成的皮肤,原理上就可以通过拼装皮肤上的插槽实现局部换肤功能。
3.创建新的Skin 替换槽点下的附件的Sprite,实现动态换皮。
4.Spine 换皮代码:
using System;
using Spine;
using Spine.Unity;
using Spine.Unity.AttachmentTools;
using UnityEngine;
/// <summary>
/// 换皮测试
/// </summary>
public class SpineChangeSkin : MonoBehaviour
{
private SkeletonAnimation skeletonAnimation;
private SkeletonDataAsset skeletonDataAsset;
private Material sourceMaterial;
private SkeletonData skeletonData;
private bool applyPma = true;
[SpineSkin] private string templateSkinName;//模板皮肤名称(初始)
private Skin equipsSkin;
private Skin collectedSkin;
#region 优化皮肤用
private Material runtimeMaterial;
private Texture2D runtimeAtlas;
#endregion
#region ChangeShin需要的 外部可显示 选择
//[SpineSlot] public string slot1; //骨骼槽点
//[SpineSkin] public string templateSkin;
//[SpineAttachment(skinField: "templateSkin")]
//public string templateAttachment;
#endregion
void Start()
{
skeletonAnimation = GetComponent<SkeletonAnimation>();
skeletonDataAsset = skeletonAnimation.SkeletonDataAsset;
sourceMaterial = skeletonDataAsset.atlasAssets[0].PrimaryMaterial;
skeletonData = skeletonAnimation.Skeleton.Data;
ReSetSkin();
}
private void ReSetSkin()
{
var skeleton = skeletonAnimation.skeleton;
var skin = skeleton.Skin;
if (skin == null)
templateSkinName = "default";
else
templateSkinName = skin.Name;
equipsSkin = new Skin("Equips");
var tSkin = skeletonData.FindSkin(templateSkinName);
if (tSkin != null)
equipsSkin.AddAttachments(tSkin);
skeletonAnimation.Skeleton.Skin = equipsSkin;
RefreshSkeletonAttachments();
}
/// <summary>
/// 局部换皮
/// </summary>
/// <param name="slot">骨骼槽点昵称</param>
/// <param name="attachmentName">SpineAttachment 类型 一般和slot名称相同</param>
/// <param name="sprite">要更改的贴图[需要开启读写功能]</param>
public void ChangeShin(string slot, string attachmentName,Sprite sprite)
{
int slotIndex = skeletonData.FindSlot(slot).Index;//根据骨骼槽点 找到槽点索引
Attachment attachment = GenerateAttachment(slotIndex, templateSkinName, attachmentName, sprite);
if (attachment == null)return;
equipsSkin.SetAttachment(slotIndex, attachmentName, attachment);
skeletonAnimation.Skeleton.SetSkin(equipsSkin);
RefreshSkeletonAttachments();
OptimizeSkin();
}
private Attachment GenerateAttachment(int slotIndex, string tSkinName, string attachmentName,Sprite sprite)
{
var sData = skeletonDataAsset.GetSkeletonData(true);
var tSkin = sData.FindSkin(tSkinName);
Attachment tAttachment = tSkin.GetAttachment(slotIndex, attachmentName);
if (tAttachment == null)
{
Debug.LogError($"skin {tSkinName} slotIndex {slotIndex} attachmentName {attachmentName} ==》attachment=null");
return null;
}
var attachment = tAttachment?.GetRemappedClone(sprite, sourceMaterial, premultiplyAlpha: this.applyPma);
return attachment;
}
/// <summary>
/// 刷新皮肤
/// </summary>
void RefreshSkeletonAttachments()
{
skeletonAnimation.Skeleton.SetSlotsToSetupPose();
skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); //skeletonAnimation.Update(0);
}
/// <summary>
/// 优化皮肤 将换过皮肤的贴图 整合到一张贴图中 [人物贴图需要开启读写功能]
/// </summary>
public void OptimizeSkin()
{
try
{
// 1. 收集所有活动皮肤的所有附件。
collectedSkin = collectedSkin ?? new Skin("Collected skin");
collectedSkin.Clear();
collectedSkin.AddAttachments(skeletonAnimation.Skeleton.Data.DefaultSkin);
collectedSkin.AddAttachments(equipsSkin);
// 2. 创造一个重新包装的皮肤。
// 注意:GetRepackedSkin()返回的材质和纹理行为类似于'new Texture2D()',需要销毁
if (runtimeMaterial)
Destroy(runtimeMaterial);
if (runtimeAtlas)
Destroy(runtimeAtlas);
var repackedSkin = collectedSkin.GetRepackedSkin("Repacked skin",sourceMaterial, out runtimeMaterial,out runtimeAtlas);
collectedSkin.Clear();
// 3.使用重新包装的皮肤。
skeletonAnimation.Skeleton.Skin = repackedSkin;
RefreshSkeletonAttachments();
}
catch (Exception e)
{
Debug.LogError($"OptimizeSkin 异常:{e.Message}");
}
}
}
5.测试代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Test : MonoBehaviour
{
public SpineSkinDate[] array;//换局部皮的Sprite
private SpineChangeSkin spineChangeSkin;
private int index = 0;
private void Start()
{
spineChangeSkin = GetComponent<SpineChangeSkin>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
if (array == null || array.Length < 1) return;
if (index >= array.Length)
index = 0;
var skinData = array[index];
index++;
spineChangeSkin?.ChangeShin(skinData.slotName, skinData.attachmentName, skinData.sprite);
}
}
}
[Serializable]
public struct SpineSkinDate
{
public string slotName;
public string attachmentName;
public Sprite sprite;
}