Unity3D手機斗地主游戲開發實戰(02)_叫地主功能實現


大體思路

前面我們實現了點擊開始游戲按鈕,系統依次給玩家發牌的邏輯和動畫,並展示當前的手牌。這期我們繼續實現接下來的功能--叫地主。

1.首先這兩天,學習了DOTween,這是一個強大的Unity動畫插件,大家可以參考:DOTween官方文檔,個人感覺DOTween還是比較好用的。

好的,我們先來重構一下動畫部分的代碼(沒有絕對牛逼的架構和設計,項目過程中不要不斷的持續改進嘛);先把之前ITween相關引用從項目中刪除,然后導入DOTween插件。

相關動畫代碼改造示例如下:

//移動動畫,動畫結束后自動銷毀
var tween = cover.transform.DOMove(playerHeapPos[termCurrentIndex].position, 0.3f);
tween.OnComplete(() => Destroy(cover));

怎么樣,比之前簡潔多了吧,而且之前ITween好像不太好用,處理自動銷毀時,跟協程點沖突(可能我自己用的方式不對),現在改用DOTween,一點問題也沒有~官網上也有這幾個動畫插件的性能比較,相對來說DOTween表現還是很不錯的。

2.再來說說具體的設計思路

剛開始覺得叫地主邏輯挺簡單,不就是分2次發牌嘛,第一次發51張,后面誰叫到地主再發3張就好了嘛,但其實實現的過程中,發現沒有那么簡單,需要注意的細節挺多:

a.發牌邏輯調整:因為發牌是按照當前玩家順序,依次發牌,相當一個箭頭一直指向當前回合玩家,發完51張牌后,箭頭又開始指向開始發牌的玩家;這時候,需要判斷首次發牌結束,由當前回合玩家開始叫牌;

b.當前玩家進入叫牌階段時,觸發倒計時,倒計時內如果玩家選擇叫牌,則箭頭保持不變,然后開始繼續給當前玩家發3張剩余的牌;

c.倒計時內如果玩家選擇不叫,則箭頭繼續指向下個玩家,下個玩家開始叫牌,回到分支b;

d.倒計時內玩家如果沒有任何選擇,結束后默認不叫,回到分支c;

e.如果3個玩家都沒叫,則流局,重新開局(本期未實現,很容易,大家后面可以自己去嘗試實現);

f.可以選擇叫3分、2分、1分(將來需要實現,設計開發提前考慮)

h.考慮到玩家自己和對手叫牌邏輯不一樣,自己的話,通過界面點擊觸發,對手暫時監聽按鍵觸發(后期改智能AI判斷),比如按下Q叫牌,按下W不叫;

i.倒計時設計,叫牌的時候,需要顯示玩家面前的計時器,計時結束后觸發不叫,其他情況下隱藏;

j.玩家只有自己回合才能叫地主,必須做限制;

玩家類調整

Player作為玩家的基類,我們需要增加ToBiding(開始叫地主),ForBid(搶地主),NotBid(不搶地主),來控制玩家在叫地主過程中的公共邏輯。

ToBiding:轉到自己回合,並設置倒計時;

ForBid:跳出增加回合,停止倒計時,並調用卡牌管理類中的搶地主功能;

ForBid:跳出增加回合,停止倒計時,並調用卡牌管理類中的不搶地主功能;

