1. 程式人生 > >Unity動態換裝之Spine換裝

Unity動態換裝之Spine換裝

一、動態換裝原理

  1. 換裝,無非就是對模型的網格,或者貼圖進行鍼對性置換;
  2. 對於3D區域性換裝,我們可能需要單獨換模型和貼圖,也可能只需要單獨置換貼圖即可
  3. 對與Spine2D角色換裝,我們基本上只需要針對性置換貼圖,也就是Slot插槽上對應的附著物Attachment即可
  4. QQ圖片20160913113901

二、換裝理論分析

  1. Spine目前提供的換裝是整體換裝,也就是動畫那邊做好幾套Skin,需要哪套直接呼叫SKeletonAnimation中的InitialSkin進行置換就行了,這個看起來很簡單嘛。
  2. 但是,如果我們需要區域性換裝,難道讓動畫把每個區域性都單獨列出來,比如我們一個角色10套
  3. 面板,每套面板有對於10個位置可以進行任意更換,那麼動畫豈不是要做10! = 3 628 800 套面板?計算機裝得下?程式呼叫邏輯不會出錯?這麼看來,這個方案不科學。
  4. 那麼,我們有沒有一種方法,可以實現到區域性換裝?於是,開始針對Spine匯出檔案進行分析;Spine可到處二進位制和Json資料檔案,為了方便分析,我們這次使用Json檔案進行分析:

    image
    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, 8710 orig:
    57, 8711 offset:0, 012 index: -113 L_leg000 14 rotate: true 15 xy:1354, 9316 size:33, 9117 orig:33, 9118 offset:0, 019 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會發生什麼呢?

    對調前: 
                 image                image
                              “cloth001”                                              “cloth002”

    對調後:
                image                  image
                              “cloth001”                                              “cloth002”

    預料之中,那麼區域性換裝的實現,就應該不成問題了,那麼我們回到程式碼層開始分析.

