前言

看了看这个代码感觉没什么可讲的,没有什么独特的Unity技巧,都是斗地主的业务逻辑,这一片简单分析一下吧。

不过这个Demo包含前后端,可以了解下前后端的职责和如何交互的。总结一下就是前端负责界面展示,后端负责数据处理。

客户端各模块实现

使用上一章讲过的框架,分成了好几个模块,分别是:UI模块、场景模块、网络模块、角色模块、音效模块。

接下来由易到难看一下这几个模块。

音效模块

这个模块实际上没用到,有一个EffectAudio 虽然在音效模块目录下,但却属于UI模块,核心代码就下面这两行,对于声音资源并播放。

    /// <summary>/// 播放音效/// </summary>/// <param name="name">文件路径+名称</param>private void PlayChatEffectAudio(string name){audioSource.clip = Resources.Load<AudioClip>("Sound/" + name);audioSource.Play();}

场景模块

这个模块主要负责场景切换,可以通过onSceneLoadAction 设置一个场景加载完成后的回调,其他就没什么了。

网络模块

网络模块实际是比较复杂的,但底层写好以后不会怎么变动,大多也是写业务逻辑。

这个模块主要做三件事:连接服务器,发数据和收数据。

连接服务器

通过ClientPeer 来连接服务器,一个此对象就是一条连接。实现了接收数据和发送数据的功能。

其中收到的数据会存在一个队列socketMsgQueue里。

发数据

作者自定义了一种网络通信格式SocketMsg,并将其做成了dll库,斗地主\Assets\Plugin\netstandard2.0\Components.dll,做成dll,与因为这个数据结构在服务端定义,做成dll调用client 来发送数据。

//
// 摘要:
//     网络消息
public class SocketMsg
{public SocketMsg();public SocketMsg(Enum state);public SocketMsg(MsgType opCode, Enum subCode, Enum state, object value = null);//// 摘要://     操作码public MsgType OpCode { get; set; }//// 摘要://     子操作public Enum SubCode { get; set; }//// 摘要://     参数public object value { get; set; }//// 摘要://     状态public Enum State { get; set; }
}
收数据

网络模块继承自ManagerBase,也就继承了MonoBehaviour

它的Update() 一直在读取client的消息队列socketMsgQueue,有消息就会处理。

根据不同的操作码,也就是自定义网络消息SocketMsg中的OpCode 来进行不同的处理。

这里也是主要写业务逻辑的地方,定义消息和对应的处理方法。

现在有用户的登录注册、信息查看、匹配、聊天、打牌功能。

