• 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接口方可。

    最后附上一张关系图:

  • 相关阅读:
    给函数中的形参设置默认值
    css-文字一行显示效果
    考试倒计时-通过开始时间字符串和间隔时间戳设置考试倒计时
    vue父向子传值,子组件无法及时更新父组件传过来的值的问题
    vue+elementUI在输入框中按回车键会刷新页面
    前端面试题记录
    Web是如何工作的:基本架构和原理
    JavaScript构造函数和原型概述
    js byte字节流和数字,字符串之间的转换,包含无符和有符之间的转换
    C#串口通讯,复制粘贴就可用,仅仅介绍怎样最快的搭建一个串口通讯,异常拦截等等需要自己加上
  • 原文地址:https://www.cnblogs.com/leiGameDesigner/p/6765232.html
Copyright © 2020-2023  润新知