Unity肉鸽卡牌开发记录04-卡牌展示

2023-11-5:此功能已经重构,以解决弃牌时的Bug,详情请见Unity肉鸽卡牌开发记录05


CardRevealTool类负责对局中卡牌的展示

  • 鼠标悬停在手牌时,显示卡牌详情
  • 在执行检索操作时,调用CardAreaManager的相关检索方法展示检索到的牌展示给玩家选择
  • 排列手牌操作
  • 从手牌区移除卡牌操作(用于玩家弃牌后更新手牌区)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//TODO: showTypeCardInDeck改用枚举传卡牌类型并添加检索张数
public class CardRevealTool : MonoBehaviour
{
public static CardRevealTool instance;

[SerializeField]
[Header("鼠标悬停卡牌详情区域")]
private GameObject showCardArea;
[SerializeField]
private GameObject cardDetail;

[SerializeField]
[Header("卡牌展示列表区域")]
private GameObject listCardArea;
[SerializeField]
private GameObject content;
[SerializeField]
private GameObject cardOnShowPrefab;
[SerializeField]
private GameObject listComponent;

[SerializeField]
[Header("手牌区域")]
private GameObject handZone;

[SerializeField]
[Header("卡牌预制体")]
private GameObject cardInHandObj;

private void Awake()
{
if(instance == null)
{
instance = this;
}
else
{
Destroy(this);
}
showCardArea.SetActive(false);
listComponent.SetActive(false);
}

/// <summary>
/// 排列手牌
/// </summary>
public void ListHand()
{
Transform handZoneTransform = handZone.transform;
int childCount = handZoneTransform.childCount;
float space = 1f / childCount;
for (int i = 0; i < childCount; i++)
{
handZoneTransform.GetChild(i).localPosition = new Vector3((-0.5f + space / 2) + i * space,
-0.2f,
handZoneTransform.GetChild(i).transform.localPosition.z);
}
}

/// <summary>
/// 从手牌区移除一张牌
/// </summary>
public void RemoveCardFromHandZone(Card card)
{
//将手牌移除出手牌区
int handNum = handZone.transform.childCount;
for (int i = 0; i < handNum; i++)
{
GameObject child = handZone.transform.GetChild(i).gameObject;
if (child.GetComponent<CardInHand>().card == card)
{
DestroyImmediate(child);
break;
}
}
}

/// <summary>
/// 显示卡牌详情,isShow表示是否展示
/// </summary>
/// <param name="showCard"></param>
/// <param name="isShow"></param>
public void showCardInfo(Card showCard,bool isShow=true)
{
showCardArea.SetActive(isShow);
cardDetail.transform.Find("Cost").GetComponent<TMPro.TMP_Text>().text = showCard.pointCost.ToString();
cardDetail.transform.Find("Name").GetComponent<TMPro.TMP_Text>().text = showCard.cardName;
cardDetail.transform.Find("Description").GetComponent<TMPro.TMP_Text>().text = showCard.ability;
if(showCard is Monster)
{
Monster monster = (Monster)showCard;
cardDetail.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "怪兽~"
+ CardEnumConvertor.GetInstance().MonsterRaceEnumToString(monster.race)
+ CardEnumConvertor.GetInstance().MonsterDutyEnumToString(monster.duty);
cardDetail.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = monster.atk.ToString();
cardDetail.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = monster.hp.ToString();
}
if (showCard is Magic)
{
Magic magic = (Magic)showCard;
cardDetail.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "魔法";
}
if (showCard is Equipment)
{
Equipment artifact = (Equipment)showCard;
cardDetail.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = artifact.hp.ToString();
cardDetail.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "装备";
}
if (showCard is Weapon)
{
Weapon weapon = (Weapon)showCard;
cardDetail.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = weapon.atk.ToString();
cardDetail.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = weapon.hp.ToString();
cardDetail.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "武具";
}
if (showCard is Trap)
{
Trap trap = (Trap)showCard;
cardDetail.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = "0";
cardDetail.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = trap.hp.ToString();
cardDetail.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "陷阱";
}
if(showCard is Arena)
{
Arena arena = (Arena)showCard;
cardDetail.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = "";
cardDetail.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = "";
cardDetail.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "场地";
}
}

#region 检索牌库展示并执行操作
/// <summary>
/// 检视牌库中所有牌并加入手牌
/// </summary>
public void showDeckAndPutToHand(int putToHandNum)
{
putToHandNum = Mathf.Min(CardAreaManager.GetInstance().GetDeckNum(), putToHandNum);
SelectCardArea selectCardArea = listComponent.GetComponent<SelectCardArea>();
selectCardArea.SelectNum = putToHandNum;
selectCardArea.selectedAction = CardAreaManager.GetInstance().PutCardFromDeckToHand;
//清空另一个action
selectCardArea.putBackToDeckAction = null;

//先清除content子物体
int childCount = content.transform.childCount;
for (int i = 0; i < childCount; i++)
{
Debug.Log(content.transform.GetChild(0));
DestroyImmediate(content.transform.GetChild(0).gameObject);
}

List<Card> cards = CardAreaManager.GetInstance().GetDeckList();
if (cards.Count <= 0)
{
Debug.Log("牌库中没有此类型的牌!");
}
else
{
foreach (Card item in cards)
{
GameObject temp = Instantiate(GenerateShowCard(item), Vector3.one, Quaternion.identity, content.transform);
temp.GetComponent<SelectabelCard>().card = item;
temp.SetActive(true);
}
listComponent.SetActive(true);
}
}

/// <summary>
/// 检视牌库中所有特定种类的牌并加入手牌
/// </summary>
/// <param name="type"></param>
public void showTypeDeckAndPutToHand(int type)
{
SelectCardArea selectCardArea = listComponent.GetComponent<SelectCardArea>();
selectCardArea.SelectNum = 1;
selectCardArea.selectedAction = CardAreaManager.GetInstance().PutCardFromDeckToHand;
//清空另一个action
selectCardArea.putBackToDeckAction = null;

//先清除content子物体
int childCount = content.transform.childCount;
for(int i=0;i<childCount;i++)
{
Debug.Log(content.transform.GetChild(0));
DestroyImmediate(content.transform.GetChild(0).gameObject);
}

CardType cardType = (CardType)type;
List<Card> cards = CardAreaManager.GetInstance().GetAllDeckWithType(cardType);
if(cards.Count <= 0)
{
Debug.Log("牌库中没有此类型的牌!");
}
else
{
foreach (Card item in cards)
{
GameObject temp = Instantiate(GenerateShowCard(item), Vector3.one, Quaternion.identity, content.transform);
temp.GetComponent<SelectabelCard>().card = item;
temp.SetActive(true);
}
listComponent.SetActive(true);
}
}
#endregion

#region 检索手牌展示并执行操作
#endregion

#region 检索坟墓场展示并执行操作
#endregion

#region 生成卡牌GameObject
/// <summary>
/// 生成展示的卡牌
/// </summary>
/// <param name="card"></param>
/// <returns></returns>
public GameObject GenerateShowCard(Card card)
{
cardOnShowPrefab.transform.Find("Cost").GetComponent<TMPro.TMP_Text>().text = card.pointCost.ToString();
cardOnShowPrefab.transform.Find("Name").GetComponent<TMPro.TMP_Text>().text = card.cardName;
cardOnShowPrefab.transform.Find("Description").GetComponent<TMPro.TMP_Text>().text = card.ability;
if (card is Monster)
{
Monster monster = (Monster)card;
cardOnShowPrefab.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "怪兽~"
+ CardEnumConvertor.GetInstance().MonsterRaceEnumToString(monster.race)
+ CardEnumConvertor.GetInstance().MonsterDutyEnumToString(monster.duty);
cardOnShowPrefab.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = monster.atk.ToString();
cardOnShowPrefab.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = monster.hp.ToString();
}
if (card is Magic)
{
Magic magic = (Magic)card;
cardOnShowPrefab.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "魔法";
cardOnShowPrefab.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = "";
cardOnShowPrefab.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = "";
}
if (card is Equipment)
{
Equipment equipment = (Equipment)card;
cardOnShowPrefab.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = "";
cardOnShowPrefab.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = equipment.hp.ToString();
cardOnShowPrefab.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "装备";
}
if (card is Weapon)
{
Weapon weapon = (Weapon)card;
cardOnShowPrefab.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = weapon.atk.ToString();
cardOnShowPrefab.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = weapon.hp.ToString();
cardOnShowPrefab.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "武器";
}
if (card is Trap)
{
Trap trap = (Trap)card;
cardOnShowPrefab.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = "0";
cardOnShowPrefab.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = trap.hp.ToString();
cardOnShowPrefab.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "陷阱";
}
if(card is Arena)
{
Arena arena = (Arena)card;
cardOnShowPrefab.transform.Find("Atk").GetComponent<TMPro.TMP_Text>().text = "";
cardOnShowPrefab.transform.Find("Hp").GetComponent<TMPro.TMP_Text>().text = "";
cardOnShowPrefab.transform.Find("Type").GetComponent<TMPro.TMP_Text>().text = "场地";
}
return cardOnShowPrefab;
}

/// <summary>
/// 生成手牌
/// </summary>
/// <param name="card"></param>
public GameObject GenerateHandCard(Card card)
{
//创建卡牌GameObject
GameObject cardObjTemp = Instantiate(cardInHandObj, Vector3.zero, Quaternion.identity);
//将对应的CardInHand类并附加给卡牌
cardObjTemp.AddComponent(System.Type.GetType(card.name + "InHand"));
cardObjTemp.transform.SetParent(handZone.transform); //为卡牌设置父物体
return cardObjTemp;
}
#endregion

public static CardRevealTool GetInstance()
{
return instance;
}
}

