Unity动态换装之Spine换装
Unity动态换装之Spine换装
注:转载请注明转载,并附原链接 http://www.cnblogs.com/liaoguipeng/p/5867510.html 燕双飞情侣
一、动态换装原理
- 换装,无非就是对模型的网格,或者贴图进行针对性置换;
- 对于3D局部换装,我们可能需要单独换模型和贴图,也可能只需要单独置换贴图即可
- 对与Spine2D角色换装,我们基本上只需要针对性置换贴图,也就是Slot插槽上对应的附着物Attachment即可
-
二、换装理论分析
- Spine目前提供的换装是整体换装,也就是动画那边做好几套Skin,需要哪套直接调用SKeletonAnimation中的InitialSkin进行置换就行了,这个看起来很简单嘛。
- 但是,如果我们需要局部换装,难道让动画把每个局部都单独列出来,比如我们一个角色10套
- 皮肤,每套皮肤有对于10个位置可以进行任意更换,那么动画岂不是要做10! = 3 628 800 套皮肤?计算机装得下?程序调用逻辑不会出错?这么看来,这个方案不科学。
- 那么,我们有没有一种方法,可以实现到局部换装?于是,开始针对Spine导出文件进行分析;Spine可到处二进制和Json数据文件,为了方便分析,我们这次使用Json文件进行分析:
Spine导出文件有Png,Json和Altas,Png只是静态贴图,我们暂且可以忽略;那么我们观察Altas,截取部分数据:1 Lead.png 2 size: 2048,128 3 format: RGBA8888 4 filter: Linear,Linear 5 repeat: none 6 L_hand000 7 rotate: true 8 xy: 790, 69 9 size: 57, 87 10 orig: 57, 87 11 offset: 0, 0 12 index: -1 13 L_leg000 14 rotate: true 15 xy: 1354, 93 16 size: 33, 91 17 orig: 33, 91 18 offset: 0, 0 19 index: -1
并没有多大作用… 那么我们在看看另一个Json文件,截取部分信息:
套装1: clothing001部分插槽和贴图数据1 "clothing001": { 2 // 此处省略一大堆动画数据和插槽数据,我们目前看套装1的武器 weapon_C部分 3 "hair_C": { 4 "hair_C": { "name": "clothing001/hair001", "x": -14.38, "y": -11.92, "rotation": -93.18, "width": 100, "height": 78 } 5 }, 6 "shield_C": { 7 "shield_C": { "name": "clothing001/shield001", "x": 20.78, "y": -0.75, "rotation": -11.65, "width": 62, "height": 77 } 8 }, 9 "weapon_C": { 10 "weapon_C": { "name": "clothing001/weapon001", "x": 50.69, "y": -1.75, "rotation": -153.42, "width": 142, "height": 87 } 11 } 12 },
套装2:clothing002部分插槽和贴图数据1 "clothing002": { 2 "hair_C": { 3 "hair_C": { "name": "cloting002/hair002", "x": 15.26, "y": -9.01, "rotation": -93.18, "width": 84, "height": 63 } 4 }, 5 "shield_C": { 6 "shield_C": { "name": "cloting002/shield002", "x": 20.78, "y": -0.75, "rotation": 92.7, "width": 80, "height": 84 } 7 }, 8 "weapon_C": { 9 "weapon_C": { "name": "cloting002/weapon002", "x": 74.02, "y": 0.77, "rotation": -153.42, "width": 190, "height": 108 } 10 } 11 } 12 },
通过数据对比,我们可以发现套装数据结构一致,只是内部存储的数据不同,那么我们尝试将clothing001套装和clothing002套装内的其中一部分数据进行对调,比如我们对调“weapon_C”的所有数据,把文件导入Unity会发生什么呢?
对调前:
“cloth001” “cloth002”
对调后:
“cloth001” “cloth002”
预料之中,那么局部换装的实现,就应该不成问题了,那么我们回到代码层开始分析.
三、Spine底层库源代码分析
- 我们先观察Spine提供的整套换装源代码:1 namespace Spine.Unity { 2 [ExecuteInEditMode, RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent] 3 [AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")] 4 public class SkeletonGraphic : MaskableGraphic, ISkeletonComponent, IAnimationStateComponent, ISkeletonAnimation { 5 6 #region Inspector 7 public SkeletonDataAsset skeletonDataAsset; 8 public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } 9 10 [SpineSkin(dataField:"skeletonDataAsset")] 11 public string initialSkinName = "default"; 12 13 [SpineAnimation(dataField:"skeletonDataAsset")] 14 public string startingAnimation; 15 public bool startingLoop; 16 public float timeScale = 1f; 17 public bool freeze;
根据initialSkinName我们继续往下查找1 // Set the initial Skin and Animation 2 if (!string.IsNullOrEmpty(initialSkinName)) 3 skeleton.SetSkin(initialSkinName); 41 /// <summary>Sets a skin by name (see SetSkin).</summary> 2 public void SetSkin (String skinName) { 3 Skin skin = data.FindSkin(skinName); 4 if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); 5 SetSkin(skin); 6 }1 /// <summary>Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default 2 /// skin}. Attachmentsfrom the new skin are attached if the corresponding attachment from the old skin was attached. If 3 /// there was no old skin, each slot's setup mode attachment is attached from the new skin.</summary> 4 /// <param name="newSkin">May be null.</param> 5 public void SetSkin (Skin newSkin) { 6 if (newSkin != null) { 7 if (skin != null) 8 newSkin.AttachAll(this, skin); 9 else { 10 ExposedList<Slot> slots = this.slots; 11 for (int i = 0, n = slots.Count; i < n; i++) { 12 Slot slot = slots.Items[i]; 13 String name = slot.data.attachmentName; 14 if (name != null) { 15 Attachment attachment = newSkin.GetAttachment(i, name); 16 if (attachment != null) slot.Attachment = attachment; 17 } 18 } 19 } 20 } 21 skin = newSkin; 22 }
就是这个函数,SetSkin(Skin newSkin),通过传入Skin套装数据,遍历所有对应的骨骼,更新骨骼上对应的贴图数据。
- 那么,思路就来了,我们Spine进行局部换装,不也就是更新某个SKin中某个Slot上对应的Attachment数据么?
因此,想想现实世界中的拼图游戏,假设我们有N块整体拼图(数据库Skin),整体拼图中对于的每一块小拼图碎片(Attachment),都能在其他N-1块整体配图(数据库Skin)找到唯一一块相同规格(相同的Slot)但是画面不同的拼图碎片(Attachment),我们需要一个动态的拼图底板(动态Skin来容纳我们的拼图碎片(Attachment),而要想拼一块完整的拼图,我们只需要从N块整体拼图(数据库Skin)中,任意取对应规格(Slot)的配图碎片(Attachment)组装到动态的拼图底板(动态Skin)中,全部Slot组装完成以后,我们就可以得到一幅完整又与其他表现不同的拼图。
- 分析得出,我们要求动画提供一套动态贴图DynamicSkin(作为新手初始套装),并且保证同一个角色的所有套装贴图卡槽保持一致,然后需要换装的贴图,做成多套完整的整套Skin贴图当作数据使用。
四、修改源代码,实现Spine局部换装目的
(1) 数据处理
1.1 全局枚举Slot数据
- 首先,我们得有套装数据,我们才可以进行数据获取,那么你可以使用数据库或者配置文件进行获取,此处暂时以临时文件替代:
全局枚举数据方便调用和传值,待会我们可以手动做数据映射:
1 public enum ESkin
2 {
3 [Description("不存在此套装,错误反馈专用")]
4 Null = 100,
5 [Description("动态组装套装基础")]
6 Dynamic = 101,
7 [Description("可选套装1")]
8 Clothing001 = 102,
9 [Description("可选套装2")]
10 Clothing002 = 103,
11 };
12
13 public enum ESlot
14 {
15 Null = 200,
16 Blet = 201, // 腰带 //
17 Weapon = 202, // 武器 //
18 BodyArmour = 203, // 盔甲 //
19 Hair = 204, // 头发 //
20 LeftHand = 205, // 左手 //
21 LeftPauldron = 206, // 左护肩 //
22 Leftleg = 207, // 左腿 //
23 LeftShoes = 208, // 左鞋 //
24 Shield = 209, // 护盾 //
25 RightHand = 210, // 右手 //
26 RightPauldron = 211, // 右护肩 //
27 Rightleg = 212, // 右腿 //
28 RightShoes = 213, // 右鞋子 //
29 }
1.2 Skin_Attactment 字典数据
然后我们针对角色有一个套装更改类 SetSkin ,如果后期拓展多个角色,你完全可以手动抽象出父类和共性的操作方法,此处以一个进行展示:
1 /// <summary>
2 /// 初始化贴图套装数据
3 /// </summary>
4 private void InitSkinData( )
5 {
6 _skinContainAttachments.Add(ESkin.Clothing001 , new List<string>()
7 {
8 "clothing001/belt001",
9 "clothing001/body001",
10 "clothing001/hair001",
11 "clothing001/L_hand001",
12 "clothing001/shield001",
13 "clothing001/L_pauldron001",
14 "clothing001/R_hand001",
15 "clothing001/weapon001",
16 "clothing001/R_pauldron001",
17 "clothing001/R_leg001",
18 "clothing001/L_leg001",
19 "clothing001/L_shoes001",
20 "clothing001/R_shoes001",
21 });
22 _skinContainAttachments.Add(ESkin.Clothing002 , new List<string>()
23 {
24 "cloting002/body002",
25 "cloting002/hair002",
26 "cloting002/L_hand002",
27 "cloting002/shield002",
28 "cloting002/L_pauldron002",
29 "cloting002/R_hand002",
30 "cloting002/weapon002",
31 "cloting002/R_pauldron002",
32 "cloting002/L_leg002",
33 "cloting002/L_shoes002",
34 "cloting002/R_leg002",
35 "cloting002/R_shoes002",
36 });
37 }
38
1.3 角色数据
也就是需要更换的部位:通常我们可能从背包中取出某部分装备,然后让玩家点击就进行更换
针对数据方便处理,我们需要给对应的某个装备进行标示,其中包括表现层中的数据:该装备属于哪套贴图Skin,属于哪个卡槽Slot;还有数据层的数据:该装备对玩家某些属性产生什么影响,此处我们只预留接口,不进行讨论;
代码如下:
1 using UnityEngine;
2 using UnityEngine.UI;
3
4 namespace Assets.Game.Animation
5 {
6 /// <summary>
7 /// 带按钮功能的皮肤部位映射
8 /// </summary>
9 public class SkinInfo : MonoBehaviour
10 {
11 [SerializeField, Header("皮肤套装")]
12 public ESkin ESkin;
13 [SerializeField, Header("插槽")]
14 public ESlot Slot;
15
16 public SetSkin SkinTarget;
17
18 #region 属性字段
19 // TODO 套装属性,进行哪些更改
20 #endregion
21 public SkinInfo( ESkin eSkin , ESlot slot )
22 {
23 ESkin = eSkin;
24 Slot = slot;
25 }
26
27 public Button SkinButton;
28
29 /// <summary>
30 /// 更新装备数据
31 /// </summary>
32 private void InvokeDataRefresh()
33 {
34 }
35
36 public void Awake( )
37 {
38 SkinTarget = GameObject.Find("LeadSpineAnim").GetComponent<SetSkin>();
39 SkinButton = GetComponent<Button>();
40
41 SkinButton.onClick.AddListener(() =>
42 {
43 var clickSuccessed = SkinTarget.ReceiveClick(SkinButton, ESkin , Slot);
44 if (clickSuccessed) //回调函数处理,进行属性数据更新
45 {
46 InvokeDataRefresh();
47 }
48 });
49 }
50
51
52 }
53 }
(2)数据映射
得到数据以后,我们需要把数据跟枚举进行相互映射,方便我们调用
2.1 插槽数据
1 /// <summary>
2 /// 根据插槽枚举映射对应插槽名称
3 /// </summary>
4 public string MappingESlot2Name( ESlot eSlot )
5 {
6 // TODO 映射,通过附着物名称找对应插槽
7 switch ( eSlot )
8 {
9 case ESlot.Blet:
10 return "blet_C";
11 case ESlot.Weapon:
12 return "weapon_C";
13 case ESlot.BodyArmour:
14 return "body_C";
15 case ESlot.Hair:
16 return "hair_C";
17 case ESlot.Shield:
18 return "shield_C";
19 case ESlot.LeftHand:
20 return "L_hand_C";
21 case ESlot.Leftleg:
22 return "L_leg_C";
23 case ESlot.LeftShoes:
24 return "L_shoes_C";
25 case ESlot.LeftPauldron:
26 return "L_pauldron_C";
27 case ESlot.RightHand:
28 return "R_hand_C";
29 case ESlot.RightPauldron:
30 return "R_pauldron_C";
31 case ESlot.Rightleg:
32 return "R_leg_C";
33 case ESlot.RightShoes:
34 return "R_shoes_C";
35 default:
36 throw new ArgumentOutOfRangeException("attachment" , eSlot , "换装目标不存在");
37 }
38 }
1 /// <summary>
2 /// 根据插槽名称映射插槽枚举,与MappingESlot2Name( ESlot eSlot )互逆
3 /// </summary>
4 /// <param name="slotName"></param>
5 /// <returns></returns>
6 public ESlot MappingName2ESlot( string slotName )
7 {
8 // TODO 映射,通过插槽找对应附着物类型
9 if ( slotName == "blet_C" ) return ESlot.Blet;
10 if ( slotName == "weapon_C" ) return ESlot.Weapon;
11 if ( slotName == "body_C" ) return ESlot.BodyArmour;
12 if ( slotName == "hair_C" ) return ESlot.Hair;
13 if ( slotName == "shield_C" ) return ESlot.Shield;
14 if ( slotName == "L_hand_C" ) return ESlot.LeftHand;
15 if ( slotName == "L_leg_C" ) return ESlot.Leftleg;
16 if ( slotName == "L_shoes_C" ) return ESlot.LeftShoes;
17 if ( slotName == "L_pauldron_C" ) return ESlot.LeftPauldron;
18 if ( slotName == "R_hand_C" ) return ESlot.RightHand;
19 if ( slotName == "R_pauldron_C" ) return ESlot.RightPauldron;
20 if ( slotName == "R_leg_C" ) return ESlot.Rightleg;
21 if ( slotName == "L_pauldron_C" ) return ESlot.LeftPauldron;
22 if ( slotName == "R_shoes_C" ) return ESlot.RightShoes;
23 return ESlot.Null;
24 }
2.2 Skin贴图数据映射
1 /// <summary>
2 /// 根据套装贴图枚举映射贴图名称
3 /// </summary>
4 private string MappingEskin2Name( ESkin eSkin )
5 {
6 switch ( eSkin )
7 {
8 case ESkin.Clothing001:
9 return "clothing001";
10 case ESkin.Clothing002:
11 return "clothing002";
12 default:
13 throw new ArgumentOutOfRangeException("eSkin" , eSkin , "The Skin Cannot Found in Character's Spine");
14 }
15 }
16
17 /// <summary>
18 /// 通过附着物名称查找对应的套装,对_skinContainAttachments进行遍历取Key
19 /// </summary>
20 /// <param name="attachmentName">插槽对应的附着物名称</param>
21 private ESkin GetSkinByAttachment( string attachmentName )
22 {
23 if ( !_skinContainAttachments.Any(skins => skins.Value.Contains(attachmentName)) ) return ESkin.Null;
24 var eSkins = _skinContainAttachments.SingleOrDefault(skin => skin.Value.Contains(attachmentName));
25 return eSkins.Key;
26 }
(3) 换装代码
3.1 换装顺序:
我们需要进行的操作是:
① 等待Spine官方源码加载套装(此套装必须设置为新手动态套装,在此基础上我们再进行动态组合)
② 读取新手套装中所有插槽数据和附着物数据,缓存到字典
③ 反序列化从数据库/系统存储/文本数据存储中得到的实际套装数据
④ 缓存数据和新手套装数据进行差异化对比,对有差别部分,以缓存数据为准,进行局部换图
⑤ 换图操作后,需要对缓存数据表进行及时更新
⑥ 切记,一切操作需要在官方Spine加载完成后,在Start函数中进行更新,否则会异常报空
1 /// <summary>
2 /// 数据层初始化
3 /// </summary>
4 public void Awake( )
5 {
6 InitSkinData();
7
8 }
9
10 /// <summary>
11 /// 行为表现层操作
12 /// </summary>
13 public void Start( )
14 {
15 _skeletonAnimation = GetComponent<SkeletonAnimation>();
16 _skeleton = _skeletonAnimation.skeleton;
17
18 //TODO 测试数据
19 //PlayerPrefsDataHlpers.SetBool(_playerSkinInitFlag , false); // 注释部分用于清理套装缓存数据
20
21 //_isPlayerSkinInit = PlayerPrefsDataHlpers.GetBool(_playerSkinInitFlag);
22 //if ( !_isPlayerSkinInit )
23 InitSkinDataAtStart();
24 //PlayerPrefsDataHlpers.SetBool(_playerSkinInitFlag , true);
25 ReloadSkinByDataAtGameStart();
26
27 }
3.2 数据定义和数据表缓存校验
1 #region Spine Animation Script
2 private SkeletonAnimation _skeletonAnimation;
3
4 private Skeleton _skeleton;
5 #endregion
6
7 #region User Data
8 private readonly Dictionary<ESkin , List<string>> _skinContainAttachments = new Dictionary<ESkin , List<string>>(); // 通过附着物映射skin
9
10 private readonly Dictionary<ESlot , string> _dynamicSlotToAttachments = new Dictionary<ESlot , string>(); // 实际使用中的数据
11
12 private readonly string _dynamicSkinName = "clothing000";
13 #endregion
14
15
16 #region 数据表缓存校验
17
18 private void SetSlotToAttachment( ESlot eAttach , string attchmentName,bool saveDatabase )
19 {
20 bool isExitKey = _dynamicSlotToAttachments.ContainsKey(eAttach);
21 if ( !isExitKey )
22 {
23 _dynamicSlotToAttachments.Add(eAttach , attchmentName);
24 }
25 else
26 {
27 _dynamicSlotToAttachments[eAttach] = attchmentName; // Reference Type Don't need to Reassignment Value
28 }
29
30 // 是否写入数据表 //
31 if (saveDatabase)
32 {
33 EncodingAttachment(eAttach , attchmentName);
34 }
35 }
36
37 /// <summary>
38 /// 编码写入缓存(也可另改写为服务器存储)
39 /// </summary>
40 /// <param name="eAttach">对应插槽</param>
41 /// <param name="attchmentName">对于贴图名称</param>
42 private void EncodingAttachment( ESlot eAttach , string attchmentName )
43 {
44 int id = (int) eAttach;
45 string flag = string.Concat("slot" , id.ToString());
46 PlayerPrefsDataHlpers.SetString(flag , attchmentName);
47 }
48
49 /// <summary>
50 /// 解码取出缓存套装数据
51 /// </summary>
52 /// <returns></returns>
53 private Dictionary<ESlot , string> DecodingAttachment( )
54 {
55 var slotToAttachments = new Dictionary<ESlot , string>();
56
57 var fristSlot = (ESlot) Enum.Parse(typeof(ESlot) , "Null" , true);
58 int start = (int) fristSlot;
59 int length = Enum.GetNames(typeof(ESlot)).Length;
60 int end = start + length;
61 for ( int i = start ; i < end ; i++ )
62 {
63 string flag = string.Concat("slot" , i.ToString());
64 string attchmentName = PlayerPrefsDataHlpers.GetString(flag);
65 if ( attchmentName != string.Empty )
66 {
67 ESlot eSlot = (ESlot) i;
68 slotToAttachments.Add(eSlot , attchmentName);
69 }
70 }
71
72 return slotToAttachments;
73 }
74
75 #endregion
3.3 读取默认套装数据,作为动态套装的基础对比数据表
1 private bool _isPlayerSkinInit; // 开关: 用于测试数据缓存 //
2
3 /// <summary>
4 /// TODO : 完善局部换装逻辑
5 /// 1、初次游戏,记录基准 Slot -> Attachment Table
6 /// 2、任何时刻实时更改套装任何部分,立刻更新映射表数据层
7 /// 3、再次游戏,基于基准装,重新根据数据表缓存数据映射表现层
8 /// 4、双重数据表校验,只要基准表和实际表任何部分不一致,认定装备需要Reloading
9 /// </summary>
10 public void InitSkinDataAtStart( )
11 {
12 // 默认设置必须为基准装 Clothing000 //
13 _skeletonAnimation.initialSkinName = _dynamicSkinName;
14
15 //var curSkin = _skeleton.Skin;
16
17 ExposedList<Slot> slots = _skeleton.slots;
18 for ( int i = 0, n = slots.Count ; i < n ; i++ )
19 {
20 Slot slot = slots.Items[i];
21 String slotName = slot.data.attachmentName;
22 if ( slotName != null )
23 {
24 ESlot eSlot = MappingName2ESlot(slotName);
25 Attachment attachment = LGetAttachment(i , slotName , _dynamicSkinName); // Find Attachment By Slot With Base Skin
26 if ( attachment == null ) continue;
27
28 string attahName = attachment.Name;
29
30 // 是否写入数据表
31 SetSlotToAttachment(eSlot , attahName,false);
32
33 }
34 }
35 }
3.4 读取基础数据表以后,读取缓存数据,对比数据进行局部换装
1
2 /// <summary>
3 /// 在基础套装自动加载完成以后,手动调用此函数
4 /// 为了局部换装数据不错乱,哪怕整套Skin换都需要更新数据表中的数据
5 /// </summary>
6 public void ReloadSkinByDataAtGameStart( )
7 {
8 var slotToAttachments = DecodingAttachment();
9 CompareAndSetAttachments(slotToAttachments);
10 }
数据对比函数:
1 /// <summary>
2 /// 对比数据表跟目前数据表(游戏初始加载后的Spine内置套装数据)差异,并更新数据和表现
3 /// </summary>
4 /// <param name="targetAttchments">缓存数据表(目标数据)</param>
5 private void CompareAndSetAttachments(Dictionary<ESlot, string> targetAttchments)
6 {
7 var curAttachments = _dynamicSlotToAttachments;
8
9 var fristSlot = (ESlot) Enum.Parse(typeof(ESlot) , "Null" , true);
10 int start = (int) fristSlot;
11
12 foreach (var eSlotKey in targetAttchments )
13 {
14 ESlot slotKey = eSlotKey.Key;
15 var curAttachment = curAttachments[slotKey];
16 var targetAttachment = targetAttchments[slotKey];
17
18 if ( curAttachment == null || curAttachment != targetAttachment )
19 {
20 ESkin eSkins = GetSkinByAttachment(targetAttachment);
21 if ( eSkins == ESkin.Null )
22 {
23 throw new Exception("Eskin 不存在与=数据表_skinContainAttachments中");
24 }
25 LChangeSkinBaseOnDynamicSkin(eSkins , slotKey);
26 }
27 }
28 }
29
3.4 核心换装代码
换装函数对外入口点:
1 /// <summary>
2 /// 基于动态套装,改变局部并重新组合动态套装
3 /// </summary>
4 /// <param name="eTargetSkin">取值套装</param>
5 /// <param name="eSlot">目标插槽</param>
6 public bool LChangeSkinBaseOnDynamicSkin( ESkin eTargetSkin , ESlot eSlot )
7 {
8 Skin dynamicSkin = _skeleton.data.FindSkin(_dynamicSkinName);
9
10 var success = LSetSkin(dynamicSkin , eTargetSkin , eSlot);
11 return success;
12 }
批量换装操作:
1 /// <summary>
2 /// 批量换装,必须保证传入的数组一一对应
3 /// </summary>
4 /// <returns>批量换装只要有其中一处换不成功,整体算作失败,需要手动进行数据回滚</returns>
5 public bool LChangeBitchSkinBaseOnDynamicSkin(ESkin[] eTargetSkins, ESlot[] eSlots)
6 {
7 if (eTargetSkins.Length != eSlots.Length) return false;
8 for (int i = 0; i < eSlots.Length; i++)
9 {
10 var success = LChangeSkinBaseOnDynamicSkin(eTargetSkins[i],eSlots[i]);
11 if (!success)
12 {
13 return false; // 任意一件换不成功,整体换装失败
14 }
15 }
16 return true;
17 }
内部换装处理函数:
1 /// <summary>
2 /// 内部处理:针对传入的需要更改的套装(实时套装),从目标皮肤中根据目标卡槽取出皮肤数据进行替换赋值操作,
3 /// 数据层和表现层同时处理变化
4 /// </summary>
5 /// <param name="dynamicSkin">赋值套装</param>
6 /// <param name="eSkin">取值套装枚举</param>
7 /// <param name="eSlot">目标插槽枚举</param>
8 private bool LSetSkin( Skin dynamicSkin , ESkin eSkin , ESlot eSlot )
9 {
10
11 if ( dynamicSkin != null )
12 {
13 ExposedList<Slot> slots = _skeleton.slots;
14 for ( int i = 0, n = slots.Count ; i < n ; i++ )
15 {
16 // Get //
17 Slot slot = slots.Items[i];
18 var targetSlotName = MappingESlot2Name(eSlot);
19 if ( slot.data.name != targetSlotName ) continue;
20
21 string attachName = slot.data.attachmentName;
22 if ( attachName != null )
23 {
24 string targetSkinName = MappingEskin2Name(eSkin);
25 Attachment attachment;
26 if ( attachName == targetSlotName )
27 {
28 attachment = LGetAttachment(i , targetSlotName , targetSkinName); // 重写L Get
29 dynamicSkin.Attachments.Remove(new Skin.AttachmentKeyTuple(i , targetSlotName));
30 dynamicSkin.Attachments.Add(new Skin.AttachmentKeyTuple(i , targetSlotName) , attachment);
31
32 }
33 else
34 {
35 attachment = dynamicSkin.GetAttachment(i , attachName); // 默认Skeleton Get
36 }
37
38 // Set //
39 if ( attachment != null )
40 {
41 slot.Attachment = attachment;
42 var attahName = attachment.Name;
43 SetSlotToAttachment(eSlot , attahName,true);
44 break;
45 }
46 }
47 }
48 _skeleton.slots = slots;
49 }
50 _skeleton.skin = dynamicSkin;
51 return true;
52 }
针对性查找真实贴图中的附着点数据(是数据不是String哦,有效的完整的Attachment数据)
1 /// <summary>
2 /// 通过指定的Skin找到对应附着点的附着物Attachment
3 /// </summary>
4 public Attachment LGetAttachment( int slotIndex , string slotName , string skinName )
5 {
6 var targetSkin = _skeleton.data.FindSkin(skinName);
7 var attachments = targetSkin.Attachments;
8
9 Attachment attachment;
10 attachments.TryGetValue(new Skin.AttachmentKeyTuple(slotIndex , slotName) , out attachment);
11 return attachment;
12 }
(4) 用户交互部分:
1 #region 接受用户点击处理
2 /// <summary>
3 /// 可交互对象内部绑定了对应的SkinInfo,根据SkinInfo赋值字段进行查找
4 /// </summary>
5 /// <param name="skinButton">换装按键</param>
6 /// <param name="eSkin">对应皮肤</param>
7 /// <param name="slot">对应插槽</param>
8 public bool ReceiveClick( Button skinButton , ESkin eSkin , ESlot slot )
9 {
10 return ResetActulSlotToAttachment(skinButton , eSkin , slot);
11 }
四、结果展示
最终测试结果,实现了动态换装,批量换装,局部换装,整体换装,动画运行时换装,换装数据缓存等,如图所示:
五、结束感言
如果你喜欢本篇博课,或者想和我探讨,请关注我的博客园(燕双飞情侣),感谢您的观看。