然后,倒計時這塊,添加一個協程Considerating,專門處理倒計時控件顯示,標識玩家正在考慮中;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

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

    private Text cardCoutText;
    private Text countDownText;
    private int consideratingTime = 6; //玩家考慮時間
    protected bool isMyTerm = false;  //當前是否是自己回合

    void Start()
    {
        cardCoutText = transform.Find("HeapPos/Text").GetComponent<Text>();
        countDownText = transform.Find("CountDown/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();
    }

    /// <summary>
    /// 用戶考慮時間
    /// </summary>
    private IEnumerator Considerating()
    {
        //倒計時
        var time = consideratingTime;
        while (time > 0)
        {
            countDownText.text = time.ToString();

            yield return new WaitForSeconds(1);
            time--;
        }
        NotBid();
    }
    /// <summary>
    /// 開始叫地主
    /// </summary>
    public virtual void ToBiding()
    {
        isMyTerm = true;

        //開始倒計時
        countDownText.transform.parent.gameObject.SetActive(true);
        StartCoroutine("Considerating");
    }
    /// <summary>
    /// 搶地主
    /// </summary>
    public void ForBid()
    {
        //關閉倒計時
        countDownText.transform.parent.gameObject.SetActive(false);
        StopCoroutine("Considerating");

        CardManager._instance.ForBid();
        isMyTerm = false;
    }
    /// <summary>
    /// 不搶地主
    /// </summary>
    public void NotBid()
    {
        //關閉倒計時
        countDownText.transform.parent.gameObject.SetActive(false);
        StopCoroutine("Considerating");

        CardManager._instance.NotBid();
        isMyTerm = false;
    }
    /// <summary>
    /// 卡牌排序(從大到小)
    /// </summary>
    protected void Sort()
    {
        cardInfos.Sort();
        cardInfos.Reverse();
    }

}
View Code

自身玩家類調整

其實就是重寫了基類ToBiding方法,調用顯示叫牌按鈕(只有自身玩家才需要)

using System;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;

/// <summary>
/// 自身玩家
/// </summary>
public class PlayerSelf : Player
{
    public GameObject prefab;   //預制件

