Unity動態換裝之Spine換裝


注:轉載請注明轉載,並附原鏈接 http://www.cnblogs.com/liaoguipeng/p/5867510.html 燕雙飛情侶

一、動態換裝原理

  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 , 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會發生什么呢?

    對調前: 
                 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 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套裝數據,遍歷所有對應的骨骼,更新骨骼上對應的貼圖數據。
  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 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 }

image

(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 }



四、結果展示

最終測試結果,實現了動態換裝,批量換裝,局部換裝,整體換裝,動畫運行時換裝,換裝數據緩存等,如圖所示:

image

 

 

五、結束感言

如果你喜歡本篇博課,或者想和我探討,請關注我的博客園(燕雙飛情侶),感謝您的觀看。

注:轉載請注明轉載,並附原鏈接 http://www.cnblogs.com/liaoguipeng/p/5867510.html 燕雙飛情侶


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM