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.mzph.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…