Unity中接收服务器消息并广播事件的实现


最近接触的项目,是一个棋牌游戏,棋牌游戏需要在前端进行一些操作,然后向服务器发送数据包,在服务器接收到客户端的数据后,会在服务端进行一系列的判断之后,然后发送给客户端一个返回数据,客户端接收到这个返回数据后,需要作出一系列的响应。那么,就针对于这一整个服务器<--->客户端的通讯过程,看看是如何来实现的。

1. 报文(消息结构体)的商定。

 客户端向服务器发送消息,客户端接收服务器的消息,都是一系列的数据,那么这些数据到底是代表了什么,应该以什么样的方式解读?这些东西,在刚开始开发的时候,客户端与服务器就需要定好相关的内容,这个东西呢,也就是消息报文啦!在消息报文中,会定义一些常用的东西:

  a、游戏状态

  b、游戏通讯协议定义

  c、游戏具体的数据包结构

 1     /// <summary>
 2     /// 游戏状态定义
 3     /// </summary>
 4     public enum GameState : byte
 5     {
 6         GS_WAIT_SETGAME = 0,
 7 
 8         GS_WAIT_AGREE = 1,
 9 
10         GS_NOTE_STATE = 20,
11     }
12 
13     /// <summary>
14     /// 游戏通讯协议定义
15     /// </summary>
16     public struct ProtocolID
17     {
18         public const int S_C_IS_SUPER_USER = 78;//超端用户消息
19         public const int C_S_SUPER_SET = 79;//超端设置
20         public const int S_C_SUPER_SET_RESULT = 80;//超端设置结果
21     }
22 
23  /// <summary>
24     ///游戏基础数据
25     /// </summary>
26     [System.Serializable]
27     [StructLayout(LayoutKind.Sequential, Pack = ClientPlat.PlatFormConfig.HallPack)]
28     public struct GameStationBase
29     {
30         /// <summary>
31         ///上庄列表
32         /// </summary>
33         [MarshalAs(UnmanagedType.ByValArray, SizeConst = GameConst.PlayerCount)]
34         public byte[] byZhuangList;
35         /// <summary>
36         /// 路子信息
37         /// </summary>
38         [MarshalAs(UnmanagedType.ByValArray, SizeConst = GameConst.GameMaxCount)]
39         public LuziData[] TLuziData;
40 
41         public int iXiaZhuTime;        /// 下注时间                    
42         public int iKaiPaiTime;        /// 开牌时间    
43         public int iFreeTime;          /// 空闲时间    
44         public int iShowWinTime;       /// 显示中奖时间
45         public int iNtStation;         //庄家位置
46         public int iNtPlayCount;       //庄家坐庄次数
47         public long i64NtMoney;         //庄家金币
48         public long i64NtWinMoney;      //庄家输赢情况
49         public long i64UserWin;         //个人输赢
50         public long i64MyMoney;         //个人金币数 -从服务端发送过去
51         public long i64UserMaxNote;         ///玩家最大下注数
52         public long i64ShangZhuangLimit; /// 上庄需要的最少金币
53 
54     }

说明:消息报文需要严格遵循前后关系,如果客户端与服务器消息报文顺序不一致,那么会导致数据混乱。

 

 

2. 消息报文确定以后,需要根据消息报文脚本中确定的ProtocolID来定义对应的事件,而且这些事件都会实现某一个接口,方便事件中心触发。

 1  public class GameStationBase_Event : IEvent
 2     {
 3         public Enum EventType
 4         {
 5             get
 6             {
 7                 return DataEventID.GameStationBase;
 8             }
 9         }
10 
11         public GameStationBase data;
12 
13         public GameStationBase_Event(GameStationBase _data)
14         {
15             data = _data;
16         }
17 
18         public virtual string ToSring()
19         {
20             string msg = string.Format("EventType:{0}", DataEventID.GameStationBase.ToString());
21             return msg;
22         }
23 
24         public void DestroySelf()
25         {
26         }
27     }

  a、从上面的代码可以看到,这个GameStationBase_Event 类实现了接口IEvent,并且实现了它的DestroySelf方法,这样的好处后面点儿会讲到。

  b、GameStationBase_Event 有一个public的枚举类型EventType 变量,并且直接返回的就是 消息报文中的“游戏通讯协议定义”,这样一来,GameStationBase_Event 就与“游戏通讯协议定义”中的每一个枚举一一对应。

  c、GameStationBase_Event还有一个 GameStationBase 类型的变量data,同时还有一个有参的构造器,直接初始化变量data,这样一来,GameStationBase_Event 就与“游戏具体的数据包结构”一一对应了。

  废话了这么多,其实也就是一句话,一个 事件类,对应着一个游戏通讯协议与一个消息结构体,并且实现了一个通用的接口IEvent。

 

