注:轉載請注明轉載,並附原鏈接 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
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
}
四、結果展示
最終測試結果,實現了動態換裝,批量換裝,局部換裝,整體換裝,動畫運行時換裝,換裝數據緩存等,如圖所示:
五、結束感言
如果你喜歡本篇博課,或者想和我探討,請關注我的博客園(燕雙飛情侶),感謝您的觀看。

