Unity肉鸽卡牌开发记录05-策略模式重构检索功能与修复弃牌Bug

弃牌Bug

  • Bug描述

    当玩家弃牌时,若玩家手上有多张同名牌且玩家选择丢弃靠右的同名牌时,但系统会丢弃从左往右的第一张同名牌

  • Bug出现频率

    每次都出现

  • 导致Bug的原因

    程序中使用List<Card>存储玩家手牌,虽然能够管理玩家手牌的类型,但无法管理顺序。当玩家弃牌时,系统会遍历HandZone下的所有手牌子物体,此时会先找到最左边的同名牌(Card类相同)并丢弃

  • Bug修复

    见下方

重构手牌列表handList

  • HandList改为存储手牌GameObject,在之后的玩家弃牌中遍历handList,找到与玩家所选手牌GameObject相同的一项,执行丢弃

    1
    private List<GameObject> handList = new List<GameObject>();
  • CardInHand类加入handIndex字段,存储手牌在手中的位置,这与之后修复手牌展示Bug有关(肉鸽卡牌开发记录06)

    1
    public int handIndex = 0; //在手牌的位置
  • 加入手牌相关函数重构(以抓牌为例)

    在抓取一张牌后,设置其在手牌中的顺序,然后加入handList

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public void DrawACard()
    {
    //如果牌库还有牌
    if(deckList.Count>0)
    {
    //取出牌库顶的牌并加入手牌列表
    Card card = deckList[0];
    deckList.RemoveAt(0);
    GameObject cardObjTemp = CardRevealTool.GetInstance().GenerateHandCard(card);
    cardObjTemp.GetComponent<CardInHand>().handIndex = handList.Count; //必然排在最后
    handList.Add(cardObjTemp); //将Card和GameObject加入List
    CardRevealTool.GetInstance().ListHand();//先排列手牌
    cardObjTemp.SetActive(true); //再将卡牌显示
    }
    else
    {
    Debug.Log("牌库已经没有牌了!");
    }
    }
  • ListHand排列手牌方法重构

    先对List中现有的手牌更新handIndex(使排列手牌不再依赖于HandZone子物体的顺序),之后按算法执行排列

    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
    public void ListHand()
    {
    //更新手牌下标
    int count = 0;
    foreach (GameObject item in CardAreaManager.GetInstance().HandList)
    {
    item.GetComponent<CardInHand>().handIndex = count;
    count++;
    }

    Transform handZoneTransform = handZone.transform;
    int childCount = handZoneTransform.childCount;
    if (childCount <= 0) return; //没有手牌直接返回

    //根据手牌大小获取牌张间隔
    float cardWith = handZoneTransform.GetChild(0).GetComponent<RectTransform>().rect.width;
    float space = cardWith / 25f * 16f;
    float halfSpace = space / 2f;
    //计算第一张牌的位置
    float startXAxis = 0f;
    if (childCount % 2 == 0)
    {
    startXAxis = -(childCount / 2) * space + halfSpace;
    }
    else
    {
    startXAxis = -(childCount - 1) / 2 * space;
    }

    //从第一张牌开始,按间隔排列手牌
    Transform hand;
    for (int i = 0; i < childCount; i++)
    {
    //横向排列手牌
    hand = handZoneTransform.GetChild(i);
    //Test
    int index = hand.GetComponent<CardInHand>().handIndex;
    hand.localPosition = new Vector3(startXAxis + index * space, 0f, 0f);
    }
    }

策略模式重构检索功能

重构手牌列表后,发现检索功能既要对Card进行操作,也要对GameObject进行操作,放回牌库的检索还要对DeckPosition进行操作,各种情况写在同一个SelectCardArea类里十分繁琐

想到用策略模式重构检索功能。

相关代码如下:

  • 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
    34
    35
    36
    37
    38
    39
    40
    public class SelectCardArea : MonoBehaviour
    {
    //卡牌展示列表显示|隐藏
    private bool isShow = true;
    public SelectCardStrategy strategy;

    public bool OnCardSelected(Card selectedCard,GameObject cardObj)
    {
    return strategy.OnCardHasSelected(selectedCard, cardObj);
    }

    public void OnCardCanceled(Card cancelCard,GameObject cardObj)
    {
    strategy.OnCardHasCanceled(cancelCard,cardObj);
    }

    public void OnConfirmed()
    {
    strategy.OnConfirmedSelected();
    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;
    }
    }
    }
  • SelectCardStrategy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public abstract class SelectCardStrategy
    {
    /// <summary>
    /// 当卡牌被选中时
    /// </summary>
    public abstract bool OnCardHasSelected(Card selectedCard, GameObject cardObj);

    /// <summary>
    /// 当卡牌取消选中时
    /// </summary>
    public abstract void OnCardHasCanceled(Card cancelCard, GameObject cardObj);

    /// <summary>
    /// 当确认选择时
    /// </summary>
    public abstract void OnConfirmedSelected();
    }
  • SelectHandCard(其他策略类略)

    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
    public class SelectHandCard : SelectCardStrategy
    {
    private Action<GameObject> selectedAction;
    private int selectNumber = 0;
    private List<GameObject> selectedCardList;

    public SelectHandCard(Action<GameObject> action,int selectNum)
    {
    selectedAction = action;
    selectNumber = selectNum;
    selectedCardList = new List<GameObject>();
    }

    public override void OnCardHasCanceled(Card cancelCard, GameObject cardObj)
    {
    //从List移除
    selectedCardList.Remove(cardObj);
    }

    public override bool OnCardHasSelected(Card selectedCard, GameObject cardObj)
    {
    //如果已经选满了,则返回false
    if (selectedCardList.Count >= selectNumber) return false;
    else
    {
    //将卡牌添加进List,返回true
    selectedCardList.Add(cardObj);
    return true;
    }
    }

    public override void OnConfirmedSelected()
    {
    //如果还没选满,不管它,直接返回
    if (selectedCardList.Count != selectNumber) return;
    else
    {
    //否则对于List中的每一张卡牌,触发Action
    foreach (GameObject item in selectedCardList)
    {
    selectedAction?.Invoke(item);
    }
    }
    }
    }
  • 检索卡牌方法

    设置好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
    34
    35
    /// <summary>
    /// 检视牌库中所有牌并加入手牌
    /// </summary>
    public void showDeckAndPutToHand(int putToHandNum)
    {
    //设置好SelectCardArea相关参数
    //规整化putToHandNum:与牌库中卡牌数量取最小
    putToHandNum = Mathf.Min(CardAreaManager.GetInstance().GetDeckNum(), putToHandNum);
    SelectCardArea selectCardArea = listComponent.GetComponent<SelectCardArea>();
    selectCardArea.strategy = new SelectCardCommon(CardAreaManager.GetInstance().PutCardFromDeckToHand,putToHandNum);

    //先清除content子物体
    int childCount = content.transform.childCount;
    for (int i = 0; i < childCount; i++)
    {
    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.SetActive(true);
    }
    listComponent.SetActive(true);
    }
    }