Unity3D手机斗地主游戏开发实战(01)_发牌功能实现
园子荒废多年,闲来无事,用Unity3D来尝试做一个简单的小游戏,一方面是对最近研究的Unity3D有点总结,一方面跟广大的园友相互学习和提高。话不多说,进入正题~
创建项目
1.创建Unity2017的2D项目,暂且叫做ChinesePoker吧,就用自带的UGUI来编辑UI, 目前只导入iTween插件,用来方便控制动画效果。
目录结构如下:
考虑卡牌需要动态生成,我把图片资源放到Resource目录,并按照Card_类型(大小王,红桃,黑桃,方片,梅花 )_数字(卡牌所在类型中的数字)命名。
素材都是网上找的,没有美工基础,就是这么个意思,大家将就看吧,:)
2.建第一个场景,默认叫001_Playing,作为主要玩牌的场景,暂时作为第1个场景,后期新场景添加进来,我们可能再调整场景的顺序。
添加一个UI->Image,选择一个背景图片;
添加3个UI->Canvas,分别取名叫Player0,Player1,Player2,代表玩家,对手1,对手2;
每个Player底下,添加一个Image,选择卡牌背面图片,分别表示发牌时各自牌堆的位置,并在桌面放置一个总牌堆的位置,默认not active;
建一个卡牌的图片,命名为Card,并作为预制件,放入Player0中间一个,稍微偏移一定位置再放置一个,用来计算每张牌跟临牌相对位置,设置not active;
建一个卡牌的背面图片,命名Cover,也作为预制件;
添加一个测试按钮TestButton;
差不多了,大概结构如下:
创建卡牌、玩家信息
1.新建CardInfo类,主要不要继承默认的MonoBehaviour类,用来作为卡牌的实体类;
实现IComparable接口,后面手牌排序会用到。
public class CardInfo : IComparable
{
public string cardName; //卡牌图片名
public CardTypes cardType; //牌的类型
public int cardIndex; //牌在所在类型的索引1-13 public CardInfo(string cardName)
{
this.cardName = cardName;
var splits = cardName.Split('_'); switch (splits[])
{
case "":
cardType = CardTypes.Hearts;
cardIndex = int.Parse(splits[]);
break;
case "":
cardType = CardTypes.Spades;
cardIndex = int.Parse(splits[]);
break;
case "":
cardType = CardTypes.Diamonds;
cardIndex = int.Parse(splits[]);
break;
case "":
cardType = CardTypes.Clubs;
cardIndex = int.Parse(splits[]);
break;
case "joker":
cardType = CardTypes.Joker;
cardIndex = int.Parse(splits[]);
break;
default:
throw new Exception(string.Format("卡牌文件名{0}非法!", cardName));
}
} //卡牌大小比较
public int CompareTo(object obj)
{
CardInfo other = obj as CardInfo; if (other == null)
throw new Exception("比较对象类型非法!"); //如果当前是大小王
if (cardType == CardTypes.Joker)
{
//对方也是大小王
if (other.cardType == CardTypes.Joker)
{
return cardIndex.CompareTo(other.cardIndex);
}
//对方不是大小王
return ;
}
//如果是一般的牌
else
{
//对方是大小王
if (other.cardType == CardTypes.Joker)
{
return -;
}
//如果对方也是一般的牌
else
{
//计算牌力
var thisNewIndex = (cardIndex == || cardIndex == ) ? + cardIndex : cardIndex;
var otherNewIndex = (other.cardIndex == || other.cardIndex == ) ? + other.cardIndex : other.cardIndex; if (thisNewIndex == otherNewIndex)
{
return -cardType.CompareTo(other.cardType);
} return thisNewIndex.CompareTo(otherNewIndex);
}
}
} }
2.Card预制件上,添加Card脚本,主要保存对应CardInfo信息、选中状态,并加载卡牌图片;
public class Card : MonoBehaviour
{
private Image image; //牌的图片
private CardInfo cardInfo; //卡牌信息
private bool isSelected; //是否选中 void Awake()
{
image = GetComponent<Image>();
} /// <summary>
/// 初始化图片
/// </summary>
/// <param name="cardInfo"></param>
public void InitImage(CardInfo cardInfo)
{
this.cardInfo = cardInfo;
image.sprite = Resources.Load("Images/Cards/" + cardInfo.cardName, typeof(Sprite)) as Sprite;
}
/// <summary>
/// 设置选择状态
/// </summary>
public void SetSelectState()
{
if (isSelected)
{
iTween.MoveTo(gameObject, transform.position - Vector3.up * 10f, 1f);
}
else
{
iTween.MoveTo(gameObject, transform.position + Vector3.up * 10f, 1f);
} isSelected = !isSelected;
}
}
3.考虑玩家分为2种类型,先创建一个公共的基类,实现玩家公共的方法,比如增加一张卡牌、清空所有卡片、排序等;
public class Player : MonoBehaviour
{
protected List<CardInfo> cardInfos = new List<CardInfo>(); //个人所持卡牌 private Text cardCoutText; void Start()
{
cardCoutText = transform.Find("HeapPos/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();
} protected void Sort()
{
cardInfos.Sort();
cardInfos.Reverse();
}
}
4.添加第一种玩家(自身玩家)PlayerSelf,继承Player,并挂载到Player0对象上;
实现整理手牌的逻辑:发牌后,从中间的位置,根据大小依次将牌展开;
获取牌面点击事件,将牌选中或取消选中;
public class PlayerSelf : Player
{
public GameObject prefab; //预制件 private Transform originPos1; //牌的初始位置
private Transform originPos2; //牌的初始位置
private List<GameObject> cards=new List<GameObject>(); 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]); var targetPos = startPos + Vector3.right * offsetX * i;
card.transform.SetAsLastSibling();
//动画移动
iTween.MoveTo(card, targetPos, 2f); cards.Add(card);
}
} public void DestroyAllCards()
{
cards.ForEach(Destroy);
cards.Clear();
} /// <summary>
/// 点击卡牌处理
/// </summary>
/// <param name="data"></param>
public void CardClick(BaseEventData data)
{
//叫牌或出牌阶段才可以选牌
if (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();
}
}
}
5.添加另一种玩家(对手玩家)PlayerOther,继承Player,并挂载到Player1,Player2对象上;
暂时没有实现任何其他功能:
public class PlayerOther : Player
{ }
实现发牌逻辑
在Camera上添加卡牌管理脚本:CardManager
1.实现洗牌逻辑,这里用生成GUID随机性后排序,达到洗牌的目的;
2.记录当前发牌回合,每发一张牌,跳转给下一个玩家;
3.记录当前玩牌回合(将来可能用到),每玩一局,跳转下个玩家开始发牌;
4.发牌逻辑:
设置牌堆的显示,动画依次给每位玩家发一张卡牌,发完牌后,隐藏牌堆,并将玩家的卡牌排序并展示;
public class CardManager : MonoBehaviour
{
public float dealCardSpeed = ; //发牌速度
public Player[] Players; //玩家的集合 public GameObject coverPrefab; //背面排预制件
public Transform heapPos; //牌堆位置
public Transform[] playerHeapPos; //玩家牌堆位置 public static CardManager _instance; //单例
public CardManagerStates cardManagerState; private string[] cardNames; //所有牌集合
private int termStartIndex = ; //回合开始玩家索引
private int termCurrentIndex = ; //回合当前玩家索引
private List<GameObject> covers = new List<GameObject>(); //背面卡牌对象,发牌结束后销毁 void Awake()
{
_instance = this; cardNames = GetCardNames();
}
/// <summary>
/// 洗牌
/// </summary>
public void ShuffleCards()
{
//进入洗牌阶段
cardManagerState = CardManagerStates.ShuffleCards;
cardNames = cardNames.OrderBy(c => Guid.NewGuid()).ToArray();
}
/// <summary>
/// 发牌
/// </summary>
public IEnumerator DealCards()
{
//进入发牌阶段
cardManagerState = CardManagerStates.DealCards; //显示牌堆
heapPos.gameObject.SetActive(true);
playerHeapPos.ToList().ForEach(s => { s.gameObject.SetActive(true); }); foreach (var cardName in cardNames)
{
//给当前玩家发一张牌
Players[termCurrentIndex].AddCard(cardName); var cover = Instantiate(coverPrefab, heapPos.position, Quaternion.identity, heapPos.transform);
cover.GetComponent<RectTransform>().localScale = Vector3.one;
covers.Add(cover);
iTween.MoveTo(cover, playerHeapPos[termCurrentIndex].position, 0.3f); yield return new WaitForSeconds( / dealCardSpeed); //下一个需要发牌者
SetNextPlayer();
} //隐藏牌堆
heapPos.gameObject.SetActive(false);
playerHeapPos[].gameObject.SetActive(false); //显示玩家手牌
Players.ToList().ForEach(s =>
{
var player0 = s as PlayerSelf;
if (player0 != null)
{
player0.GenerateAllCards();
}
});
//动画结束,进入叫牌阶段
yield return new WaitForSeconds(2f);
covers.ForEach(Destroy);
cardManagerState = CardManagerStates.Bid;
}
/// <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>
/// <returns></returns> private void SetNextPlayer()
{
termCurrentIndex = (termCurrentIndex + ) % Players.Length;
}
/// <summary>
/// 切换开始回合玩家
/// </summary>
public void SetNextTerm()
{
termStartIndex = (termStartIndex + ) % 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;
} //for test
public void OnTestClick()
{
ClearCards();
ShuffleCards();
StartCoroutine(DealCards());
} }
总结
其实发牌后的动画,可以由override基类的方法,交由Player子类处理,不用CardManager集中管理,大家可以优化一下。
大体逻辑完成,我们验证下效果吧:
资源
Unity3D手机斗地主游戏开发实战(01)_发牌功能实现的更多相关文章
- Unity3D手机斗地主游戏开发实战(04)_出牌判断大小(已完结)
之前我们实现了叫地主.玩家和电脑自动出牌主要功能,但是还有个问题,出牌的时候,没有有效性检查和比较牌力大小.比如说,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9. 问题1:出牌检 ...
- Unity3D手机斗地主游戏开发实战(04)_出牌判断大小
之前我们实现了叫地主.玩家和电脑自动出牌主要功能,但是还有个问题,出牌的时候,没有有效性检查和比较牌力大小.比如说,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9. 问题1:出牌检 ...
- Unity3D手机斗地主游戏开发实战(03)_地主牌显示和出牌逻辑(不定期更新中~~~)
Hi,之前有同学说要我把源码发出来,那我就把半成品源码的链接放在每篇文件的最后,有兴趣的话可以查阅参考,有问题可以跟我私信,也可以关注我的个人公众号,互相交流嘛.当然,代码也是在不断的持续改进中~ 上 ...
- Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现(不定期更新中~~~)
目录 Unity3D手机斗地主游戏开发实战(01)_发牌功能实现 Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现 一.大体思路 前面我们实现了点击开始游戏按钮,系统依次给玩家发牌的逻辑和 ...
- Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现
大体思路 前面我们实现了点击开始游戏按钮,系统依次给玩家发牌的逻辑和动画,并展示当前的手牌.这期我们继续实现接下来的功能--叫地主. 1.首先这两天,学习了DOTween,这是一个强大的Unity动画 ...
- 【转】 [Unity3D]手机3D游戏开发:场景切换与数据存储(PlayerPrefs 类的介绍与使用)
http://blog.csdn.net/pleasecallmewhy/article/details/8543181 在Unity中的数据存储和iOS中字典的存储基本相同,是通过关键字实现数据存储 ...
- maven实战(01)_搭建开发环境
一 下载maven 在maven官网上可下载maven:http://maven.apache.org/download.cgi 下载好后,解压.我的解压到了:D:\maven\apache-mave ...
- (转)火溶CEO王伟峰:Unity3D手机网游开发
今天看到这篇文章,感觉很不错,尤其是那句“Unity3D的坑我觉得最严重的坑就是没有懂3D的程序员,把Unity当成Office用”. 转自http://blog.csdn.net/wwwang891 ...
- 手机3D游戏开发:自定义Joystick的相关设置和脚本源码
Joystick在手游开发中非常常见,也就是在手机屏幕上的虚拟操纵杆,但是Unity3D自带的Joystick贴图比较原始,所以经常有使用自定义贴图的需求. 下面就来演示一下如何实现自定义JoySti ...
随机推荐
- 06jQuery-03-选择器查找和过滤
1.查找 find().parent().prev().next() 通常情况下选择器可以直接定位到我们想要的元素,但是,当我们拿到一个jQuery对象后,还可以以这个对象为基准,进行查找和过滤. 最 ...
- JSP页面格式化数字或时间 基于jstl的
jsp页面格式化数字或时间 转载自: http://blog.csdn.net/hakunamatata2008/archive/2011/01/21/6156203.aspx Tags fmt:re ...
- 一篇搞定Python正则表达式
1. 正则表达式语法 1.1 字符与字符类 1 特殊字符:\.^$?+*{}[]()| 以上特殊字符要想使用字面值,必须使用\进行转义 2 字符类 1. 包含在[]中的一个或者多个字符被称为字符 ...
- OpenStack Ocata 超详细搭建文档
前言 搭建前必须看我本文档搭建的是分布式O版openstack(controller+ N compute + 1 cinder)的文档.openstack版本为Ocata.搭建的时候,请严格按照文档 ...
- 《Node.js在CLI下的工程化体系实践》成都OSC源创汇分享总结
背景: 随着开发团队规模不断发展壮大,在人员增加的同时也带来了协作成本的增加,业务项目越来越多,类型也各不相同.常见的类型有组件类.活动类.基于React+redux的业务项目.RN项目.Node.j ...
- 【JVM】Java中的JavaCore/HeapDump文件及其分析方法
产生时间 Java程序运行时,有时会产生JavaCore及HeapDump文件,它一般发生于Java程序遇到致命问题的情况下. 有时致命问题发生后,Java应用不会死掉,还能继续运行: 但有时致命问题 ...
- poj2337欧拉回路要求输出路径
Catenyms Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8368 Ac ...
- 在Ubuntu终端彻底删除软件
在Ubuntu终端彻底删除软件 1.删除软件 方法一.如果你知道要删除软件的具体名称,可以使用 sudo apt-get remove --purge 软件名称 sudo apt-get autore ...
- windows下PHP中Fatal error Call to undefined function curl_init()的解决方法
参考官方解决方法:http://nz.php.net/manual/en/curl.installation.php 1.php安装目录下的ext文件夹下面是否有php_curl.dll文件,然后右键 ...
- asp.net 的发布与执行
asp.net的工程文件在测试完成后就是发布(部署)和执行. 发布 本地发布的例子 1,菜单项 生成--发布 2,如果是第一次发布,点击自定义,设置配置文件的名称 3,发布方法--文件系统,设置本地 ...