Unity3D手機鬥地主遊戲開發實戰(01)_發牌功能實現

園子荒廢多年,閒來無事,用Unity3D來嘗試作一個簡單的小遊戲,一方面是對最近研究的Unity3D有點總結,一方面跟廣大的園友相互學習和提升。話很少說,進入正題~git

建立項目

1.建立Unity2017的2D項目,暫且叫作ChinesePoker吧,就用自帶的UGUI來編輯UI, 目前只導入iTween插件,用來方便控制動畫效果。github

目錄結構以下:ide

  

考慮卡牌須要動態生成,我把圖片資源放到Resource目錄,並按照Card_類型(大小王,紅桃,黑桃,方片,梅花 )_數字(卡牌所在類型中的數字)命名。學習

素材都是網上找的,沒有美工基礎,就是這麼個意思,你們將就看吧,:)測試

2.建第一個場景,默認叫001_Playing,做爲主要玩牌的場景,暫時做爲第1個場景,後期新場景添加進來,咱們可能再調整場景的順序。優化

添加一個UI->Image,選擇一個背景圖片;動畫

添加3個UI->Canvas,分別取名叫Player0,Player1,Player2,表明玩家,對手1,對手2;ui

每一個Player底下,添加一個Image,選擇卡牌背面圖片,分別表示發牌時各自牌堆的位置,並在桌面放置一個總牌堆的位置,默認not active;this

建一個卡牌的圖片,命名爲Card,並做爲預製件,放入Player0中間一個,稍微偏移必定位置再放置一個,用來計算每張牌跟臨牌相對位置,設置not active;spa

建一個卡牌的背面圖片,命名Cover,也做爲預製件;

添加一個測試按鈕TestButton;

差很少了,大概結構以下:

建立卡牌、玩家信息

1.新建CardInfo類,主要不要繼承默認的MonoBehaviour類,用來做爲卡牌的實體類;

實現IComparable接口,後面手牌排序會用到。

public class CardInfo : IComparable
{
    public string cardName; //卡牌圖片名
    public CardTypes cardType; //牌的類型
    public int cardIndex;      //牌在所在類型的索引1-13

    public CardInfo(string cardName)
    {
        this.cardName = cardName;
        var splits = cardName.Split('_');

        switch (splits[1])
        {
            case "1":
                cardType = CardTypes.Hearts;
                cardIndex = int.Parse(splits[2]);
                break;
            case "2":
                cardType = CardTypes.Spades;
                cardIndex = int.Parse(splits[2]);
                break;
            case "3":
                cardType = CardTypes.Diamonds;
                cardIndex = int.Parse(splits[2]);
                break;
            case "4":
                cardType = CardTypes.Clubs;
                cardIndex = int.Parse(splits[2]);
                break;
            case "joker":
                cardType = CardTypes.Joker;
                cardIndex = int.Parse(splits[2]);
                break;
            default:
                throw new Exception(string.Format("卡牌文件名{0}非法!", cardName));
        }
    }

    //卡牌大小比較
    public int CompareTo(object obj)
    {
        CardInfo other = obj as CardInfo;

        if (other == null)
            throw new Exception("比較對象類型非法!");

        //若是當前是大小王
        if (cardType == CardTypes.Joker)
        {
            //對方也是大小王
            if (other.cardType == CardTypes.Joker)
            {
                return cardIndex.CompareTo(other.cardIndex);
            }
            //對方不是大小王
            return 1;
        }
        //若是是通常的牌
        else
        {
            //對方是大小王
            if (other.cardType == CardTypes.Joker)
            {
                return -1;
            }
            //若是對方也是通常的牌
            else
            {
                //計算牌力
                var thisNewIndex = (cardIndex == 1 || cardIndex == 2) ? 13 + cardIndex : cardIndex;
                var otherNewIndex = (other.cardIndex == 1 || other.cardIndex == 2) ? 13 + other.cardIndex : other.cardIndex;

                if (thisNewIndex == otherNewIndex)
                {
                    return -cardType.CompareTo(other.cardType);
                }

                return thisNewIndex.CompareTo(otherNewIndex);
            }
        }
    }

}
View Code

2.Card預製件上,添加Card腳本,主要保存對應CardInfo信息、選中狀態,並加載卡牌圖片;

public class Card : MonoBehaviour
{
    private Image image;        //牌的圖片
    private CardInfo cardInfo;  //卡牌信息
    private bool isSelected;    //是否選中

    void Awake()
    {
        image = GetComponent<Image>();
    }

