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); 4
1 /// <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.2 Skin_Attactment 字典数据
然后我们针对角色有一个套装更改类 SetSkin ,如果后期拓展多个角色,你完全可以手动抽象出父类和共性的操作方法,此处以一个进行展示:
1.3 角色数据
也就是需要更换的部位:通常我们可能从背包中取出某部分装备,然后让玩家点击就进行更换
针对数据方便处理,我们需要给对应的某个装备进行标示,其中包括表现层中的数据:该装备属于哪套贴图Skin,属于哪个卡槽Slot;还有数据层的数据:该装备对玩家某些属性产生什么影响,此处我们只预留接口,不进行讨论;
代码如下:
(2)数据映射
得到数据以后,我们需要把数据跟枚举进行相互映射,方便我们调用
2.1 插槽数据
2.2 Skin贴图数据映射
(3) 换装代码
3.1 换装顺序:
我们需要进行的操作是:
① 等待Spine官方源码加载套装(此套装必须设置为新手动态套装,在此基础上我们再进行动态组合)
② 读取新手套装中所有插槽数据和附着物数据,缓存到字典
③ 反序列化从数据库/系统存储/文本数据存储中得到的实际套装数据
④ 缓存数据和新手套装数据进行差异化对比,对有差别部分,以缓存数据为准,进行局部换图
⑤ 换图操作后,需要对缓存数据表进行及时更新
⑥ 切记,一切操作需要在官方Spine加载完成后,在Start函数中进行更新,否则会异常报空
3.2 数据定义和数据表缓存校验
3.3 读取默认套装数据,作为动态套装的基础对比数据表
3.4 读取基础数据表以后,读取缓存数据,对比数据进行局部换装
数据对比函数:
3.4 核心换装代码
换装函数对外入口点:
批量换装操作:
内部换装处理函数:
针对性查找真实贴图中的附着点数据(是数据不是String哦,有效的完整的Attachment数据)
(4) 用户交互部分:
四、结果展示
最终测试结果,实现了动态换装,批量换装,局部换装,整体换装,动画运行时换装,换装数据缓存等,如图所示:
五、结束感言
如果你喜欢本篇博课,或者想和我探讨,请关注我的博客园(燕双飞情侣),感谢您的观看。