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