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

總結

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

資源

項目源碼

img_8f0a90f3cbaa0e044fb8bf7b13c4317b.jpe

文章作者:原子蛋
文章出處:https://www.cnblogs.com/lizzie-xhu/
個人網站:https://www.lancel0t.cn/
個人博客:https://blog.lancel0t.cn/
微信公眾號:原子蛋Live+
掃一掃左側的二維碼(或者長按識別二維碼),關注本人微信公共號,獲取更多資源。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/393469.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/393469.shtml
英文地址,請注明出處:http://en.pswp.cn/news/393469.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TensorFlow 學習(十)—— 工具函數

1. 基本 tf.clip_by_value() 截斷&#xff0c;常和對數函數結合使用 # 計算交叉熵crose_ent -tf.reduce_mean(tf.log(y_*tf.clip_by_value(y, 1e-10, 1.))) a tf.reshape(tf.range(6, dtypetf.float32), [2, 3]) tf.clip_by_value(a, 2.5, 4.5) # 將值限定在 2.5 …

delphi5開發人員指南_非設計人員的網頁設計開發人員指南

delphi5開發人員指南I created my first website as a school project when I was 14. The task was simple: create a very basic site including some text, images, and a table. My usual attitude to school projects was to completely forget about them and later come…

leetcode1292. 元素和小于等于閾值的正方形的最大邊長(二分法+前綴和)

給你一個大小為 m x n 的矩陣 mat 和一個整數閾值 threshold。 請你返回元素總和小于或等于閾值的正方形區域的最大邊長&#xff1b;如果沒有這樣的正方形區域&#xff0c;則返回 0 。 示例 2&#xff1a; 輸入&#xff1a;mat [[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2…

java 反射 獲取成員_java 反射獲取成員

