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消息体的接收及消息的处理,这 ...
随机推荐
- 关于随机数 C++
void test() { srand();//这里设置了 说明又得从头开始循环一次了 //如果没有设置 它还是基于main函数里的srand(1) for(int i=;i<;i++) { c ...
- cocos2d-x_下载游戏引擎并创建第一个项目
我是一名小白. 下载并创建游戏项目 第一步:去官网下载cocos2d-x http://www.cocos.com/download 第二步:将安装包里边的 setup.py 拖进命令行点击回车键 , ...
- Swift Alamofire
转载:https://www.jianshu.com/p/07b1ec36a689最近AFNetworking的作者Matt Thompson 提出了一个新的类似AFNetworking的网络基础库, ...
- 041_go语言中的panic
代码演示: package main import "os" func main() { // panic("a problem") _, err := os. ...
- Nexus3 上传的文件在哪里
上传文件 ojdbc7.jar,上传步骤略. 服务器上默认的文件存放路径是: nexus/sonatype-work/nexus3/blobs/default/content/ 一堆文件夹,根据时间确 ...
- 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统 | 简单的分库分表设计
前言 项目涉及到了一些设计模式,如果你看的不是很明白,没有关系坚持下来,写完之后去思考去品,你就会有一种突拨开云雾的感觉,所以请不要在半途感觉自己看不懂选择放弃,如果我哪里写的详细,或者需要修正请联系 ...
- 反制面试官 | 14张原理图 | 再也不怕被问 volatile!
反制面试官 | 14张原理图 | 再也不怕被问 volatile! 悟空 爱学习的程序猿,自主开发了Java学习平台.PMP刷题小程序.目前主修Java.多线程.SpringBoot.SpringCl ...
- 在laravel中遇到并发的解决方案
1,在mysql中创建唯一索引,在代码中try catch mysql的1062错误 2.将存在并发的代码丢给队列异步处理.这种解决方案的问题是,接下来的代码不能依赖队列的处理结果 3.使用mysql ...
- Go Channel 详解
原文链接:Go Channel 详解 Channel类型 Channel类型的定义格式如下: ChannelType = ( "chan" | "chan" & ...
- 给你项目加个Mock吧
mockjs官网:http://mockjs.com/ 一.简介 1.什么是mock 拦截请求,生成随机数据. 2.mock的使用场景 当后端接口还未完成的时候,前端需要一些数据来写页面,此时就需要M ...