三、Spine底層庫原始碼分析

  1. 我們先觀察Spine提供的整套換裝原始碼:

    image
     1 namespace Spine.Unity {
     2     [ExecuteInEditMode, RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent]
     3     [AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")]
     4 publicclass 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 publicstring initialSkinName ="default";
    12 13         [SpineAnimation(dataField:"skeletonDataAsset")]
    14 publicstring startingAnimation;
    15 publicbool startingLoop;
    16 publicfloat timeScale = 1f;
    17 publicbool freeze;


    根據initialSkinName我們繼續往下查詢
    1 // Set the initial Skin and Animation2 if (!string.IsNullOrEmpty(initialSkinName))
    3                 skeleton.SetSkin(initialSkinName);
    4 


    1 ///<summary>Sets a skin by name (see SetSkin).</summary>2 publicvoid SetSkin (String skinName) {
    3             Skin skin = data.FindSkin(skinName);
    4 if (skin ==null) thrownew 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 publicvoid  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套裝資料,遍歷所有對應的骨骼,更新骨骼上對應的貼圖資料。
  2. 那麼,思路就來了,我們Spine進行區域性換裝,不也就是更新某個SKin中某個Slot上對應的Attachment資料麼?
    因此,想想現實世界中的拼圖遊戲,假設我們有N塊整體拼圖(資料庫Skin),整體拼圖中對於的每一塊小拼圖碎片(Attachment),都能在其他N-1塊整體配圖(資料庫Skin)找到唯一一塊相同規格(相同的Slot)但是畫面不同的拼圖碎片(Attachment),我們需要一個動態的拼圖底板(動態Skin來容納我們的拼圖碎片(Attachment),而要想拼一塊完整的拼圖,我們只需要從N塊整體拼圖(資料庫Skin)中,任意取對應規格(Slot)的配圖碎片(Attachment)組裝到動態的拼圖底板(動態Skin)中,全部Slot組裝完成以後,我們就可以得到一幅完整又與其他表現不同的拼圖。

         )L7C@5592F4D@TNVRP(`O3B
  3. 分析得出,我們要求動畫提供一套動態貼圖DynamicSkin(作為新手初始套裝),並且保證同一個角色的所有套裝貼圖卡槽保持一致,然後需要換裝的貼圖,做成多套完整的整套Skin貼圖當作資料使用。

四、修改原始碼,實現Spine區域性換裝目的

(1) 資料處理

1.1 全域性列舉Slot資料

    首先,我們得有套裝資料,我們才可以進行資料獲取,那麼你可以使用資料庫或者配置檔案進行獲取,此處暫時以臨時檔案替代:
    全域性列舉資料方便呼叫和傳值,待會我們可以手動做資料對映:

 1 publicenum 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 publicenum 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 privatevoid 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 publicclass 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 #endregion21 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 privatevoid InvokeDataRefresh()
33         {
34         }
35 36 publicvoid 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 }

image

(2)資料對映

得到資料以後,我們需要把資料跟列舉進行相互對映,方便我們呼叫

2.1 插槽資料

 1 ///<summary> 2 /// 根據插槽列舉對映對應插槽名稱
 3 ///</summary> 4 publicstring 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 thrownew 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 privatestring 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 thrownew 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 publicvoid Awake( )
 5     {
 6         InitSkinData();
 7  8     }
 9 10 ///<summary>11 /// 行為表現層操作
12 ///</summary>13 publicvoid 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 privatereadonly Dictionary<ESkin , List<string>> _skinContainAttachments =new Dictionary<ESkin , List<string>>(); // 通過附著物對映skin 9 10 privatereadonly Dictionary<ESlot , string> _dynamicSlotToAttachments =new Dictionary<ESlot , string>(); // 實際使用中的資料11 12 privatereadonlystring _dynamicSkinName ="clothing000";
13 #endregion14 15 16 #region 資料表快取校驗17 18 privatevoid 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 else26         {
27             _dynamicSlotToAttachments[eAttach] = attchmentName;   // Reference Type Don’t need to Reassignment Value28         }
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 privatevoid 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 privatebool _isPlayerSkinInit;  // 開關: 用於測試資料快取 //
 2  3 ///<summary> 4 /// TODO : 完善區域性換裝邏輯
 5 /// 1、初次遊戲,記錄基準 Slot -> Attachment Table
 6 /// 2、任何時刻實時更改套裝任何部分,立刻更新對映表資料層
 7 /// 3、再次遊戲,基於基準裝,重新根據資料表快取資料對映表現層
 8 /// 4、雙重資料表校驗,只要基準表和實際表任何部分不一致,認定裝備需要Reloading
 9 ///</summary>10 publicvoid 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 Skin26 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 publicvoid ReloadSkinByDataAtGameStart( )
 7     {
 8         var slotToAttachments = DecodingAttachment();
 9         CompareAndSetAttachments(slotToAttachments);
10     }


資料對比函式:

 1 ///<summary> 2 /// 對比資料表跟目前資料表(遊戲初始載入後的Spine內建套裝資料)差異,並更新資料和表現
 3 ///</summary> 4 ///<param name="targetAttchments">快取資料表(目標資料)</param> 5 privatevoid 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 thrownew 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 publicbool 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 publicbool LChangeBitchSkinBaseOnDynamicSkin(ESkin[] eTargetSkins, ESlot[] eSlots)
 6     {
 7 if (eTargetSkins.Length != eSlots.Length) returnfalse;
 8 for (int i =0; i < eSlots.Length; i++)
 9         {
10             var success =  LChangeSkinBaseOnDynamicSkin(eTargetSkins[i],eSlots[i]);
11 if (!success)
12             {
13 returnfalse;  // 任意一件換不成功,整體換裝失敗 14             }
15         }
16 returntrue;
17     }



內部換裝處理函式:

 1 ///<summary> 2 /// 內部處理:針對傳入的需要更改的套裝(實時套裝),從目標面板中根據目標卡槽取出面板資料進行替換賦值操作,
 3 /// 資料層和表現層同時處理變化
 4 ///</summary> 5 ///<param name="dynamicSkin">賦值套裝</param> 6 ///<param name="eSkin">取值套裝列舉</param> 7 ///<param name="eSlot">目標插槽列舉</param> 8 privatebool 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 Get29                         dynamicSkin.Attachments.Remove(new Skin.AttachmentKeyTuple(i , targetSlotName));
30                         dynamicSkin.Attachments.Add(new Skin.AttachmentKeyTuple(i , targetSlotName) , attachment);
31 32                     }
33 else34                     {
35                         attachment = dynamicSkin.GetAttachment(i , attachName);   // 預設Skeleton Get36                     }
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 returntrue;
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)  使用者互動部分:

<