Unity中接收服务器消息并广播事件的实现
最近接触的项目,是一个棋牌游戏,棋牌游戏需要在前端进行一些操作,然后向服务器发送数据包,在服务器接收到客户端的数据后,会在服务端进行一系列的判断之后,然后发送给客户端一个返回数据,客户端接收到这个返回数据后,需要作出一系列的响应。那么,就针对于这一整个服务器<--->客户端的通讯过程,看看是如何来实现的。
1. 报文(消息结构体)的商定。
客户端向服务器发送消息,客户端接收服务器的消息,都是一系列的数据,那么这些数据到底是代表了什么,应该以什么样的方式解读?这些东西,在刚开始开发的时候,客户端与服务器就需要定好相关的内容,这个东西呢,也就是消息报文啦!在消息报文中,会定义一些常用的东西:
a、游戏状态
b、游戏通讯协议定义
c、游戏具体的数据包结构
/// <summary>
/// 游戏状态定义
/// </summary>
public enum GameState : byte
{
GS_WAIT_SETGAME = , GS_WAIT_AGREE = , GS_NOTE_STATE = ,
} /// <summary>
/// 游戏通讯协议定义
/// </summary>
public struct ProtocolID
{
public const int S_C_IS_SUPER_USER = ;//超端用户消息
public const int C_S_SUPER_SET = ;//超端设置
public const int S_C_SUPER_SET_RESULT = ;//超端设置结果
} /// <summary>
///游戏基础数据
/// </summary>
[System.Serializable]
[StructLayout(LayoutKind.Sequential, Pack = ClientPlat.PlatFormConfig.HallPack)]
public struct GameStationBase
{
/// <summary>
///上庄列表
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = GameConst.PlayerCount)]
public byte[] byZhuangList;
/// <summary>
/// 路子信息
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = GameConst.GameMaxCount)]
public LuziData[] TLuziData; public int iXiaZhuTime; /// 下注时间
public int iKaiPaiTime; /// 开牌时间
public int iFreeTime; /// 空闲时间
public int iShowWinTime; /// 显示中奖时间
public int iNtStation; //庄家位置
public int iNtPlayCount; //庄家坐庄次数
public long i64NtMoney; //庄家金币
public long i64NtWinMoney; //庄家输赢情况
public long i64UserWin; //个人输赢
public long i64MyMoney; //个人金币数 -从服务端发送过去
public long i64UserMaxNote; ///玩家最大下注数
public long i64ShangZhuangLimit; /// 上庄需要的最少金币 }
说明:消息报文需要严格遵循前后关系,如果客户端与服务器消息报文顺序不一致,那么会导致数据混乱。
2. 消息报文确定以后,需要根据消息报文脚本中确定的ProtocolID来定义对应的事件,而且这些事件都会实现某一个接口,方便事件中心触发。
public class GameStationBase_Event : IEvent
{
public Enum EventType
{
get
{
return DataEventID.GameStationBase;
}
} public GameStationBase data; public GameStationBase_Event(GameStationBase _data)
{
data = _data;
} public virtual string ToSring()
{
string msg = string.Format("EventType:{0}", DataEventID.GameStationBase.ToString());
return msg;
} public void DestroySelf()
{
}
}
a、从上面的代码可以看到,这个GameStationBase_Event 类实现了接口IEvent,并且实现了它的DestroySelf方法,这样的好处后面点儿会讲到。
b、GameStationBase_Event 有一个public的枚举类型EventType 变量,并且直接返回的就是 消息报文中的“游戏通讯协议定义”,这样一来,GameStationBase_Event 就与“游戏通讯协议定义”中的每一个枚举一一对应。
c、GameStationBase_Event还有一个 GameStationBase 类型的变量data,同时还有一个有参的构造器,直接初始化变量data,这样一来,GameStationBase_Event 就与“游戏具体的数据包结构”一一对应了。
废话了这么多,其实也就是一句话,一个 事件类,对应着一个游戏通讯协议与一个消息结构体,并且实现了一个通用的接口IEvent。
3、既然消息该如何发送,该如何接受解析已经定义好了,那么接下来是不是就该发送接受消息了呢?没错,接下来,就开始定义我们向服务器发送消息时该做些什么,接收到服务器的消息时,我们又该做些什么。
接下来,顶一个消息处理脚本,叫做 GameLogicTable,至于为什么叫这个,我也不知道 - -!
d、其它的东西,比如来个静态的实例啊什么的,见代码:
public class GameLogicTable : GameLogicBase
{ //声明一个静态实例
private static GameLogicTable instance;
//公有的静态实例获取方法
public static GameLogicTable Instance
{
get
{
if (instance == null)
{
instance = new GameLogicTable(, );
}
return instance;
} } /// <summary>
/// 通讯协议(服务器==》客户端)
/// </summary>
/// <param name="data"></param>
/// <param name="bAssistantID"></param>
private void ProcessGameLogic(byte[] data, uint bAssistantID)
{
//Debug.LogError("========数据接受处理=======>>" + bAssistantID.ToString());
switch (bAssistantID)
{
case ProtocolID.S_C_APPLY_ZHUANG_RESULT:
{
S_C_ApplyZhuangResult ndata = new S_C_ApplyZhuangResult();
ndata = (S_C_ApplyZhuangResult)HNNetToolKit.Instance.BytesToStruct(data, ndata.GetType());
ApplyZhuangResult_Event evt = new ApplyZhuangResult_Event(ndata);
EventCenter.Instance.TriggerEvent(evt);
}
break;
default:
break;
}
}
/// <summary>
/// 初始化游戏
/// </summary>
/// <param name="data">Data.</param>
public void InitGameState(byte[] data)
{ GameState gs = (GameState)base.GameStatus;
//Debug.LogError("=========数据接收=========>>"+ gs.ToString()); switch (gs)
{
case GameState.GS_WAIT_SETGAME://无庄等待状态
case GameState.GS_WAIT_AGREE:
case GameState.GS_WAIT_NEXT:
{
GameStationBase pState = new GameStationBase();
if (!HNNetToolKit.Instance.AssertSize(data.Length, pState.GetType())) return;
pState = (GameStationBase)HNNetToolKit.Instance.BytesToStruct(data, pState.GetType());
GameStationBase_Event evt = new GameStationBase_Event(pState);
EventCenter.Instance.TriggerEvent(evt);
}
break;
}
}
/// <summary>
/// 通讯协议(客户端==》服务端)
/// </summary>
#region //超端设置
public void SuperSet(SuperUserSetData node)
{
SendData(ProtocolID.C_S_SUPER_SET,node);
}
a、客户端--->服务端:向客户端发送一个消息ID,然后附上对应结构的数据node。
b、服务端--->客户端:根据接收到的消息ID,来做出对应的处理:
1.新建一个对应消息ID的数据报文 ndata;
2.将接受到的消息data的内容赋值给ndata;
3.新建一个对应消息ID的 事件类,并且将ndata作为参数传递给构造器。
4.使用事件中心,触发事件。(为什么不在这里处理,还要添加事件呢?)
4. 现在虽然定义好了收到消息与发送消息的行为,那么现在还差一个东西,那就是接受到服务器消息时,触发事件的EventCenter,这个EventCenter的作用很简单,就是让需要监听服务器消息的地方可以注册监听,然后当服务器发送对应消息时,就触发事件即可。那么这个EventCenter是如何设计的呢?
a、首先,增加一个Dictionary<enum,IEventHandlerManger> 类型的 eventHandlers,用来保存消息ID与对应 要触发的 方法。
b、然后,增加一个HNSafeQueue<IEvent> 类型eventQueue,用来保存需要触发的事件列表。
c、写一个public的Addlistener(enum,IEventHandlerManger)方法,用于事件的注册
d、写一个public的TriggerEvent(IEvent eve)方法,用于触发事件。在这个TriggerEvent方法中,其实就是将eve传入到eventQueue队列中,等待被触发。
e、写一个BroadcastEvent()方法,该方法就是将eventQueue队列中的eve事件一个个取出,然后调用封装好的事件触发方法触发事件回调。
e、在该脚本的Updata() 方法中,一直调用BroadcastEvent()方法。
f、其它一些逻辑处理,例如初始化啦、清空啦,删除事件注册啦等等,这里就不一一介绍了。
public class EventCenter : HNBehaviourSingleton<EventCenter>, IEventCenter
{
/// <summary>
/// 保存事件监听处理列表,对应说明a
/// </summary>
private Dictionary<Enum, IEventHandlerManger> eventHandlers = new Dictionary<Enum, IEventHandlerManger>(); /// <summary>
/// 事件队列,对应说明b
/// </summary>
private HNSafeQueue<IEvent> eventQueue = new HNSafeQueue<IEvent>(); //对应说明f
public void Update()
{
BoardCastEvent();
} //广播事件方法,对应说明中的e
public void BoardCastEvent()
{
if ( eventQueue.Count < )
{
return;
}
IEvent evt = eventQueue.Dequeue();
BoardCastEvent(evt);
}
//触发事件方法,对应说明中的d
public void TriggerEvent(IEvent evt)
{
string msg = string.Format("TriggerEvent :{0}", evt.ToSring());
HNLogger.Log(msg);
this.eventQueue.Enqueue(evt);
} //注册事件监听方法
public bool AddEventListener(Enum eventType, EventHandle eventHandle, bool isPermanently = false)
{
bool flag = false;
if ( !this.eventHandlers.ContainsKey(eventType) )
{
this.eventHandlers[eventType] = new EventReceiver();
}
flag = this.eventHandlers[eventType].AddHandler(eventHandle);
}
return flag;
}
5.到了这里,其实就可以知道为什么在 定义事件类的时候,需要让事件类来实现IEvent接口了,因为当接收到服务器消息时,会通过EventCenter的 TriggerEvent(IEvent eve)方法将接受到的数据转化为对应的事件类,然后加入到eventQueue队列中,而加入
eventQueue队列,则需要是IEvent接口类型的方可,所以定义事件类型的时候必须要实现IEvent接口方可。
最后附上一张关系图:
Unity中接收服务器消息并广播事件的实现的更多相关文章
- Unity中使用C#实现UDP广播
没有系统的学习过网络,想做联机游戏还真是费劲,想做在局域网内实现自动搜索服务器的功能,然后就想到了使用UDP进行广播,把服务器的信息广播给每一个玩家. Socket udpSocket = new S ...
- 关于Unity中从服务器下载资源压缩包AssetBundle的步骤
AssetBundle 1: 在Unity中,能为用户存储资源的一种压缩格式的打包集合,他可以存任意一种Unity引擎可以识别的资源: 模型,音频,纹理图,动画, 开发者自定义的二进制文件; 2: 这 ...
- C#使用ProtocolBuffer(ProtoBuf)进行Unity中的Socket通信
首先来说一下本文中例子所要实现的功能: 基于ProtoBuf序列化对象 使用Socket实现时时通信 数据包的编码和解码 下面来看具体的步骤: 一.Unity中使用ProtoBuf 导入DLL到Uni ...
- Unity中嵌入网页插件Embedded Browser2.1.0
背景 最近刚换了工作,新公司不是做手游的,一开始有点抵触,总觉得不是做游戏自己就是跨行了,认为自己不对口,但是慢慢发现在这可以学的东西面很广,所以感觉又到了打怪升级的时候了,老子就在这进阶了. 一进公 ...
- ZeroMQ接口函数之 :zmq_recvmsg – 从一个socket上接收一个消息帧
ZeroMQ 官方地址 :http://api.zeromq.org/4-1:zmq-recvmsg zmq_recvmsg(3) ØMQ Manual - ØMQ/4.1.0 Nam ...
- 【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher
一.简介 最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少.但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子.看小说,要么就是 ...
- 关于Unity中NGUI的Checkbox复选框、Slider滑动条和Button的6种触发回调事件的方式
Checkbox复选框 1.创建一个NGUI背景Sprite1节点 2.打开NGUI---->Open---->Prefab Toolbar---->选择一个复选框节点,拖拽到背景节 ...
- 关于Unity中的Input输入事件
截获鼠标,键盘的消息 监听事件我们都是在Update里面监听的. Unity的虚拟轴打开:Edit-->Project Settings-->Input,打开的各个Name就是双引号里面要 ...
- Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理
在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...
随机推荐
- 新手阅读 Nebula Graph 源码的姿势
摘要:在本文中,我们将通过数据流快速学习 Nebula Graph,以用户在客户端输入一条 nGQL 语句 SHOW SPACES 为例,使用 GDB 追踪语句输入时 Nebula Graph 是怎么 ...
- Python爬虫教程:验证码的爬取和识别详解
今天要给大家介绍的是验证码的爬取和识别,不过只涉及到最简单的图形验证码,也是现在比较常见的一种类型. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻 ...
- Java 设置、删除、获取Word文档背景(基于Spire.Cloud.SDK for Java)
本文介绍使用Spire.Cloud.SDK for Java 提供的BackgroundApi接口来操作Word文档背景的方法,可设置背景,包括设置颜色背景setBackgroundColor().图 ...
- 【模式识别与机器学习】——4.3离散K-L变换
全称:Karhunen-Loeve变换(卡洛南-洛伊变换) 前面讨论的特征选择是在一定准则下,从n个特征中选出k个来反映原有模式. 这种简单删掉某n-k个特征的做法并不十分理想,因为一般来说,原来的n ...
- Flutter 容器(6) - FractionallySizedBox
FractionallySizedBox 用法与SizedBox类似,只不过FractionallySizedBox的宽高是百分比大小,widthFactor,heightFactor参数就是相对于父 ...
- Vue CLI3 移动端适配 【px2rem 或 postcss-plugin-px2rem】
Vue CLI3 移动端适配 [px2rem 或 postcss-plugin-px2rem] 今天,我们使用Vue CLI3 做一个移动端适配 . 前言 首先确定你的项目是Vue CLI3版本以上的 ...
- 【ZJOI2012】灾难 - LCA+拓扑排序
题目描述 阿米巴是小强的好朋友. 阿米巴和小强在草原上捉蚂蚱.小强突然想,如果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难. 学过生物的阿米巴告 ...
- redis读写分离及可用性设计
Redis缓存架构设计 对于下面两个架构图,有如下想法: 1)redis主从复制模式,为了解决master读写压力,对master进行写操作,对slave进行读操作. 2)而在分片集群中,如果对部分分 ...
- Java引用类型之软引用(1)
Java使用SoftReference来表示软引用,软引用是用来描述一些“还有用但是非必须”的对象.对于软引用关联着的对象,在JVM应用即将发生内存溢出异常之前,将会把这些软引用关联的对象列进去回收对 ...
- 为什么 max() 应该写成 b < a ? a : b 呢?
在 < C++ Templates 2nd Edition >Chapter 1 中,作者将 max() 模板定义如下: template <typename T> T max ...