    private Transform originPos1; //牌的初始位置
    private Transform originPos2; //牌的初始位置
    private List<GameObject> cards = new List<GameObject>();
    private bool canSelectCard = false;     //玩家是否可以選牌

    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]);
            card.transform.SetAsLastSibling();
            //動畫移動
            var tween = card.transform.DOMoveX(startPos.x + offsetX * i, 1f);
            if (i == cardInfos.Count - 1) //最后一張動畫
            {
                tween.OnComplete(() => { canSelectCard = true; });
            }
            cards.Add(card);
        }
    }
    /// <summary>
    /// 銷毀所有卡牌對象
    /// </summary>
    public void DestroyAllCards()
    {
        cards.ForEach(Destroy);
        cards.Clear();
    }
    /// <summary>
    /// 開始叫牌
    /// </summary>
    public override void ToBiding()
    {
        base.ToBiding();
        CardManager._instance.SetBidButtonActive(true);
    }
    /// <summary>
    /// 點擊卡牌處理
    /// </summary>
    /// <param name="data"></param>
    public void CardClick(BaseEventData data)
    {
        //叫牌或出牌階段才可以選牌
        if (canSelectCard &&
            (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

對手玩家類調整

主要模擬對手叫牌,如果是玩家回合,按下Q叫牌,按下W不叫

using UnityEngine;

public class PlayerOther : Player
{
    void Update()
    {
        //如果當前是自己回合,模擬對手叫牌
        if (isMyTerm)
        {
            if (Input.GetKeyDown(KeyCode.Q))    //叫牌
            {
                ForBid();
            }
            if (Input.GetKeyDown(KeyCode.W))    //不叫
            {
                NotBid();
            }
        }
    }
}
View Code

卡牌管理類調整

首先需要改造發牌方法,現在區分發牌是發普通牌還是發發地主牌,根據發牌的不同類型,取牌堆里的不同牌,再判斷是依次發牌,還是只發給地主牌。

然后具體實現玩家開始搶地主StartBiding,叫地主ForBid,不叫地主NotBid 3個方法:

StartBiding:從當前回合玩家開始叫牌;

ForBid:設置當前回合玩家是地主,並給地主發余下3張牌;

NotBid :轉向下個回合玩家開始叫牌;

using System;
using System.Collections;
using System.IO;
using System.Linq;
using DG.Tweening;
using UnityEngine;

/// <summary>
/// 卡牌管理
/// </summary>
public class CardManager : MonoBehaviour
{
    public static CardManager _instance;    //單例


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

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

    private string[] cardNames;  //所有牌集合
    private int termStartIndex;  //回合開始玩家索引
    private int termCurrentIndex;  //回合當前玩家索引
    private int bankerIndex;        //當前地主索引
    private GameObject bidBtns;
    void Awake()
    {
        _instance = this;
        cardNames = GetCardNames();

        bidBtns = GameObject.Find("BidBtns");
        bidBtns.SetActive(false);
    }
    /// <summary>
    /// 洗牌
    /// </summary>
    public void ShuffleCards()
    {
        //進入洗牌階段
        cardManagerState = CardManagerStates.ShuffleCards;
        cardNames = cardNames.OrderBy(c => Guid.NewGuid()).ToArray();
    }
    /// <summary>
    /// 開始發牌
    /// </summary>
    public IEnumerator DealCards()
    {
        //進入發牌階段
        cardManagerState = CardManagerStates.DealCards;
        termCurrentIndex = termStartIndex;

        yield return DealHeapCards(false);
    }
    /// <summary>
    /// 發牌堆上的牌(如果現在不是搶地主階段,發普通牌,如果是,發地主牌)
    /// </summary>
    /// <returns></returns>
    private IEnumerator DealHeapCards(bool ifForBid)
    {
        //顯示牌堆
        heapPos.gameObject.SetActive(true);
        playerHeapPos.ToList().ForEach(s => { s.gameObject.SetActive(true); });

        var cardNamesNeeded = ifForBid
            ? cardNames.Skip(cardNames.Length - 3).Take(3)  //如果是搶地主牌,取最后3張
            : cardNames.Take(cardNames.Length - 3);         //如果首次發牌

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

            var cover = Instantiate(coverPrefab, heapPos.position, Quaternion.identity, heapPos.transform);
            cover.GetComponent<RectTransform>().localScale = Vector3.one;
            //移動動畫,動畫結束后自動銷毀
            var tween = cover.transform.DOMove(playerHeapPos[termCurrentIndex].position, 0.3f);
            tween.OnComplete(() => Destroy(cover));

            yield return new WaitForSeconds(1 / dealCardSpeed);

            //下一個需要發牌者
            if (!ifForBid)
                SetNextPlayer();
        }

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

        //發普通牌
        if (!ifForBid)
        {
            //顯示玩家手牌
            ShowPlayerSelfCards();
            StartBiding();
        }
        //發地主牌
        else
        {
            if (Players[bankerIndex] is PlayerSelf)
            {
                //顯示玩家手牌
                ShowPlayerSelfCards();
            }
            cardManagerState = CardManagerStates.Playing;
        }
    }
    /// <summary>
    /// 開始搶地主
    /// </summary>
    private void StartBiding()
    {
        cardManagerState = CardManagerStates.Bid;

        Players[termCurrentIndex].ToBiding();
    }
    /// <summary>
    /// 顯示玩家手牌
    /// </summary>
    private void ShowPlayerSelfCards()
    {
        Players.ToList().ForEach(s =>
        {
            var player0 = s as PlayerSelf;
            if (player0 != null)
            {
                player0.GenerateAllCards();
            }
        });
    }
    /// <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>
    public void ForBid()
    {
        //設置當前地主
        bankerIndex = termCurrentIndex;

        //給地主發剩下的3張牌
        SetBidButtonActive(false);
        StartCoroutine(DealHeapCards(true));
    }
    /// <summary>
    /// 不叫
    /// </summary>
    public void NotBid()
    {
        SetBidButtonActive(false);
        SetNextPlayer();
        Players[termCurrentIndex].ToBiding();
    }
    /// <summary>
    /// 控制叫地主按鈕是否顯示
    /// </summary>
    /// <param name="isActive"></param>
    public void SetBidButtonActive(bool isActive)
    {
        bidBtns.SetActive(isActive);
    }

    /// <summary>
    /// 設置下一輪開始玩家
    /// </summary>
    public void SetNextTerm()
    {
        termStartIndex = (termStartIndex + 1) % Players.Length;
    }
    /// <summary>
    /// 設置下個回合玩家
    /// </summary>
    /// <returns></returns>
    public void SetNextPlayer()
    {
        termCurrentIndex = (termCurrentIndex + 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;
    }

    //開始新回合
    public void OnStartTermClick()
    {
        ClearCards();
        ShuffleCards();
        StartCoroutine(DealCards());
    }

}
View Code

總結

至此,我們【叫地主】功能大體完成了,來試試效果吧~

資源

項目源碼


免責聲明!

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



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