package com.wxjaa; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class TestReflect { public static void main(String[] args) throws Exception { // getDeclaredField 可以獲取私有成員&#xff0c; …

Koa 中實現 chunked 數據傳輸

有關于 Transfer-Encoding:chunked 類型的響應&#xff0c;參見之前的文章HTTP 響應的分塊傳輸。這里看 Koa 中如何實現。 Koa 中請求返回的處理 雖然官方文檔有描述說明不建議直接調用 response.write&#xff1a; Bypassing Koas response handling is not supported. Avoid …

git 短寫設置_如何在短短幾分鐘內設置一個Git客戶端

git 短寫設置Today we’re going to talk about Git. You’re going to learn what Git is and how to set up a Git client on your computer.今天我們將討論Git。 您將學習什么是Git&#xff0c;以及如何在計算機上設置Git客戶端。 什么是Git&#xff1f; (What is Git?) I…

P1977 出租車拼車

P1977 出租車拼車 題目背景 話說小 x 有一次去參加比賽&#xff0c;雖然學校離比賽地點不太遠&#xff0c;但小 x 還是想坐 出租車去。大學城的出租車總是比較另類&#xff0c;有“拼車”一說&#xff0c;也就是說&#xff0c;你一個人 坐車去&#xff0c;還是一堆人一起&#…

leetcode1011. 在 D 天內送達包裹的能力(二分查找)

傳送帶上的包裹必須在 D 天內從一個港口運送到另一個港口。 傳送帶上的第 i 個包裹的重量為 weights[i]。每一天&#xff0c;我們都會按給出重量的順序往傳送帶上裝載包裹。我們裝載的重量不會超過船的最大運載重量。 返回能在 D 天內將傳送帶上的所有包裹送達的船的最低運載…

java集合概念_JAVA集合概念

Java集合是使程序能夠存儲和操縱元素不固定的一組數據。 所有Java集合類都位于java.uti包中。與Java數組不同&#xff0c;Java集合中不能存放基本數據類型&#xff0c;只能存放對象的引用。但是在JDK5.0以后的版本當中&#xff0c;JAVA增加了“自動裝箱”和“自動拆箱”的機制&…

項目計劃總結

項目計劃總結 任務 日期 聽課&#xff08;min&#xff09; 編程&#xff08;min&#xff09; 閱讀課本&#xff08;min&#xff09; 日總結&#xff08;min&#xff09; 2017/3/13 120 70 190 2017/3/14 80 80 2017/3/15 90 30 120 2017/3/16 …

HTML5新特性之Mutation Observer

Mutation Observer&#xff08;變動觀察器&#xff09;是監視DOM變動的接口。當DOM對象樹發生任何變動時&#xff0c;Mutation Observer會得到通知。 要概念上&#xff0c;它很接近事件。可以理解為&#xff0c;當DOM發生變動會觸發Mutation Observer事件。但是&#xff0c;它與…

leetcode230. 二叉搜索樹中第K小的元素(中序遍歷)

給定一個二叉搜索樹&#xff0c;編寫一個函數 kthSmallest 來查找其中第 k 個最小的元素。說明&#xff1a; 你可以假設 k 總是有效的&#xff0c;1 ≤ k ≤ 二叉搜索樹元素個數。示例 1:輸入: root [3,1,4,null,2], k 13/ \1 4\2 輸出: 1解題思路 變量 cnt:統計已經按序遍…

Python操作MongoDB - 極簡教程

2019獨角獸企業重金招聘Python工程師標準>>> Python 連接 MongoDB 安裝PyMongo模塊 pip install pymongo使用MongoClient建立連接 from pymongo import MongoClient # 以下為三種建立連接的方式 #client MongoClient() #client MongoClient(localhost, 27017) #cl…

nuxt.js的核心代碼_Nuxt.js中的通用應用程序代碼結構

nuxt.js的核心代碼by Krutie Patel通過克魯蒂帕特爾(Krutie Patel) Nuxt.js中的通用應用程序代碼結構 (Universal application code structure in Nuxt.js) Nuxt.js中的源代碼結構的簡要摘要 (A brief summary of source code structure in Nuxt.js) Are you new to the Nuxt.…

java 省市區三級聯動_AJAX省市區三級聯動下拉菜單(java版)

此小程序的功能主要是采用異步請求方式從數據庫中調取省市區信息顯示到下拉列表&#xff1a;代碼如下&#xff1a;建立數據庫中的代碼和一些配置文件信息就省略了&#xff0c;主要有JavaScript中的代碼為&#xff1a;$(document).ready(function(){$.get("getProvince.do&…

20155305喬磊2016-2017-2《Java程序設計》第四周學習總結

20155305喬磊2016-2017-2《Java程序設計》第四周學習總結 教材學習內容總結 繼承 繼承就是避免多個類間重復定義共同行為。面向對象中&#xff0c;子類繼承父類&#xff0c;就是把程序中相同的代碼部分提升為父類。extends關鍵字&#xff0c;表示前者會擴充后者的行為&#xff…

leetcode29. 兩數相除(位運算)

給定兩個整數&#xff0c;被除數 dividend 和除數 divisor。將兩數相除&#xff0c;要求不使用乘法、除法和 mod 運算符。 返回被除數 dividend 除以除數 divisor 得到的商。 整數除法的結果應當截去&#xff08;truncate&#xff09;其小數部分&#xff0c;例如&#xff1a;…

【eclipse轉idea的第一天】配置idea

為什么80%的碼農都做不了架構師&#xff1f;>>> 導入maven項目 設置maven(全局) 為了不然才轉idea的碼友們重復我犯過的錯&#xff0c;我這兒截圖步驟說明下&#xff1a; 這里是列表文本這里是列表文本idea的設置有兩種&#xff1a;全局&#xff0c;局部(我這么叫的…

node.js web框架_使用Node.js進行Web爬取的終極指南

node.js web框架So what’s web scraping anyway? It involves automating away the laborious task of collecting information from websites.那么&#xff0c;什么是網絡抓取&#xff1f; 它涉及自動化從網站收集信息的艱巨任務。 There are a lot of use cases for web s…

java局部內部類 final_Java的局部內部類以及final類型的參數和變量

Thinking In Java里面的說法(***正確的說法)&#xff1a; 如果定義一個匿名內部類&#xff0c;并且希望它使用一個在其外部定的對象&#xff0c;那么編譯器會要求其參數引用是final 的。publicclassTester {publicstaticvoidmain(String[] args) {A a newA();C c newC();c.shou…