Unity Spine 局部换皮

本文详细介绍了Spine游戏引擎中如何实现角色的动态换肤功能,通过创建和替换皮肤中的插槽附件,实现局部换皮。提供了一段Unity C#代码示例,展示了如何在运行时改变角色的皮肤,并通过`SpineChangeSkin`类的`ChangeShin`方法进行局部换肤操作。同时,还展示了如何通过`OptimizeSkin`方法优化皮肤,将换过的皮肤贴图整合到一张新贴图中,提高性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值