    /// <summary>
    /// 初始化圖片
    /// </summary>
    /// <param name="cardInfo"></param>
    public void InitImage(CardInfo cardInfo)
    {
        this.cardInfo = cardInfo;
        image.sprite = Resources.Load("Images/Cards/" + cardInfo.cardName, typeof(Sprite)) as Sprite;
    }
    /// <summary>
    /// 設置選擇狀態
    /// </summary>
    public void SetSelectState()
    {
        if (isSelected)
        {
            iTween.MoveTo(gameObject, transform.position - Vector3.up * 10f, 1f);
        }
        else
        {
            iTween.MoveTo(gameObject, transform.position + Vector3.up * 10f, 1f);
        }

        isSelected = !isSelected;
    }
}
View Code

3.考慮玩家分爲2種類型,先建立一個公共的基類,實現玩家公共的方法,好比增長一張卡牌、清空全部卡片、排序等;

public class Player : MonoBehaviour
{
    protected List<CardInfo> cardInfos = new List<CardInfo>();  //我的所持卡牌

    private Text cardCoutText;

    void Start()
    {
        cardCoutText = transform.Find("HeapPos/Text").GetComponent<Text>();
    }

    /// <summary>
    /// 增長一張卡牌
    /// </summary>
    /// <param name="cardName"></param>
    public void AddCard(string cardName)
    {
        cardInfos.Add(new CardInfo(cardName));
        cardCoutText.text = cardInfos.Count.ToString();
    }
    /// <summary>
    /// 清空全部卡片
    /// </summary>
    public void DropCards()
    {
        cardInfos.Clear();
    }

    protected void Sort()
    {
        cardInfos.Sort();
        cardInfos.Reverse();
    }
}
View Code

4.添加第一種玩家(自身玩家)PlayerSelf,繼承Player,並掛載到Player0對象上;

實現整理手牌的邏輯:發牌後,從中間的位置,根據大小依次將牌展開;

獲取牌面點擊事件,將牌選中或取消選中;

public class PlayerSelf : Player
{
    public GameObject prefab;   //預製件

    private Transform originPos1; //牌的初始位置
    private Transform originPos2; //牌的初始位置
    private List<GameObject>  cards=new List<GameObject>();

    void Awake()
    {
        originPos1 = transform.Find("OriginPos1");
        originPos2 = transform.Find("OriginPos2");
    }

    //整理手牌
    public void GenerateAllCards()
    {
        //排序
        Sort();
        //計算每張牌的偏移
        var offsetX = originPos2.position.x - originPos1.position.x;
        //獲取最左邊的起點
        int leftCount = (cardInfos.Count / 2);
        var startPos = originPos1.position + Vector3.left * offsetX * leftCount;

        for (int i = 0; i < cardInfos.Count; i++)
        {
            //生成卡牌
            var card = Instantiate(prefab, originPos1.position, Quaternion.identity, transform);
            card.GetComponent<RectTransform>().localScale = Vector3.one * 0.6f;
            card.GetComponent<Card>().InitImage(cardInfos[i]);

            var targetPos = startPos + Vector3.right * offsetX * i;
            card.transform.SetAsLastSibling();
            //動畫移動
            iTween.MoveTo(card, targetPos, 2f);

            cards.Add(card);
        }
    }

    public void DestroyAllCards()
    {
        cards.ForEach(Destroy);
        cards.Clear();
    }

    /// <summary>
    /// 點擊卡牌處理
    /// </summary>
    /// <param name="data"></param>
    public void CardClick(BaseEventData data)
    {
        //叫牌或出牌階段才能夠選牌
        if (CardManager._instance.cardManagerState == CardManagerStates.Bid ||
            CardManager._instance.cardManagerState == CardManagerStates.Playing)
        {
            var eventData = data as PointerEventData;
            if (eventData == null) return;

            var card = eventData.pointerCurrentRaycast.gameObject.GetComponent<Card>();
            if (card == null) return;

            card.SetSelectState();
        }
    }
}
View Code

5.添加另外一種玩家(對手玩家)PlayerOther,繼承Player,並掛載到Player1,Player2對象上;

暫時沒有實現任何其餘功能:

public class PlayerOther : Player
{
   
}
View Code

實現發牌邏輯

在Camera上添加卡牌管理腳本:CardManager

1.實現洗牌邏輯,這裏用生成GUID隨機性後排序,達到洗牌的目的;

2.記錄當前發牌回合,每發一張牌,跳轉給下一個玩家;