其他检索-展示逻辑与上方检索牌库的代码逻辑类似

生成展示的卡牌方法GenerateShowCard用于展示检索结果

生成手牌方法GenerateHandCard用于抓牌后将手牌Prefab实例化至手牌区域

检索——展示逻辑详解

卡牌展示列表区域

该区域为一系列UI组件:

  • ListCardComponent:空物体,用于挂载子物体组件并挂载SelectCardArea脚本
  • 两个Button,Hide和Confirm:Hide用于显示|隐藏展示列表区域便于玩家观察战场,Confirm用于玩家确认选择。
  • ListCard区域:Scroll Rect,用于展示检索到的卡牌

SelectCardArea类

  • selectNum,int类型,表示需要选中的卡牌数量
  • selectedCards,List<Card>,储存选中的卡牌
  • selectedAction,Action<Card>,在确定选择后触发Action调用CardAreaManager中的相关方法
  • 提供放回牌库特定区域(从手牌放回牌库)的专用Action和DeckPosition
  • OnCardHasSelected和OnCardCancelSelected方法:SelectableCard类在选中、取消选中时调用,用于增删selectCards中的卡牌
  • OnConformedSelected方法:确认按钮按下时调用,对于每张在selectedCards中的Card,触发Action
  • ChangeListAreaVisible方法:改变列表的显示|隐藏状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class SelectCardArea : MonoBehaviour
{
private int selectNum = 1;
public int SelectNum { get => selectNum; set { } }

private List<Card> selectedCards = new List<Card>();

public Action<Card> selectedAction;

//放回牌库特定位置专用
public Action<Card,DeckPosition> putBackToDeckAction;
public DeckPosition deckPosition = DeckPosition.Top;

//卡牌展示列表显示|隐藏
private bool isShow = true;

private void OnEnable()
{
selectedCards.Clear();
}

/// <summary>
/// 当卡牌被选中时
/// </summary>
/// <param name="selectedCard"></param>
/// <returns></returns>
public bool OnCardHasSelected(Card selectedCard)
{
//如果已经选满了,则返回false
if (selectedCards.Count >= selectNum) return false;
else
{
//将卡牌添加进List,返回true
selectedCards.Add(selectedCard);
return true;
}
}

/// <summary>
/// 当卡牌取消选中时
/// </summary>
/// <param name="cancelCard"></param>
public void OnCardCancelSelected(Card cancelCard)
{
//从List移除
selectedCards.Remove(cancelCard);
}

/// <summary>
/// 确认选择按钮按下时调用该方法
/// </summary>
public void OnConformedSelected()
{
//如果还没选满,不管它,直接返回
if (selectedCards.Count != selectNum) return;
else
{
//否则对于List中的每一张卡牌,触发Action
foreach (Card item in selectedCards)
{
selectedAction?.Invoke(item);
putBackToDeckAction?.Invoke(item, deckPosition);
}
//触发完成之后将展示列表失活
gameObject.SetActive(false);
}
}

/// <summary>
/// 显示|隐藏卡牌列表
/// </summary>
public void ChangeListAreaVisible()
{
//根据isShow改变显示|隐藏状态
if(isShow)
{
transform.Find("ListCard").gameObject.SetActive(false);
isShow = false;
}
else
{
transform.Find("ListCard").gameObject.SetActive(true);
isShow = true;
}
}
}

SelectableCard类

  • 鼠标点击选中时,通知SelectCardArea并把对应的Card传递过去,同时改变视觉效果为选中
  • 选中状态下再次点击,即取消选中,通知SelectCardArea,并改变视觉效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class SelectabelCard : MonoBehaviour,IPointerClickHandler
{
public Card card;

private bool isSelected = false;

public void OnPointerClick(PointerEventData eventData)
{
if(!isSelected)
{
//如果成功选中
if(GameObject.Find("ListCardComponent").GetComponent<SelectCardArea>().OnCardHasSelected(card))
{
isSelected = true;
//改变卡牌选中的效果
GetComponent<Image>().color = Color.yellow;
}
}
else
{
isSelected = false;
//改变卡牌选中的效果
GetComponent<Image>().color = new Color32(49, 207, 243,255);
GameObject.Find("ListCardComponent").GetComponent<SelectCardArea>().OnCardCancelSelected(card);
}
}
}

效果