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 = ; //玩家考虑时间
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 > )
{
countDownText.text = time.ToString(); yield return new WaitForSeconds();
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();
} }
自身玩家类调整
其实就是重写了基类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 / );
var startPos = originPos1.position + Vector3.left * offsetX * leftCount; for (int i = ; 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 - ) //最后一张动画
{
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();
}
}
}
对手玩家类调整
主要模拟对手叫牌,如果是玩家回合,按下Q叫牌,按下W不叫
using UnityEngine; public class PlayerOther : Player
{
void Update()
{
//如果当前是自己回合,模拟对手叫牌
if (isMyTerm)
{
if (Input.GetKeyDown(KeyCode.Q)) //叫牌
{
ForBid();
}
if (Input.GetKeyDown(KeyCode.W)) //不叫
{
NotBid();
}
}
}
}
卡牌管理类调整
首先需要改造发牌方法,现在区分发牌是发普通牌还是发发地主牌,根据发牌的不同类型,取牌堆里的不同牌,再判断是依次发牌,还是只发给地主牌。
然后具体实现玩家开始抢地主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 = ; //发牌速度
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 - ).Take() //如果是抢地主牌,取最后3张
: cardNames.Take(cardNames.Length - ); //如果首次发牌 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( / dealCardSpeed); //下一个需要发牌者
if (!ifForBid)
SetNextPlayer();
} //隐藏牌堆
heapPos.gameObject.SetActive(false);
playerHeapPos[].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 + ) % Players.Length;
}
/// <summary>
/// 设置下个回合玩家
/// </summary>
/// <returns></returns>
public void SetNextPlayer()
{
termCurrentIndex = (termCurrentIndex + ) % 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());
} }
总结
至此,我们【叫地主】功能大体完成了,来试试效果吧~
资源
Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现(不定期更新中~~~)的更多相关文章
- Unity3D手机斗地主游戏开发实战(04)_出牌判断大小(已完结)
之前我们实现了叫地主.玩家和电脑自动出牌主要功能,但是还有个问题,出牌的时候,没有有效性检查和比较牌力大小.比如说,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9. 问题1:出牌检 ...
- Unity3D手机斗地主游戏开发实战(04)_出牌判断大小
之前我们实现了叫地主.玩家和电脑自动出牌主要功能,但是还有个问题,出牌的时候,没有有效性检查和比较牌力大小.比如说,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9. 问题1:出牌检 ...
- Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现
大体思路 前面我们实现了点击开始游戏按钮,系统依次给玩家发牌的逻辑和动画,并展示当前的手牌.这期我们继续实现接下来的功能--叫地主. 1.首先这两天,学习了DOTween,这是一个强大的Unity动画 ...
- Unity3D手机斗地主游戏开发实战(01)_发牌功能实现
园子荒废多年,闲来无事,用Unity3D来尝试做一个简单的小游戏,一方面是对最近研究的Unity3D有点总结,一方面跟广大的园友相互学习和提高.话不多说,进入正题~ 一.创建项目 1.创建Unity2 ...
- Unity3D手机斗地主游戏开发实战(03)_地主牌显示和出牌逻辑(不定期更新中~~~)
Hi,之前有同学说要我把源码发出来,那我就把半成品源码的链接放在每篇文件的最后,有兴趣的话可以查阅参考,有问题可以跟我私信,也可以关注我的个人公众号,互相交流嘛.当然,代码也是在不断的持续改进中~ 上 ...
- 【转】 [Unity3D]手机3D游戏开发:场景切换与数据存储(PlayerPrefs 类的介绍与使用)
http://blog.csdn.net/pleasecallmewhy/article/details/8543181 在Unity中的数据存储和iOS中字典的存储基本相同,是通过关键字实现数据存储 ...
- (转)火溶CEO王伟峰:Unity3D手机网游开发
今天看到这篇文章,感觉很不错,尤其是那句“Unity3D的坑我觉得最严重的坑就是没有懂3D的程序员,把Unity当成Office用”. 转自http://blog.csdn.net/wwwang891 ...
- 手机3D游戏开发:自定义Joystick的相关设置和脚本源码
Joystick在手游开发中非常常见,也就是在手机屏幕上的虚拟操纵杆,但是Unity3D自带的Joystick贴图比较原始,所以经常有使用自定义贴图的需求. 下面就来演示一下如何实现自定义JoySti ...
- cocos2d-x游戏开发实战原创视频讲座系列1之2048游戏开发
cocos2d-x游戏开发实战原创视频讲座系列1之2048游戏开发 的产生 视持续更新中.... 视频存放地址例如以下:http://ipd.pps.tv/user/1058663622 ...
随机推荐
- Java_注解_01_注解(Annotation)详解
一.注解的概念 Annotation(注解)是插入代码中的元数据(元数据从metadata一词译来,就是“描述数据的数据”的意思),在JDK5.0及以后版本引入.它可以在编译期使用预编译工具进行处理, ...
- vue+element搭建的后台管理系统
最近工作不是很忙,自己在学习vue,在网上找了一个简单的项目练练手..... 这是本人的gitHub 上的项目地址:https://github.com/shixiaoyanyan/vue-admin ...
- servlet_2
package com.atguigu.servlet; import java.io.IOException; import javax.servlet.Servlet;import javax.s ...
- python基础之五大标准数据类型
学习一门语言,往往都是从Hello World开始. 但是笔者认为,在一个黑框框中输出一个"你好,世界"并没有什么了不起,要看透事物的本质,熟悉一门语言,就要了解其底层,就是我们常 ...
- C语言编译过程及数据类型
写在前面 C语言可以称得上是高级语言中的低级语言,接下来一段时间,我会写一下文章关于c语言,把它的神秘面纱一 一揭开.下面主要是c语言的C语言编译过程及数据类型 源文件编译过程 为了使计算机能执行高级 ...
- oracle存储过程中is和as区别
在存储过程(PROCEDURE)和函数(FUNCTION)中没有区别:在视图(VIEW)中只能用AS不能用IS:在游标(CURSOR)中只能用IS不能用AS.
- Apache Spark 2.2.0 中文文档 - 集群模式概述 | ApacheCN
集群模式概述 该文档给出了 Spark 如何在集群上运行.使之更容易来理解所涉及到的组件的简短概述.通过阅读 应用提交指南 来学习关于在集群上启动应用. 组件 Spark 应用在集群上作为独立的进程组 ...
- S2_SQL_第二章
第二章:初始mySql 2.1:mySql简介 2.1.2:mysql的优势 运行速度块,体积小,命令执行的块 使用成本低,开源的 容易使用 可移植性强 2.2:mysql的配置 2.2.1:端口配置 ...
- Google赛马分析
原题 想必田忌赛马的故事,大家都耳熟能详.但是,大家知道Goolge的童鞋们是怎么赛马的么?不过,首先,大家要先尝试一下:有25匹马,每次只能五匹一起跑,那么最少跑几次,才能确定前三甲呢? 分析 这样 ...
- PHP MYSQL 搜索周边坐标,并计算两个点之间的距离
搜索附近地点,例如,坐标(39.91, 116.37)附近500米内的人,首先算出“给定坐标附近500米”这个范围的坐标范围. 虽然它是个圆,但我们可以先求出该圆的外接正方形,然后拿正方形的经纬度范围 ...