3.記錄當前玩牌回合(未來可能用到),每玩一局,跳轉下個玩家開始發牌;

4.發牌邏輯:

設置牌堆的顯示,動畫依次給每位玩家發一張卡牌,發完牌後,隱藏牌堆,並將玩家的卡牌排序並展現;

public class CardManager : MonoBehaviour
{
    public float dealCardSpeed = 20;  //發牌速度
    public Player[] Players;    //玩家的集合

    public GameObject coverPrefab;      //背面排預製件
    public Transform heapPos;           //牌堆位置
    public Transform[] playerHeapPos;    //玩家牌堆位置


    public static CardManager _instance;    //單例
    public CardManagerStates cardManagerState;

    private string[] cardNames;  //全部牌集合
    private int termStartIndex = 0;  //回合開始玩家索引
    private int termCurrentIndex = 0;  //回合當前玩家索引
    private List<GameObject> covers = new List<GameObject>();   //背面卡牌對象,發牌結束後銷燬

    void Awake()
    {
        _instance = this;

        cardNames = GetCardNames();
    }
    /// <summary>
    /// 洗牌
    /// </summary>
    public void ShuffleCards()
    {
        //進入洗牌階段
        cardManagerState = CardManagerStates.ShuffleCards;
        cardNames = cardNames.OrderBy(c => Guid.NewGuid()).ToArray();
    }
    /// <summary>
    /// 發牌
    /// </summary>
    public IEnumerator DealCards()
    {
        //進入發牌階段
        cardManagerState = CardManagerStates.DealCards;

        //顯示牌堆
        heapPos.gameObject.SetActive(true);
        playerHeapPos.ToList().ForEach(s => { s.gameObject.SetActive(true); });

        foreach (var cardName in cardNames)
        {
            //給當前玩家發一張牌
            Players[termCurrentIndex].AddCard(cardName);

            var cover = Instantiate(coverPrefab, heapPos.position, Quaternion.identity, heapPos.transform);
            cover.GetComponent<RectTransform>().localScale = Vector3.one;
            covers.Add(cover);
            iTween.MoveTo(cover, playerHeapPos[termCurrentIndex].position, 0.3f);

            yield return new WaitForSeconds(1 / dealCardSpeed);

            //下一個須要發牌者
            SetNextPlayer();
        }

        //隱藏牌堆
        heapPos.gameObject.SetActive(false);
        playerHeapPos[0].gameObject.SetActive(false);

        //顯示玩家手牌
        Players.ToList().ForEach(s =>
        {
            var player0 = s as PlayerSelf;
            if (player0 != null)
            {
                player0.GenerateAllCards();
            }
        });
        //動畫結束,進入叫牌階段
        yield return new WaitForSeconds(2f);
        covers.ForEach(Destroy);
        cardManagerState = CardManagerStates.Bid;
    }
    /// <summary>
    /// 清空牌局
    /// </summary>
    public void ClearCards()
    {
        //清空全部玩家卡牌
        Players.ToList().ForEach(s => s.DropCards());

        //顯示玩家手牌
        Players.ToList().ForEach(s =>
        {
            var player0 = s as PlayerSelf;
            if (player0 != null)
            {
                player0.DestroyAllCards();
            }
        });
    }

    /// <summary>
    /// 獲取下個玩家
    /// </summary>
    /// <returns></returns>

    private void SetNextPlayer()
    {
        termCurrentIndex = (termCurrentIndex + 1) % Players.Length;
    }
    /// <summary>
    /// 切換開始回合玩家
    /// </summary>
    public void SetNextTerm()
    {
        termStartIndex = (termStartIndex + 1) % Players.Length;
    }
    private string[] GetCardNames()
    {
        //路徑  
        string fullPath = "Assets/Resources/Images/Cards/";

        if (Directory.Exists(fullPath))
        {
            DirectoryInfo direction = new DirectoryInfo(fullPath);
            FileInfo[] files = direction.GetFiles("*.png", SearchOption.AllDirectories);

            return files.Select(s => Path.GetFileNameWithoutExtension(s.Name)).ToArray();
        }
        return null;
    }

    //for test
    public void OnTestClick()
    {
        ClearCards();
        ShuffleCards();
        StartCoroutine(DealCards());
    }

}
View Code

總結

其實發牌後的動畫,能夠由override基類的方法,交由Player子類處理,不用CardManager集中管理,你們能夠優化一下。

大致邏輯完成,咱們驗證下效果吧:

資源

項目源碼

相關文章
相關標籤/搜索