这里只看打牌消息的处理,根据SubCode 来确定具体的业务,这里一共有三个功能,如下所示

    public override void OnReceive(SocketMsg msg){var code = (FightCode)msg.SubCode;switch (code){case FightCode.Get_Card_Result:       // 获得卡牌GetCard(msg);break;case FightCode.Turn_Grab_Bro:     // 轮换抢地主TurnGrabBro(msg);break;case FightCode.Grab_Landlord_Bro:    // 抢地主成功GrabLandlordBro(msg); break;default:break;}}

轮换抢地主逻辑如下

   /// <summary>/// 是否第一个玩家抢地主,而不是别的玩家不叫而到他/// </summary>private bool isFirst = true;/// <summary>/// 转换抢地主/// </summary>/// <param name="msg"></param>private void TurnGrabBro(SocketMsg msg){if (isFirst == true)    {isFirst = false;}else    // 如果自己不是第一个的话,播放“不要”声效{Dispatch(AreaCode.UI, UIEvent.EffectAudio, "Fight/Woman_NoOrder");}var userId = (int)msg.value;if (userId == Data.GameData.UserCharacterDto.Id)    // 轮到谁选择了,把他的按钮调亮/或显示出抢地主按钮{Dispatch(AreaCode.UI, UIEvent.Show_Grab_Button, true);}}

实在是没什么分析的,自己都能看懂,直接看发牌操作,这里调用角色模块设置了三个玩家的卡牌,在本地实际上只设置了自己的卡牌,其他两个玩家的卡牌信息没有同步回来。具体看看角色模块的处理就知道了。

    /// <summary>/// 获取卡牌/// </summary>/// <param name="msg"></param>private void GetCard(SocketMsg msg){//设置玩家卡牌Dispatch(AreaCode.CHARACTER, CharacterEvent.Init_MyCard, msg.value);Dispatch(AreaCode.CHARACTER, CharacterEvent.Init_LeftCard, null);Dispatch(AreaCode.CHARACTER, CharacterEvent.Init_RightCard, null);//设置倍数Dispatch(AreaCode.UI, UIEvent.Change_Mutiple, 1);}

角色模块

这里的角色管理,就是管理自己手中的牌。接上所述,先看给其他两个玩家发牌是如何处理的。

给左右玩家发牌

这两个分为左右,实际是一样的,这里只看左边玩家,也就是LeftPlayerCtrl.cs

发牌最后调到了这个方法,这是个协程,每0.1s发一张牌,实现动态发牌效果,一共17张,都用的同一个资源Card/OtherCard,这个资源是卡牌的背面,如下图,也就是说,给左右两个玩家发牌只是做了这个发牌动作,实际没有数据。

    /// <summary>/// 协程延时一秒/// </summary>/// <returns></returns>private IEnumerator InitCardList(){GameObject cardPrefab = Resources.Load<GameObject>("Card/OtherCard");for (int i = 0; i < 17; i++){CreateGo(cardPrefab, i);yield return new WaitForSeconds(0.1f);}}/// <summary>/// 创建卡牌/// </summary>/// <param name="cardPrefab"></param>/// <param name="index"></param>private void CreateGo(GameObject cardPrefab, int index){GameObject cardGo = Instantiate(cardPrefab, cardParent);cardGo.transform.localPosition = new Vector2((0.15f * index), 0);cardGo.GetComponent<SpriteRenderer>().sortingOrder = index;}

给自己发牌

给自己发牌和给左边玩家发牌类似,只不过是有数据的。

这里只贴有区别的部分,可以看到创建卡牌的时候还新建了一个CardCtrl 结构,这个对象用来控制具体的一张牌,通过它的Init 方法初始化了这个牌的信息。

    /// <summary>/// 创建卡牌/// </summary>/// <param name="card"></param>/// <param name="index"></param>private void CreateGo(GameObject cardPrefab, CardDto card, int index){GameObject cardGo = Instantiate(cardPrefab, cardParent);cardGo.name = card.Name;cardGo.transform.localPosition = new Vector2((0.25f * index), 0);CardCtrl cardCtrl = cardGo.GetComponent<CardCtrl>();cardCtrl.Init(card, index, true);//缓存本地cardCtrlsList.Add(cardCtrl);}

通过CardCtrl 初始化具体的牌信息,其中根据牌名字替换了精灵,替换为对应的图片资源。

    /// <summary>/// 初始化/// </summary>/// <param name="cardDto">卡牌信息</param>/// <param name="index">索引</param>/// <param name="isMine">是否自己的卡牌</param>public void Init(CardDto cardDto, int index, bool isMine){this.cardDto = cardDto;this.isMine = isMine;if (isSelect){isSelect = false;transform.localPosition -= new Vector3(0, 0.3f, 0);}string resPath = string.Empty;if (isMine){resPath = "Poker/" + cardDto.Name;}else{resPath = "Poker/CardBack";}spriteRenderer = GetComponent<SpriteRenderer>();spriteRenderer.sortingOrder = index++;spriteRenderer.sprite = Resources.Load<Sprite>(resPath);}

角色模块实现的功能就这么多了,,,没错就一个发牌功能,我也是才发现,本来想写出牌的相关功能,结果一看竟然没写。不过毕竟是Demo,知道了一个斗地主游戏大致怎么开发的就行了。

UI模块

这个模块不具体分析了,都是些琐碎的东西,理解了上一篇讲的框架后,自己都能看懂。

好吧,到此为止,本篇似乎没分析出啥干货来,就看到一个发牌逻辑,还是客户端的,真正的发牌逻辑在服务端。

那么还真是巧了,这个项目刚好有服务端代码,这里就把服务端代码也分析一下吧。

服务端

看完这个服务端解决了我的一些疑惑,为什么上面用到的那些SocketMsg 等结构要做成dll,原来定义源码在服务端,和客户端通用。

服务端框架代码就不多说了,无非是连接与消息收发。

功能逻辑还是比较多的,这里只说一下出牌和发牌逻辑。

出牌

直接看代码吧

     /// <summary>/// 发牌/// </summary>private void Deal(ClientPeer client, DealDto dto){SingleExecute.Instance.Execute(() =>{if (UserCache.IsOnline(client) == false){socketMsg.State = null;return;}int userId = UserCache.GetClientUserId(client);FightRoom room = FightCache.GetRoomByUId(userId);//玩家出牌、玩家掉线if (room.LeaveUIdList.Contains(userId)){Turn(room);}bool canDeal = room.DeadCard(dto.Type, dto.Weight, dto.Length, userId, dto.SelectCardList);if (canDeal == false){socketMsg.State = FightCode.必须大于上次一次出牌;socketMsg.SubCode = FightCode.Deal_Result;client.Send(socketMsg);return;}else{//返回客户端出牌成功socketMsg.State = FightCode.Success;socketMsg.SubCode = FightCode.Deal_Result;client.Send(socketMsg);//广播出牌结果socketMsg.value = dto;BroCast(room, socketMsg, client);//检查剩余手牌List<CardDto> remainCardList = room.GetPlayerModel(userId).CardList;if (remainCardList.Count == 0){//游戏结束GameOver(userId, room);}else{Turn(room);}}});}

根据选择的卡牌列表判断能否出牌,如果不能,就返回错误提示。

如果能出牌,给本客户端返回出牌成功,并给房间内用户广播这个用户的卡牌列表,让房间内的客户端都更新展示效果。如果牌出完了就代表胜利了。

就这么多了,这里的难点主要是判断是否能出牌,即选择的牌是否符合规则,是否大于上一家的牌,感兴趣自己看DeadCard 是如何实现的。

发牌

首先要洗牌,就是创建54张牌放到一个队列里,然后每次随机从中取一张放到一个新队列里。发牌就每次从新队列首部取出一张牌。这就是本项目LibraryModel.cs 中创建牌、洗牌、发牌的过程。

在玩家初始化手牌和抢到地主的时候会进行发牌操作,就这。

就这,没啥说的了,其他功能没必要说了,如果看到这儿都看懂了,剩下的自己也都能看懂了。

【斗地主代码分析】(2)-斗地主逻辑-客户端与服务端相关推荐

  1. swagger生成对应的客户端、服务端代码

    根据yaml文件生成对应的客户端.服务端代码 前言 ​ 对于早期的webservice接口,我们可以根据wsdl文件生成对应的客户端和服务端代码.那么同样的针对于Restful风格的接口,也有同样的根 ...

  2. QTcpSocket客户端和服务端发送图片(或大文件)小Demo

    先看一下效果: 思路: 发图片.大文件与发短字符不大一样. 1.文件和图片通过TCP可能一次发不过去,可能要发很多次.所以我们在发送文件.数据.以及文字最好带上文件的大小. 2.图片转换成文件流的形式 ...

  3. 使用Netty实现客户端和服务端之间的双向通信

    欢迎阅读本篇文章 提示:本文只是提供部分核心代码,源码详见代码示例 使用Netty实现客户端和服务端之间的双向通信 前言 一.服务端 二.客户端 前言 在上个月的开发计划中,有一个系统控制喇叭播放的功 ...

  4. swagger 返回json字符串_[Swagger] Swagger Codegen 高效开发客户端对接服务端代码

    [Swagger] Swagger Codegen 高效开发客户端对接服务端代码 @TOC 手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博 ...

  5. 客户端从服务端下载文件的流程分析

    客户端从服务端下载文件的流程分析: 浏览器发送一个请求,请求访问服务器中的某个网页(如:down.php),该网页的代码如下. 服务器接受到该请求以后,马上运行该down.php文件 运行该文件的时候 ...

  6. java上传音频到服务器_Java 客户端向服务端上传mp3文件数据的实例代码

    客户端: package cn.itcast.uploadpicture.demo; import java.io.BufferedInputStream; import java.io.FileIn ...

  7. linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现

    1 TCP简介 tcp是一种基于流的应用层协议,其"可靠的数据传输"实现的原理就是,"拥塞控制"的滑动窗口机制,该机制包含的算法主要有"慢启动&quo ...

  8. NodeJS SSR服务端渲染:公共代码区分客户端和服务端

    SSR服务端渲染(英语:server side render)指一般情况下,一个web页面的数据渲染完全由客户端或者浏览器端来完成.先从服务器请求,然后到页面:再通过AJAX请求到页面数据并把相应的数 ...

  9. QT中使用C++ socket通信(了解socket通信、socket的三次握手和四次挥手、socket函数说明、客户端与服务端的代码实例)

    一.TCP/IP协议四个抽象层: 二.socket位置 socket就在应用程序的传输层和应用层之间,传输层的底一层的服务提供给socket抽象层,socket抽象层再提供给应用层. 三.socket ...

  10. Netty实战 IM即时通讯系统(十二)构建客户端与服务端pipeline

    Netty实战 IM即时通讯系统(十二)构建客户端与服务端pipeline 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向 ...

最新文章

  1. js编程思路--给网站定义一个全局的js对象,放到window对象中
  2. asp php时间格式,ASP_asp格式化日期时间格式的代码,' ====================================== - phpStudy...
  3. python批量处理csv_Python批量处理csv并保存过程代码解析
  4. Invalidate和postInvalidate
  5. Oracle 实例恢复时 前滚(roll forward) 后滚(roll back) 问题
  6. 台式计算机cpu多好,2019台式处理器排行榜_台式机处理器排行榜 前六强详细介绍...
  7. 黑马程序员——OC代码规范和编程风格
  8. 【Hack The Box】windows练习-- Conceal
  9. EAGLE转Protel文件
  10. 怎么修改打印机服务器权限,Win7怎么设置网络打印机管理权限?
  11. 网络表情NLP(一)︱颜文字表情实体识别、属性检测、新颜发现
  12. 万网域名是否注册批量查询工具
  13. 超详细的fiddler教程,从小白到精通(五)❤️
  14. Linux笔记——软件包管理
  15. 迎接混合云时代 IBM云计算发展大提速
  16. 演讲达人成长记作者1月26日在西单图书大厦做讲座
  17. 继电保护原理1.2-反时限过流保护
  18. 华为应用内支付验签失败,报错Signature length not correct
  19. 【ansys workbench】在ansys2020版本的mechanical中怎么施加约束或载荷?结构约束和载荷?fixed support
  20. 阿里云/腾讯云视频截帧

热门文章

  1. 20年以后的科技发展小短文计算机,20年后的我小学想象作文
  2. 秦灭六国《大秦帝国》书评
  3. QT 调用windows socket
  4. Oracle dba_users视图
  5. mq 的Publish/Subscribe 模式
  6. python作业——输入一个月份单词输出对应月份缩写
  7. wordpress企业主题安装
  8. QEMU 安装与使用
  9. 小麦苗的常用代码--常用命令(仅限自己使用)--下
  10. SQL Server 2008 中文版安装下载地址