Hi,之前有同學說要我把源碼發出來,那我就把半成品源碼的鏈接放在每篇文件的最后,有興趣的話可以查閱參考,有問題可以跟我私信,也可以關注我的個人公眾號,互相交流嘛。當然,代碼也是在不斷的持續改進中~
上期我們實現了叫地主功能,不過遺留了一個小功能:叫地主完成以后,要顯示地主的3張牌,這期首先彌補這塊的功能;
接着我們要進入開發出牌邏輯的開發階段,好了,廢話不多說,繼續我們斗地主開發之旅~
地主牌的顯示
我們在玩家界面的頂部中間位置,放置一個新的GameObject,命名為BidCards,用來記錄3張地主牌的顯示位置。
所以我們重構了CardManager中的發牌方法,在給地主發牌同時,生成地主牌的實例,放在BidCards相應位置:

/// <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); //如果首次發牌 //計算每張地主牌的位置 int cardIndex = 0; var width = (bidCards.GetComponent<RectTransform>().sizeDelta.x - 20) / 3; var centerBidPos = Vector3.zero; var leftBidPos = centerBidPos - Vector3.left * width; var rightBidPos = centerBidPos + Vector3.left * width; List<Vector3> bidPoss = new List<Vector3> { leftBidPos, centerBidPos, rightBidPos }; 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) { //顯示地主牌 var bidcard = Instantiate(cardPrefab, bidCards.transform.TransformPoint(bidPoss[cardIndex]), Quaternion.identity, bidCards.transform); bidcard.GetComponent<Card>().InitImage(new CardInfo(cardName)); bidcard.GetComponent<RectTransform>().localScale = Vector3.one * 0.3f; } else { //下一個需要發牌者 SetNextPlayer(); } cardIndex++; } //隱藏牌堆 heapPos.gameObject.SetActive(false); playerHeapPos[0].gameObject.SetActive(false); //發普通牌 if (!ifForBid) { //顯示玩家手牌 ShowPlayerSelfCards(); StartBiding(); } //發地主牌 else { if (Players[bankerIndex] is PlayerSelf) { //顯示玩家手牌 ShowPlayerSelfCards(); } StartFollowing(); } }
好的,我們地主牌顯示已經沒有問題了,接下來,我們要實現出牌回合邏輯
出牌回合功能實現
出牌回合其實跟叫地主回合類似,也是可以抽象出3種方法:進入出牌階段、出牌、不出(比較進入叫地主階段、叫地主、不叫地主);
因此,我們參照叫地主的邏輯,再實現出牌邏輯:
調整Player基類
添加開始出牌ToFollowing、出牌ForFollow、不出NotFollow:
- ToFollowing:進入自己回合,關閉其他人的倒計時,進入自己的倒計時階段;
- ForFollow:關閉自己的倒計時,然后將選擇的牌添加到出牌區域,跳出自己回合;
- NotFollow:關閉自己的倒計時,跳出自己回合;

/// <summary> /// 開始出牌 /// </summary> public virtual void ToFollowing() { isMyTerm = true; //關閉倒計時 StopCountDown(CountDownTypes.Follow); //開始倒計時 StartCountDown(CountDownTypes.Follow); } /// <summary> /// 出牌 /// </summary> public void ForFollow() { //關閉倒計時 StopCountDown(CountDownTypes.Follow); //選擇的牌,添加到出牌區域 var selectedCards = cardInfos.Where(s => s.isSelected).ToList(); var offset = 5; for (int i = 0; i < selectedCards.Count(); i++) { var card = Instantiate(prefabSmall, smallCardPos.position + Vector3.right * offset * i, Quaternion.identity, smallCardPos.transform); card.GetComponent<RectTransform>().localScale = Vector3.one * 0.3f; card.GetComponent<Image>().sprite = Resources.Load("Images/Cards/" + selectedCards[i].cardName, typeof(Sprite)) as Sprite; card.transform.SetAsLastSibling(); smallCards.Add(card); } cardInfos = cardInfos.Where(s => !s.isSelected).ToList(); CardManager._instance.ForFollow(); isMyTerm = false; } /// <summary> /// 不出 /// </summary> public void NotFollow() { //關閉倒計時 StopCountDown(CountDownTypes.Follow); CardManager._instance.NotFollow(); isMyTerm = false; } /// <summary> /// 銷毀出牌對象 /// </summary> public void DropAllSmallCards() { smallCards.ForEach(Destroy); smallCards.Clear(); }
調整PlayerSelf類
實現ToFollowing:
調用基類的ToFollowing,並顯示出牌按鈕以供玩家選擇

/// <summary> /// 開始出牌 /// </summary> public override void ToFollowing() { base.ToFollowing(); CardManager._instance.SetFollowButtonActive(true); }
調整PlayerOther類
模擬出牌,隨機選擇除手牌中的一張

void Update() { //如果當前是自己回合,模擬對手叫牌 if (isMyTerm) { if (CardManager._instance.cardManagerState == CardManagerStates.Bid) { if (Input.GetKeyDown(KeyCode.Q)) //叫牌 { ForBid(); } if (Input.GetKeyDown(KeyCode.W)) //不叫 { NotBid(); } } if (CardManager._instance.cardManagerState == CardManagerStates.Playing) { if (Input.GetKeyDown(KeyCode.Q)) //出牌 { var rd1 = Random.Range(0, cardInfos.Count); cardInfos[rd1].isSelected = true; ForFollow(); } if (Input.GetKeyDown(KeyCode.W)) //不出 { NotFollow(); } } } }
調整CardManager
實現卡牌管理對玩家出牌的控制:
- 發完地主牌以后,開始出牌階段,由地主先出牌;
- 玩家選擇出牌后,將上輪玩家的出牌堆清空,並將選擇的牌添加到自己的出牌堆,輪轉到下個玩家;
- 玩家選擇不出牌,將上輪玩家的出牌堆清空,輪轉到下個玩家;

/// <summary> /// 開始出牌階段 /// </summary> private void StartFollowing() { cardManagerState = CardManagerStates.Playing; //地主先出牌 Players[bankerIndex].ToFollowing(); } /// <summary> /// 玩家出牌 /// </summary> public void ForFollow() { SetFollowButtonActive(false); //上輪玩家出牌清空 Players[(termCurrentIndex + Players.Length - 1) % 3].DropAllSmallCards(); if (Players[termCurrentIndex] is PlayerSelf) ShowPlayerSelfCards(); SetNextPlayer(); Players[termCurrentIndex].ToFollowing(); } /// <summary> /// 玩家不出 /// </summary> public void NotFollow() { SetFollowButtonActive(false); //上輪玩家出牌清空 Players[(termCurrentIndex + Players.Length - 1) % 3].DropAllSmallCards(); SetNextPlayer(); Players[termCurrentIndex].ToFollowing(); }
代碼整理
現在我們的代碼具有一定的規模了,為了方便更好的管理,把現有的代碼重新整理一下,並進行功能分類,比如:
總結
嗯,今天到此為止,我們再來測試驗證下,當然,目前只是實現了出牌的功能,沒有對牌力進行校驗和出牌的控制,對手玩家隨機模擬出牌,尚未加入AI。我們以后逐步去實現~來看看這期的效果吧~