Unity動態換裝之Spine換裝
阿新 • • 發佈:2019-01-22
一、動態換裝原理
- 換裝,無非就是對模型的網格,或者貼圖進行鍼對性置換;
- 對於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, 8710 orig:
並沒有多大作用… 那麼我們在看看另一個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
套裝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 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套裝資料,遍歷所有對應的骨骼,更新骨骼上對應的貼圖資料。
- 那麼,思路就來了,我們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 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 }
(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) 使用者互動部分:
<