3、既然消息该如何发送,该如何接受解析已经定义好了,那么接下来是不是就该发送接受消息了呢?没错,接下来,就开始定义我们向服务器发送消息时该做些什么,接收到服务器的消息时,我们又该做些什么。

  接下来,顶一个消息处理脚本,叫做 GameLogicTable,至于为什么叫这个,我也不知道 - -!

  

  d、其它的东西,比如来个静态的实例啊什么的,见代码:

 1  public class GameLogicTable : GameLogicBase
 2     {
 3 
 4        //声明一个静态实例
 5         private static GameLogicTable instance;
 6       //公有的静态实例获取方法
 7         public static GameLogicTable Instance
 8         {
 9             get
10             {
11                 if (instance == null)
12                 {
13                     instance = new GameLogicTable(0, 180);
14                 }
15                 return instance;
16             }
17 
18         }
19 
20 /// <summary>
21         /// 通讯协议(服务器==》客户端)
22         /// </summary>
23         /// <param name="data"></param>
24         /// <param name="bAssistantID"></param>
25         private void ProcessGameLogic(byte[] data, uint bAssistantID)
26         {
27             //Debug.LogError("========数据接受处理=======>>" + bAssistantID.ToString());
28             switch (bAssistantID)
29             {
30                 case ProtocolID.S_C_APPLY_ZHUANG_RESULT:
31                     {
32                         S_C_ApplyZhuangResult ndata = new S_C_ApplyZhuangResult();
33                         ndata = (S_C_ApplyZhuangResult)HNNetToolKit.Instance.BytesToStruct(data, ndata.GetType());
34                         ApplyZhuangResult_Event evt = new ApplyZhuangResult_Event(ndata);
35                         EventCenter.Instance.TriggerEvent(evt);
36                     }
37                     break;
38                 default:
39                 break;
40                }
41          }
42 /// <summary>
43         /// 初始化游戏
44         /// </summary>
45         /// <param name="data">Data.</param>
46         public void InitGameState(byte[] data)
47         {
48 
49             GameState gs = (GameState)base.GameStatus;
50             //Debug.LogError("=========数据接收=========>>"+ gs.ToString());
51 
52 
53             switch (gs)
54             {
55                 case GameState.GS_WAIT_SETGAME://无庄等待状态
56                 case GameState.GS_WAIT_AGREE:
57                 case GameState.GS_WAIT_NEXT:
58                     {
59                         GameStationBase pState = new GameStationBase();
60                         if (!HNNetToolKit.Instance.AssertSize(data.Length, pState.GetType())) return;
61                         pState = (GameStationBase)HNNetToolKit.Instance.BytesToStruct(data, pState.GetType());
62                         GameStationBase_Event evt = new GameStationBase_Event(pState);
63                         EventCenter.Instance.TriggerEvent(evt);  
64                     }
65                     break;
66              }
67         }        
68 /// <summary>
69         /// 通讯协议(客户端==》服务端)
70         /// </summary>
71         #region
72 
73         //超端设置
74         public void SuperSet(SuperUserSetData node)
75         {
76             SendData(ProtocolID.C_S_SUPER_SET,node);
77         }

  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、其它一些逻辑处理,例如初始化啦、清空啦,删除事件注册啦等等,这里就不一一介绍了。

 1 public class EventCenter : HNBehaviourSingleton<EventCenter>, IEventCenter
 2     {
 3         /// <summary>
 4         /// 保存事件监听处理列表,对应说明a
 5         /// </summary>
 6         private Dictionary<Enum, IEventHandlerManger> eventHandlers = new Dictionary<Enum, IEventHandlerManger>();
 7 
 8  /// <summary>
 9         /// 事件队列,对应说明b
10         /// </summary>
11         private HNSafeQueue<IEvent> eventQueue = new HNSafeQueue<IEvent>();
12 
13 //对应说明f
14  public void Update()
15         {
16             BoardCastEvent();
17         }
18 
19 //广播事件方法,对应说明中的e
20    public void BoardCastEvent()
21         {
22             if ( eventQueue.Count < 1 )
23             {
24                 return;
25             }
26             IEvent evt = eventQueue.Dequeue();
27             BoardCastEvent(evt);
28         }
29 //触发事件方法,对应说明中的d
30 public void TriggerEvent(IEvent evt)
31         {
32             string msg = string.Format("TriggerEvent :{0}", evt.ToSring());
33             HNLogger.Log(msg);
34             this.eventQueue.Enqueue(evt);
35         }
36 
37 //注册事件监听方法
38         public bool AddEventListener(Enum eventType, EventHandle eventHandle, bool isPermanently = false)
39         {
40             bool flag = false;
41             if ( !this.eventHandlers.ContainsKey(eventType) )
42             {
43                 this.eventHandlers[eventType] = new EventReceiver();
44              }
45              flag = this.eventHandlers[eventType].AddHandler(eventHandle);
46             }
47             return flag;
48         }

 

5.到了这里,其实就可以知道为什么在 定义事件类的时候,需要让事件类来实现IEvent接口了,因为当接收到服务器消息时,会通过EventCenter的 TriggerEvent(IEvent eve)方法将接受到的数据转化为对应的事件类,然后加入到eventQueue队列中,而加入

eventQueue队列,则需要是IEvent接口类型的方可,所以定义事件类型的时候必须要实现IEvent接口方可。

最后附上一张关系图:

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM