Working code
						commit
						6490c98849
					
				|  | @ -0,0 +1 @@ | |||
| .DS_Store | ||||
|  | @ -0,0 +1,8 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: c919269e61d904cf0964b56eb9b1b6ab | ||||
| folderAsset: yes | ||||
| DefaultImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,37 @@ | |||
| # GURU Dof kcp Client | ||||
| 
 | ||||
| ### VERSION 0.0.1 | ||||
| 
 | ||||
| ## 插件介绍 | ||||
| 
 | ||||
| 基于upm_guru_kcp库,服务与dof项目的客户端类封装 | ||||
| 
 | ||||
| 
 | ||||
| ## 安装和接入 | ||||
| 
 | ||||
| ### 插件引入 | ||||
| 
 | ||||
| - 本插件需要使用公司内部的Gitlab加载对应的repo, 详见 [Castbox内部git服务器Gitea使用指南](https://docs.google.com/document/d/1DiGPDD5Teu2QcYaBhqAcsqMbb3DJ5sdnTQMuvVzJSUk/edit#heading=h.w9tkkzwwyjxf) | ||||
| - 根据文档部署好本机配置后, 请在Unity内部配置如下参数 | ||||
|   - TBD | ||||
|   - TBD | ||||
|   - TBD | ||||
| 
 | ||||
| 
 | ||||
| ## 逻辑实现 | ||||
| 
 | ||||
| - TBD | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ## 问题答疑 | ||||
| 
 | ||||
| - Q | ||||
| > A | ||||
| 
 | ||||
| - Q | ||||
| > A | ||||
| 
 | ||||
| - Q | ||||
| > A | ||||
|  | @ -0,0 +1,7 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 177739947c6344e95a62c8671cf2e96c | ||||
| TextScriptImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,8 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 23b66401566ec4490b51f7d2375e1228 | ||||
| folderAsset: yes | ||||
| DefaultImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,509 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using UnityEngine; | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
| 
 | ||||
|     public class GameEventMgr | ||||
|     { | ||||
|         private static GameEventMgr _instance; | ||||
|         public static GameEventMgr Instance | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (_instance == null) | ||||
|                 { | ||||
|                     _instance = new GameEventMgr(); | ||||
|                 } | ||||
|                 return _instance; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void Init(bool is_debug_mode = false) | ||||
|         { | ||||
|             if (_instance == null) | ||||
|             { | ||||
|                 _instance = new GameEventMgr(); | ||||
|             } | ||||
| 
 | ||||
|             _isDebugMode = is_debug_mode; | ||||
| 
 | ||||
|             if(_isDebugMode) | ||||
|             { | ||||
|                 Debug.Log($"[GameEventMgr]Init: Running in debug mode"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static bool _isDebugMode = false; | ||||
| 
 | ||||
|         struct DelegateGameObjectBonding | ||||
|         { | ||||
|             public GameObject gameObject; | ||||
|             public Delegate listener; | ||||
| 
 | ||||
|             public DelegateGameObjectBonding(GameObject game_object, Delegate del) | ||||
|             { | ||||
|                 gameObject = game_object; | ||||
|                 listener = del; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private readonly Queue<List<Delegate>> _listenerListQueue = new (); | ||||
|         private readonly Queue<List<DelegateGameObjectBonding>> _listenerBondingListQueue = new (); | ||||
|         private readonly Dictionary<int, List<Delegate>> _eventTable = new (); | ||||
|         private readonly Dictionary<int, List<DelegateGameObjectBonding>> _eventObjTable = new (); | ||||
|         private readonly Dictionary<int, List<Delegate>> _pendingAddTable = new (); | ||||
|         private readonly Dictionary<int, List<DelegateGameObjectBonding>> _pendingObjAddTable = new (); | ||||
|         private readonly Dictionary<int, List<Delegate>> _pendingRemoveTable = new (); | ||||
|         private readonly Dictionary<int, List<DelegateGameObjectBonding>> _pendingObjRemoveTable = new (); | ||||
| 
 | ||||
|         // Using raising event list to prevent infinite loop call or changing delegate list | ||||
|         private readonly List<int> _raisingEventIds = new (); | ||||
| 
 | ||||
|         private GameEventMgr() { } | ||||
| 
 | ||||
|         public void AddListener(int event_id, Action new_listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoAddListener(event_id, new_listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void AddListener<T>(int event_id, Action<T> new_listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoAddListener(event_id, new_listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void AddListener<T1, T2>(int event_id, Action<T1, T2> new_listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoAddListener(event_id, new_listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void AddListener<T1, T2, T3>(int event_id, Action<T1, T2, T3> new_listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoAddListener(event_id, new_listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void RemoveListener(int event_id, Action listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoRemoveListener(event_id, listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void RemoveListener<T>(int event_id, Action<T> listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoRemoveListener(event_id, listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void RemoveListener<T1, T2>(int event_id, Action<T1, T2> listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoRemoveListener(event_id, listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void RemoveListener<T1, T2, T3>(int event_id, Action<T1, T2, T3> listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             DoRemoveListener(event_id, listener, life_cycle_obj); | ||||
|         } | ||||
| 
 | ||||
|         public void Raise(int event_id) | ||||
|         { | ||||
|             DoRaise(event_id); | ||||
|         } | ||||
| 
 | ||||
|         public void Raise<T>(int event_id, T arg1) | ||||
|         { | ||||
|             DoRaise(event_id, arg1); | ||||
|         } | ||||
| 
 | ||||
|         public void Raise<T1, T2>(int event_id, T1 arg1, T2 arg2) | ||||
|         { | ||||
|             DoRaise(event_id, arg1, arg2); | ||||
|         } | ||||
| 
 | ||||
|         public void Raise<T1, T2, T3>(int event_id, T1 arg1, T2 arg2, T3 arg3) | ||||
|         { | ||||
|             DoRaise(event_id, arg1, arg2, arg3); | ||||
|         } | ||||
| 
 | ||||
|         public void ClearNullGameObjectListeners() | ||||
|         { | ||||
|             var etor = _eventObjTable.GetEnumerator(); | ||||
| 
 | ||||
|             while (etor.MoveNext()) | ||||
|             { | ||||
|                 var listeners = etor.Current.Value; | ||||
| 
 | ||||
|                 for (int i = 0; i < listeners.Count; i++) | ||||
|                 { | ||||
|                     if(listeners[i].gameObject == null) | ||||
|                     { | ||||
|                         listeners.RemoveAt(i--); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private List<Delegate> SpawnDelegateList() | ||||
|         { | ||||
|             if(_listenerListQueue.Count > 0) | ||||
|             { | ||||
|                 return _listenerListQueue.Dequeue(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return new List<Delegate>(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void DespawnDelegateList(List<Delegate> list) | ||||
|         { | ||||
|             if (list == null) return; | ||||
| 
 | ||||
|             list.Clear(); | ||||
|             _listenerListQueue.Enqueue(list); | ||||
|         } | ||||
| 
 | ||||
|         private List<DelegateGameObjectBonding> SpawnDelegateBondingList() | ||||
|         { | ||||
|             if (_listenerBondingListQueue.Count > 0) | ||||
|             { | ||||
|                 return _listenerBondingListQueue.Dequeue(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return new List<DelegateGameObjectBonding>(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void DespawnDelegateBondingList(List<DelegateGameObjectBonding> list) | ||||
|         { | ||||
|             if (list == null) return; | ||||
| 
 | ||||
|             list.Clear(); | ||||
|             _listenerBondingListQueue.Enqueue(list); | ||||
|         } | ||||
| 
 | ||||
|         private bool DoAddListener(int event_id, Delegate new_listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             if (new_listener == null) | ||||
|             { | ||||
|                 Debug.LogError($"[GameEventMgr]DoAddListener: Can't add empty listener for event {event_id}!"); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             bool result = true; | ||||
| 
 | ||||
|             // If event is raising, add it into pending list | ||||
|             if (_raisingEventIds.Contains(event_id)) | ||||
|             { | ||||
|                 if (life_cycle_obj != null) | ||||
|                 { | ||||
|                     if (!_pendingObjAddTable.TryGetValue(event_id, out var pending_listeners)) | ||||
|                     { | ||||
|                         pending_listeners = SpawnDelegateBondingList(); | ||||
|                         _pendingObjAddTable[event_id] = pending_listeners; | ||||
|                     } | ||||
| 
 | ||||
|                     pending_listeners.Add(new DelegateGameObjectBonding(life_cycle_obj, new_listener)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (!_pendingAddTable.TryGetValue(event_id, out var pending_listeners)) | ||||
|                     { | ||||
|                         pending_listeners = SpawnDelegateList(); | ||||
|                         _pendingAddTable[event_id] = pending_listeners; | ||||
|                     } | ||||
| 
 | ||||
|                     pending_listeners.Add(new_listener); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (life_cycle_obj != null) | ||||
|                 { | ||||
|                     if (!_eventObjTable.TryGetValue(event_id, out var listener_bondings)) | ||||
|                     { | ||||
|                         listener_bondings = SpawnDelegateBondingList(); | ||||
|                         _eventObjTable[event_id] = listener_bondings; | ||||
|                     } | ||||
| 
 | ||||
|                     if (listener_bondings.Count == 0 || listener_bondings[0].listener.GetType() == new_listener.GetType()) | ||||
|                     { | ||||
|                         var bonding = new DelegateGameObjectBonding(life_cycle_obj, new_listener); | ||||
| 
 | ||||
|                         if (_isDebugMode && listener_bondings.Contains(bonding)) | ||||
|                         { | ||||
|                             Debug.LogError($"[GameEventMgr]DoAddListener: Already in the list, will not add it again [{new_listener.Target}.{new_listener.Method?.Name}]"); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             listener_bondings.Add(bonding); | ||||
|                         } | ||||
|                     } | ||||
|                     else if (listener_bondings[0].listener.GetType() != new_listener.GetType()) | ||||
|                     { | ||||
|                         Debug.LogError($"[GameEventMgr]DoAddListener: Attempting to add listener with inconsistent signature for event {event_id}. Current listeners type({listener_bondings[0].listener.GetType().Name}) != added type({new_listener.GetType().Name})"); | ||||
|                         result = false; | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (!_eventTable.TryGetValue(event_id, out var listeners)) | ||||
|                     { | ||||
|                         listeners = SpawnDelegateList(); | ||||
|                         _eventTable[event_id] = listeners; | ||||
|                     } | ||||
| 
 | ||||
|                     if (listeners.Count == 0 || listeners[0].GetType() == new_listener.GetType()) | ||||
|                     { | ||||
|                         if (_isDebugMode && listeners.Contains(new_listener)) | ||||
|                         { | ||||
|                             Debug.LogError($"[GameEventMgr]DoAddListener: Already in the list, will not add it again [{new_listener.Target}.{new_listener.Method?.Name}]"); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             listeners.Add(new_listener); | ||||
|                         } | ||||
|                     } | ||||
|                     else if (listeners[0].GetType() != new_listener.GetType()) | ||||
|                     { | ||||
|                         Debug.LogError($"[GameEventMgr]DoAddListener: Attempting to add listener with inconsistent signature for event {event_id}. Current listeners type({listeners[0].GetType().Name}) != added type({new_listener.GetType().Name})"); | ||||
|                         result = false; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         private bool DoRemoveListener(int event_id, Delegate listener, GameObject life_cycle_obj = null) | ||||
|         { | ||||
|             if (listener == null) | ||||
|             { | ||||
|                 Debug.LogError($"[GameEventMgr]DoRemoveListener: Can't remove empty listener for event {event_id}!"); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             bool result = false; | ||||
| 
 | ||||
|             // If event is raising, add it into pending list | ||||
|             if (_raisingEventIds.Contains(event_id)) | ||||
|             { | ||||
|                 if (life_cycle_obj != null) | ||||
|                 { | ||||
|                     if (!_pendingObjRemoveTable.TryGetValue(event_id, out var pending_listeners)) | ||||
|                     { | ||||
|                         pending_listeners = SpawnDelegateBondingList(); | ||||
|                         _pendingObjRemoveTable[event_id] = pending_listeners; | ||||
|                     } | ||||
| 
 | ||||
|                     pending_listeners.Add(new DelegateGameObjectBonding(life_cycle_obj, listener)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (!_pendingRemoveTable.TryGetValue(event_id, out var pending_listeners)) | ||||
|                     { | ||||
|                         pending_listeners = SpawnDelegateList(); | ||||
|                         _pendingRemoveTable[event_id] = pending_listeners; | ||||
|                     } | ||||
| 
 | ||||
|                     pending_listeners.Add(listener); | ||||
|                 } | ||||
| 
 | ||||
|                 result = true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (life_cycle_obj != null) | ||||
|                 { | ||||
|                     if (_eventObjTable.TryGetValue(event_id, out var listeners)) | ||||
|                     { | ||||
|                         if (listeners.Count > 0) | ||||
|                         { | ||||
|                             listeners.Remove(new DelegateGameObjectBonding(life_cycle_obj, listener)); | ||||
|                             result = true; | ||||
|                         } | ||||
| 
 | ||||
|                         if (listeners.Count == 0) | ||||
|                         { | ||||
|                             DespawnDelegateBondingList(listeners); | ||||
|                             _eventObjTable.Remove(event_id); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (_eventTable.TryGetValue(event_id, out var listeners)) | ||||
|                     { | ||||
|                         if (listeners.Count > 0) | ||||
|                         { | ||||
|                             listeners.Remove(listener); | ||||
|                             result = true; | ||||
|                         } | ||||
| 
 | ||||
|                         if (listeners.Count == 0) | ||||
|                         { | ||||
|                             DespawnDelegateList(listeners); | ||||
|                             _eventTable.Remove(event_id); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         private void LogException(Exception ex, string event_tag, string method_name) | ||||
|         { | ||||
|             string msg = ex.Message; | ||||
|             string stack_trace = ex.StackTrace; | ||||
| 
 | ||||
|             if (ex.InnerException != null) | ||||
|             { | ||||
|                 msg = ex.InnerException.Message; | ||||
|                 stack_trace = ex.InnerException.StackTrace; | ||||
|             } | ||||
| 
 | ||||
|             Debug.LogError($"[GameEventMgr]{method_name}: Event({event_tag}) {msg}\nStack Trace: {stack_trace}"); | ||||
|         } | ||||
| 
 | ||||
|         private bool DoRaise(int event_id, params object[] args) | ||||
|         { | ||||
|             if (_raisingEventIds.Contains(event_id)) | ||||
|             { | ||||
|                 Debug.LogError($"[GameEventMgr]DoRaise: Can't raise event({event_id}) again inside the same event raising process!"); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             bool result = false; | ||||
|             _raisingEventIds.Add(event_id); | ||||
| 
 | ||||
|             if (_eventObjTable.TryGetValue(event_id, out var bond_listeners)) | ||||
|             { | ||||
|                 for (int i = 0; i < bond_listeners.Count; i++) | ||||
|                 { | ||||
|                     var bond_listener = bond_listeners[i]; | ||||
| 
 | ||||
|                     if (bond_listener.gameObject != null) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             bond_listener.listener.Method.Invoke(bond_listener.listener.Target, args); | ||||
|                             result = true; | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             bond_listeners.RemoveAt(i--); | ||||
|                             LogException(ex, $"{event_id}_Obj[{bond_listener.gameObject.name}]", "DoRaise"); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         bond_listeners.RemoveAt(i--); | ||||
|                         Debug.Log($"[GameEventMgr]DoRaise: Remove null GameObject listener ({event_id})!"); | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (_eventTable.TryGetValue(event_id, out var listeners)) | ||||
|             { | ||||
|                 for (int i = 0; i < listeners.Count; i++) | ||||
|                 { | ||||
|                     var listener = listeners[i]; | ||||
| 
 | ||||
|                     if(listener != null) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             listener.Method.Invoke(listener.Target, args); | ||||
|                             result = true; | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             listeners.RemoveAt(i--); | ||||
|                             LogException(ex, event_id.ToString(), "DoRaise"); | ||||
|                         } | ||||
|                     } | ||||
|                 }                 | ||||
|             } | ||||
| 
 | ||||
|             _raisingEventIds.Remove(event_id); | ||||
| 
 | ||||
|             if (_pendingObjAddTable.TryGetValue(event_id, out var add_bond_listeners)) | ||||
|             { | ||||
|                 if (add_bond_listeners != null && add_bond_listeners.Count > 0) | ||||
|                 { | ||||
|                     bond_listeners.AddRange(add_bond_listeners); | ||||
|                     DespawnDelegateBondingList(add_bond_listeners); | ||||
|                 } | ||||
| 
 | ||||
|                 _pendingObjAddTable.Remove(event_id); | ||||
|             } | ||||
| 
 | ||||
|             if (_pendingObjRemoveTable.TryGetValue(event_id, out var remove_bond_listeners)) | ||||
|             { | ||||
|                 if (remove_bond_listeners != null && remove_bond_listeners.Count > 0) | ||||
|                 { | ||||
|                     for (int i = 0; i < remove_bond_listeners.Count; i++) | ||||
|                     { | ||||
|                         bond_listeners.Remove(remove_bond_listeners[i]); | ||||
|                     } | ||||
| 
 | ||||
|                     DespawnDelegateBondingList(remove_bond_listeners); | ||||
|                 } | ||||
| 
 | ||||
|                 _pendingObjRemoveTable.Remove(event_id); | ||||
|             } | ||||
| 
 | ||||
|             if (bond_listeners?.Count == 0) | ||||
|             { | ||||
|                 DespawnDelegateBondingList(bond_listeners); | ||||
|                 _eventObjTable.Remove(event_id); | ||||
|             } | ||||
| 
 | ||||
|             if (_pendingAddTable.TryGetValue(event_id, out var add_listeners)) | ||||
|             { | ||||
|                 if (add_listeners != null && add_listeners.Count > 0) | ||||
|                 { | ||||
|                     listeners.AddRange(add_listeners); | ||||
|                     DespawnDelegateList(add_listeners); | ||||
|                 } | ||||
| 
 | ||||
|                 _pendingAddTable.Remove(event_id); | ||||
|             } | ||||
| 
 | ||||
|             if (_pendingRemoveTable.TryGetValue(event_id, out var remove_listeners)) | ||||
|             { | ||||
|                 if (remove_listeners != null && remove_listeners.Count > 0) | ||||
|                 { | ||||
|                     for (int i = 0; i < remove_listeners.Count; i++) | ||||
|                     { | ||||
|                         listeners.Remove(remove_listeners[i]); | ||||
|                     } | ||||
| 
 | ||||
|                     DespawnDelegateList(remove_listeners); | ||||
|                 } | ||||
| 
 | ||||
|                 _pendingRemoveTable.Remove(event_id); | ||||
|             } | ||||
| 
 | ||||
|             if (listeners?.Count == 0) | ||||
|             { | ||||
|                 DespawnDelegateList(listeners); | ||||
|                 _eventTable.Remove(event_id); | ||||
|             } | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         public void Clear() | ||||
|         { | ||||
|             _eventTable.Clear(); | ||||
|             _eventObjTable.Clear(); | ||||
|             _pendingAddTable.Clear(); | ||||
|             _pendingObjAddTable.Clear(); | ||||
|             _pendingRemoveTable.Clear(); | ||||
|             _pendingObjRemoveTable.Clear(); | ||||
|             _raisingEventIds.Clear(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 8912da6e3d9994390b3795e3e9b065d9 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,168 @@ | |||
| using System; | ||||
| using System.Net; | ||||
| using System.IO; | ||||
| using dotNetty_kcp; | ||||
| using DotNetty.Buffers; | ||||
| using UnityEngine; | ||||
| using Guru; | ||||
| using Dof; | ||||
| 
 | ||||
| namespace DofLibrary | ||||
| { | ||||
| 
 | ||||
|     public class GameKcpClient : KcpListener | ||||
|     { | ||||
|         public Action OnConnected; | ||||
| 
 | ||||
|         private KcpClient _kcpClient; | ||||
|         public IMessageSender sender { get; private set; } = new MessageSender(); | ||||
|         private IMessageReceiver _receiver; | ||||
|         private bool _running; | ||||
|          | ||||
|         public GameKcpClient(DotNetty.Unity.Level level = DotNetty.Unity.Level.ALL) | ||||
|         { | ||||
|             // 指定DotNetty日志的显示级别 | ||||
|             DotNetty.Unity.UnityLoggerFactory.Default.Level = level; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当前 Client 是否处于连接状态 | ||||
|         /// </summary> | ||||
|         public bool Running => _running; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 创建一个到 Dof GameServer 的连接 | ||||
|         /// </summary> | ||||
|         /// <param name="serverAddress">Dof GameServer 的地址,形如 xxx.xxx.xxx.xxx</param> | ||||
|         /// <param name="port">房间ID</param> | ||||
|         /// <param name="receiver">事件回调接口</param> | ||||
|         public async void Connect(string serverAddress, int port, IMessageReceiver receiver) | ||||
|         { | ||||
|             _receiver = receiver; | ||||
|             _receiver.MessageSender = sender; | ||||
|             _running = true; | ||||
| 
 | ||||
|             var channelConfig = new ChannelConfig(); | ||||
|             channelConfig.initNodelay(true, 40, 2, true); | ||||
|             channelConfig.Sndwnd = 512; | ||||
|             channelConfig.Rcvwnd = 512; | ||||
|             channelConfig.Mtu = 512; | ||||
|             channelConfig.FecDataShardCount = 10; | ||||
|             channelConfig.FecParityShardCount = 3; | ||||
|             channelConfig.AckNoDelay = true; | ||||
|             channelConfig.Crc32Check = false; | ||||
|             channelConfig.UseConvChannel = true; | ||||
|             //channelConfig.Conv = UnityEngine.Random.Range(1, int.MaxValue); | ||||
|             channelConfig.TimeoutMillis = 10000; | ||||
| 
 | ||||
|             _kcpClient = new KcpClient(); | ||||
|             _kcpClient.init(channelConfig); | ||||
| 
 | ||||
|             var remoteAddress = new IPEndPoint(IPAddress.Parse(serverAddress), port); | ||||
|             var task = _kcpClient.BindLocal(); | ||||
|             await task; | ||||
|             var channel = task.Result; | ||||
|             _kcpClient.connect(channel, remoteAddress, channelConfig, this); | ||||
|         } | ||||
| 
 | ||||
|         private void ProcessMessage(ServerMessage serverMessage) | ||||
|         { | ||||
|             if (serverMessage.PlayerEntered != null) | ||||
|             { | ||||
|                 var cid = serverMessage.PlayerEntered.Cid; | ||||
|                 sender.Cid = cid; | ||||
|                 _receiver.OnPlayerEntered(cid); | ||||
|             } | ||||
|             else if(serverMessage.GameStart != null) | ||||
|             { | ||||
|                 _receiver.OnGameStart(serverMessage.GameStart); | ||||
|             } | ||||
|             else if (serverMessage.LevelStart != null) | ||||
|             { | ||||
|                 _receiver.OnLevelStart(serverMessage.LevelStart); | ||||
|             } | ||||
|             else if (serverMessage.PointFound != null) | ||||
|             { | ||||
|                 _receiver.OnPointFound(serverMessage.PointFound); | ||||
|             } | ||||
|             else if (serverMessage.GameFinish != null) | ||||
|             { | ||||
|                 _receiver.OnGameFinish(serverMessage.GameFinish); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// kcp 连接后的回调函数 | ||||
|         /// </summary> | ||||
|         /// <param name="ukcp">kcp连接</param> | ||||
|         public void onConnected(Ukcp ukcp) | ||||
|         { | ||||
|             Debug.Log($"[GameKcpClient]onConnected: conv = {ukcp.getConv()}"); | ||||
|             sender.Client = ukcp; | ||||
| 
 | ||||
|             if (OnConnected != null) | ||||
|             { | ||||
|                 Loom.QueueOnMainThread(() => | ||||
|                 { | ||||
|                     OnConnected.Invoke(); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// kcp 接收到消息的回调函数 | ||||
|         /// </summary> | ||||
|         /// <param name="byteBuf">接收到的消息buffer</param> | ||||
|         /// <param name="ukcp">kcp连接</param> | ||||
|         public void handleReceive(IByteBuffer byteBuf, Ukcp ukcp) | ||||
|         { | ||||
|             Debug.Log("[GameKcpClient]handleReceive"); | ||||
| 
 | ||||
|             var ms = new MemoryStream(1024 * 1024 * 1); | ||||
|             var data_len = byteBuf.ReadableBytes; | ||||
|             byteBuf.ReadBytes(ms, data_len); | ||||
|             var msg = ProtobufHelper.FromBytes(typeof(ServerMessage), ms.GetBuffer(), 0, data_len); | ||||
| 
 | ||||
|             if (msg is ServerMessage server_msg) | ||||
|             { | ||||
|                 Loom.QueueOnMainThread(() => | ||||
|                 { | ||||
|                     ProcessMessage(server_msg); | ||||
|                 }); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Debug.LogError("[GameKcpClient]handleReceive: Fail to Deserialize ServerMessage"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// kcp 连接或者接收消息出错的回调函数 | ||||
|         /// </summary> | ||||
|         /// <param name="ex">异常</param> | ||||
|         /// <param name="ukcp">kcp连接</param> | ||||
|         public void handleException(Exception ex, Ukcp ukcp) | ||||
|         { | ||||
|             Debug.LogError($"[GameKcpClient]handleException: {ex}"); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// kcp 连接关闭时的回调函数 | ||||
|         /// </summary> | ||||
|         /// <param name="ukcp">kcp连接</param> | ||||
|         public void handleClose(Ukcp ukcp) | ||||
|         { | ||||
|             Debug.Log("[GameKcpClient]: Connection closed"); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 关闭 Client,应该在游戏结束时调用 | ||||
|         /// </summary> | ||||
|         public void Close() | ||||
|         { | ||||
|             _kcpClient?.stop(); | ||||
|             _running = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 71aa1db1be3544524bb58107e80708ca | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,16 @@ | |||
| { | ||||
|     "name": "GuruDofLib.Runtime", | ||||
|     "rootNamespace": "", | ||||
|     "references": [ | ||||
|         "GUID:832e7ae06a4304a17a11ca2f7b21373d" | ||||
|     ], | ||||
|     "includePlatforms": [], | ||||
|     "excludePlatforms": [], | ||||
|     "allowUnsafeCode": false, | ||||
|     "overrideReferences": false, | ||||
|     "precompiledReferences": [], | ||||
|     "autoReferenced": true, | ||||
|     "defineConstraints": [], | ||||
|     "versionDefines": [], | ||||
|     "noEngineReferences": false | ||||
| } | ||||
|  | @ -0,0 +1,7 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: e2e9b8b39d6d74d1094c47c89de72e2e | ||||
| AssemblyDefinitionImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,45 @@ | |||
| using Dof; | ||||
| 
 | ||||
| namespace DofLibrary | ||||
| { | ||||
|     public interface IMessageReceiver | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Set MessageSender for sending messages | ||||
|         /// </summary> | ||||
|         IMessageSender? MessageSender | ||||
|         { | ||||
|             set; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当玩家进入房间成功时,服务端发送此事件 | ||||
|         /// </summary> | ||||
|         /// <param name="cid">当前房间里的玩家编号,为0|1</param> | ||||
|         void OnPlayerEntered(long cid); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当房间里已经进入两个玩家时,服务端发送此事件 | ||||
|         /// </summary> | ||||
|         /// <param name="gameStart"></param> | ||||
|         void OnGameStart(GameStart gameStart); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当两个玩家都准备好了某个关卡时,服务端发送此事件 | ||||
|         /// </summary> | ||||
|         /// <param name="levelStart">当前开始的关卡ID</param> | ||||
|         void OnLevelStart(LevelStart levelStart); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当另一个玩家找到了当前关卡的某个点时,服务端发送此事件 | ||||
|         /// </summary> | ||||
|         /// <param name="pointFound">另一个玩家找到的点</param> | ||||
|         void OnPointFound(PointFound pointFound); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当两个玩家都完成了所有关卡时,服务端发送此事件 | ||||
|         /// </summary> | ||||
|         /// <param name="gameFinish">两个玩家的得分</param> | ||||
|         void OnGameFinish(GameFinish gameFinish); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 2d2e7c96d274a436895dfb34fdac9d8d | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,59 @@ | |||
| using dotNetty_kcp; | ||||
| 
 | ||||
| namespace DofLibrary | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 游戏消息发送接口 | ||||
|     /// </summary> | ||||
|     public interface IMessageSender | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// 设置 kcp 客户端,用于发送消息 | ||||
|         /// </summary> | ||||
|         Ukcp? Client | ||||
|         { | ||||
|             set; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当收到服务端发来的 PlayerEntered 事件时,设置房间中的玩家编号  | ||||
|         /// </summary> | ||||
|         long Cid | ||||
|         { | ||||
|             set; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 发送 PlayerEnter 进入房间 消息 | ||||
|         /// </summary> | ||||
|         /// <param name="roomId">房间ID</param> | ||||
|         /// <param name="uid">玩家ID</param> | ||||
|         /// <param name="nickName">玩家昵称</param> | ||||
|         /// <param name="country">玩家国家</param> | ||||
|         void PlayerEnter(string roomId, string uid, string nickName, string country); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 发送 LevelPrepared 关卡准备完毕 消息 | ||||
|         /// </summary> | ||||
|         /// <param name="levelId">关卡ID</param> | ||||
|         void LevelPrepared(string levelId); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 发送 PointFound 找到点位 消息 | ||||
|         /// </summary> | ||||
|         /// <param name="levelId">当前关卡ID</param> | ||||
|         /// <param name="pointId">点位编号</param> | ||||
|         void PointFound(string levelId, int pointId); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 发送 LevelEnd 关卡结束 消息 | ||||
|         /// </summary> | ||||
|         /// <param name="levelId">关卡ID</param> | ||||
|         void LevelEnd(string levelId); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 发送 AllLevelEnd 所有关卡结束 消息 | ||||
|         /// </summary> | ||||
|         void AllLevelEnd(); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 29452c4084f2f45399bb395cedd91ebd | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,218 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| using UnityEngine; | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     // Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to. | ||||
|     public class Loom : MonoBehaviour | ||||
|     { | ||||
|         public static int maxThreads = 10; | ||||
| 
 | ||||
|         private static int numThreads; | ||||
|         private static Loom _instance; | ||||
| 
 | ||||
|         public static Loom Instance | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 Initialize(); | ||||
|                 return _instance; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         void Awake() | ||||
|         { | ||||
|             _instance = this; | ||||
|             initialized = true; | ||||
|         } | ||||
| 
 | ||||
|         static bool initialized; | ||||
| 
 | ||||
|         public static void Initialize() | ||||
|         { | ||||
|             if (!initialized) | ||||
|             { | ||||
|                 if (!Application.isPlaying) return; | ||||
| 
 | ||||
|                 initialized = true; | ||||
|                 var g = new GameObject("Loom"); | ||||
|                 DontDestroyOnLoad(g); | ||||
|                 _instance = g.AddComponent<Loom>(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public struct DelayedQueueItem | ||||
|         { | ||||
|             public float time; | ||||
|             public Action action; | ||||
|         } | ||||
| 
 | ||||
|         private List<Action> _actions = new List<Action>(); | ||||
|         private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>(); | ||||
|         private List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>(); | ||||
| 
 | ||||
|         // Runs a set of statements on the main thread | ||||
|         public static void QueueOnMainThread(Action action) | ||||
|         { | ||||
|             QueueOnMainThread(action, 0f); | ||||
|         } | ||||
| 
 | ||||
|         // Runs a set of statements on the main thread (with an optional delay). | ||||
|         public static void QueueOnMainThread(Action action, float time) | ||||
|         { | ||||
|             if (time != 0) | ||||
|             { | ||||
|                 lock (_instance._delayed) | ||||
|                 { | ||||
|                     _instance._delayed.Add(new DelayedQueueItem { time = Time.unscaledTime + time, action = action }); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 lock (_instance._actions) | ||||
|                 { | ||||
|                     _instance._actions.Add(action); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         //  Runs a set of statements on another thread | ||||
|         public static Thread RunAsync(Action a) | ||||
|         { | ||||
|             Initialize(); | ||||
| 
 | ||||
|             while (numThreads >= maxThreads) | ||||
|             { | ||||
|                 Thread.Sleep(1); | ||||
|             } | ||||
| 
 | ||||
|             Interlocked.Increment(ref numThreads); | ||||
|             ThreadPool.QueueUserWorkItem(RunAction, a); | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         private static void RunAction(object action) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 ((Action)action)(); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 Interlocked.Decrement(ref numThreads); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void Reset() | ||||
|         { | ||||
|             if (_instance == null) return; | ||||
| 
 | ||||
|             lock (_instance._delayed) | ||||
|             { | ||||
|                 _instance._delayed.Clear(); | ||||
|                 _instance._currentDelayed.Clear(); | ||||
|             } | ||||
| 
 | ||||
|             lock (_instance._actions) | ||||
|             { | ||||
|                 _instance._actions.Clear(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool IsThreadsMax() | ||||
|         { | ||||
|             return numThreads >= maxThreads; | ||||
|         } | ||||
| 
 | ||||
|         void OnDisable() | ||||
|         { | ||||
|             if (_instance == this) | ||||
|             { | ||||
|                 _instance = null; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         List<Action> _currentActions = new List<Action>(); | ||||
| 
 | ||||
|         void Update() | ||||
|         { | ||||
|             if (_actions.Count > 0) | ||||
|             { | ||||
|                 lock (_actions) | ||||
|                 { | ||||
|                     _currentActions.Clear(); | ||||
|                     _currentActions.AddRange(_actions); | ||||
|                     _actions.Clear(); | ||||
|                 } | ||||
| 
 | ||||
|                 for (int i = 0; i < _currentActions.Count; i++) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         _currentActions[i].Invoke(); | ||||
|                     } | ||||
|                     catch(Exception ex) | ||||
|                     { | ||||
|                         LogException(ex); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (_delayed.Count > 0) | ||||
|                 { | ||||
|                     lock (_delayed) | ||||
|                     { | ||||
|                         _currentDelayed.Clear(); | ||||
| 
 | ||||
|                         for (int i = 0; i < _delayed.Count; i++) | ||||
|                         { | ||||
|                             var d = _delayed[i]; | ||||
| 
 | ||||
|                             if (d.time <= Time.unscaledTime) | ||||
|                             { | ||||
|                                 _currentDelayed.Add(d); | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         for (int i = 0; i < _currentDelayed.Count; i++) | ||||
|                         { | ||||
|                             var item = _currentDelayed[i]; | ||||
|                             _delayed.Remove(item); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     for (int i = 0; i < _currentDelayed.Count; i++) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             _currentDelayed[i].action.Invoke(); | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             LogException(ex); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         void LogException(Exception ex) | ||||
|         { | ||||
|             string msg = ex.Message; | ||||
|             string stack_trace = ex.StackTrace; | ||||
| 
 | ||||
|             if (ex.InnerException != null) | ||||
|             { | ||||
|                 msg = ex.InnerException.Message; | ||||
|                 stack_trace = ex.InnerException.StackTrace; | ||||
|             } | ||||
| 
 | ||||
|             Debug.LogError($"[Loom]InvokeMainThreadAction: {msg}\nStack Trace: {stack_trace}"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 8d56f3a7c2d1a48c78f742f63a075fb7 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,122 @@ | |||
| using Dof; | ||||
| using dotNetty_kcp; | ||||
| using DotNetty.Buffers; | ||||
| using UnityEngine; | ||||
| using System.IO; | ||||
| using base_kcp; | ||||
| using Guru; | ||||
| 
 | ||||
| namespace DofLibrary | ||||
| { | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 游戏消息发送接口实现类 | ||||
|     /// </summary> | ||||
|     internal class MessageSender : IMessageSender | ||||
|     { | ||||
|         private long _cid; | ||||
|         private Ukcp _client; | ||||
|         private MemoryStream _localSendMs = new(1024 * 1024 * 1); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 设置 kcp 客户端,用于发送消息 | ||||
|         /// </summary> | ||||
|         public Ukcp Client | ||||
|         { | ||||
|             set => _client = value; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 当收到服务端发来的 PlayerEntered 事件时,设置房间中的玩家编号  | ||||
|         /// </summary> | ||||
|         public long Cid | ||||
|         { | ||||
|             set => _cid = value; | ||||
|         } | ||||
| 
 | ||||
|         public void PlayerEnter(string roomId, string uid, string nickName, string country) | ||||
|         { | ||||
|             var playerEnter = new ClientMessage | ||||
|             { | ||||
|                 PlayerEnter = new PlayerEnter | ||||
|                 { | ||||
|                     RoomId = roomId, | ||||
|                     Uid = uid, | ||||
|                     NickName = nickName, | ||||
|                     Country = country | ||||
|                 } | ||||
|             }; | ||||
|             Send(playerEnter); | ||||
|             Debug.Log("PlayerEnter message sent"); | ||||
|         } | ||||
| 
 | ||||
|         public void LevelPrepared(string levelId) | ||||
|         { | ||||
|             var message = new ClientMessage | ||||
|             { | ||||
|                 LevelPrepared = new LevelPrepared | ||||
|                 { | ||||
|                     Cid = _cid, | ||||
|                     Level = levelId | ||||
|                 } | ||||
|             }; | ||||
|             Send(message); | ||||
|             Debug.Log("LevelPrepared message sent"); | ||||
|         } | ||||
| 
 | ||||
|         public void PointFound(string levelId, int pointId) | ||||
|         { | ||||
|             var message = new ClientMessage | ||||
|             { | ||||
|                 PointFound = new PointFound | ||||
|                 { | ||||
|                     Cid = _cid, | ||||
|                     Level = levelId, | ||||
|                     PointId = pointId | ||||
|                 } | ||||
|             }; | ||||
|             Send(message); | ||||
|             Debug.Log("PointFound message sent"); | ||||
|         } | ||||
| 
 | ||||
|         public void LevelEnd(string levelId) | ||||
|         { | ||||
|             var message = new ClientMessage | ||||
|             { | ||||
|                 LevelEnd = new LevelEnd | ||||
|                 { | ||||
|                     Cid = _cid, | ||||
|                     Level = levelId | ||||
|                 } | ||||
|             }; | ||||
|             Send(message); | ||||
|             Debug.Log("LevelEnd message sent"); | ||||
|         } | ||||
| 
 | ||||
|         public void AllLevelEnd() | ||||
|         { | ||||
|             var message = new ClientMessage | ||||
|             { | ||||
|                 AllLevelEnd = new AllLevelEnd | ||||
|                 { | ||||
|                     Cid = _cid | ||||
|                 } | ||||
|             }; | ||||
|             Send(message); | ||||
|             Debug.Log("AllLevelEnd message sent"); | ||||
|         } | ||||
| 
 | ||||
|         private void Send(ClientMessage message) | ||||
|         { | ||||
|             _localSendMs.SetLength(0); | ||||
|             _localSendMs.Position = 0; | ||||
| 
 | ||||
|             ProtobufHelper.ToStream(message, _localSendMs); | ||||
| 
 | ||||
|             var dataBuf = Unpooled.DirectBuffer(1024 * 1024 * 1); | ||||
|             dataBuf.WriteBytes(_localSendMs.GetBuffer(), 0, (int)_localSendMs.Length); | ||||
|             _client.write(dataBuf); | ||||
|             dataBuf.Release(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 83018a8afd2ab4af499e831caa714f77 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,8 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 92dc6b0ee5121a94bb34348448a983a1 | ||||
| folderAsset: yes | ||||
| DefaultImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,263 @@ | |||
| // This file was generated by a tool; you should avoid making direct changes. | ||||
| // Consider using 'partial classes' to extend these types | ||||
| // Input: messages.proto | ||||
| 
 | ||||
| #pragma warning disable CS1591, CS0612, CS3021 | ||||
| 
 | ||||
| namespace Dof | ||||
| { | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class ClientMessage | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"player_enter")] | ||||
|         public PlayerEnter PlayerEnter | ||||
|         { | ||||
|             get { return __pbn__actual.Is(1) ? ((PlayerEnter)__pbn__actual.Object) : default(PlayerEnter); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(1, value); } | ||||
|         } | ||||
|         public bool ShouldSerializePlayerEnter() => __pbn__actual.Is(1); | ||||
|         public void ResetPlayerEnter() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 1); | ||||
| 
 | ||||
|         private global::ProtoBuf.DiscriminatedUnionObject __pbn__actual; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"level_prepared")] | ||||
|         public LevelPrepared LevelPrepared | ||||
|         { | ||||
|             get { return __pbn__actual.Is(2) ? ((LevelPrepared)__pbn__actual.Object) : default(LevelPrepared); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(2, value); } | ||||
|         } | ||||
|         public bool ShouldSerializeLevelPrepared() => __pbn__actual.Is(2); | ||||
|         public void ResetLevelPrepared() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 2); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(3, Name = @"point_found")] | ||||
|         public PointFound PointFound | ||||
|         { | ||||
|             get { return __pbn__actual.Is(3) ? ((PointFound)__pbn__actual.Object) : default(PointFound); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(3, value); } | ||||
|         } | ||||
|         public bool ShouldSerializePointFound() => __pbn__actual.Is(3); | ||||
|         public void ResetPointFound() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 3); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(4, Name = @"level_end")] | ||||
|         public LevelEnd LevelEnd | ||||
|         { | ||||
|             get { return __pbn__actual.Is(4) ? ((LevelEnd)__pbn__actual.Object) : default(LevelEnd); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(4, value); } | ||||
|         } | ||||
|         public bool ShouldSerializeLevelEnd() => __pbn__actual.Is(4); | ||||
|         public void ResetLevelEnd() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 4); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(5, Name = @"all_level_end")] | ||||
|         public AllLevelEnd AllLevelEnd | ||||
|         { | ||||
|             get { return __pbn__actual.Is(5) ? ((AllLevelEnd)__pbn__actual.Object) : default(AllLevelEnd); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(5, value); } | ||||
|         } | ||||
|         public bool ShouldSerializeAllLevelEnd() => __pbn__actual.Is(5); | ||||
|         public void ResetAllLevelEnd() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 5); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(6, Name = @"player_leave")] | ||||
|         public PlayerLeave PlayerLeave | ||||
|         { | ||||
|             get { return __pbn__actual.Is(6) ? ((PlayerLeave)__pbn__actual.Object) : default(PlayerLeave); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(6, value); } | ||||
|         } | ||||
|         public bool ShouldSerializePlayerLeave() => __pbn__actual.Is(6); | ||||
|         public void ResetPlayerLeave() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 6); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class ServerMessage | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"player_entered")] | ||||
|         public PlayerEntered PlayerEntered | ||||
|         { | ||||
|             get { return __pbn__actual.Is(1) ? ((PlayerEntered)__pbn__actual.Object) : default(PlayerEntered); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(1, value); } | ||||
|         } | ||||
|         public bool ShouldSerializePlayerEntered() => __pbn__actual.Is(1); | ||||
|         public void ResetPlayerEntered() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 1); | ||||
| 
 | ||||
|         private global::ProtoBuf.DiscriminatedUnionObject __pbn__actual; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"game_start")] | ||||
|         public GameStart GameStart | ||||
|         { | ||||
|             get { return __pbn__actual.Is(2) ? ((GameStart)__pbn__actual.Object) : default(GameStart); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(2, value); } | ||||
|         } | ||||
|         public bool ShouldSerializeGameStart() => __pbn__actual.Is(2); | ||||
|         public void ResetGameStart() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 2); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(3, Name = @"level_start")] | ||||
|         public LevelStart LevelStart | ||||
|         { | ||||
|             get { return __pbn__actual.Is(3) ? ((LevelStart)__pbn__actual.Object) : default(LevelStart); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(3, value); } | ||||
|         } | ||||
|         public bool ShouldSerializeLevelStart() => __pbn__actual.Is(3); | ||||
|         public void ResetLevelStart() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 3); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(4, Name = @"point_found")] | ||||
|         public PointFound PointFound | ||||
|         { | ||||
|             get { return __pbn__actual.Is(4) ? ((PointFound)__pbn__actual.Object) : default(PointFound); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(4, value); } | ||||
|         } | ||||
|         public bool ShouldSerializePointFound() => __pbn__actual.Is(4); | ||||
|         public void ResetPointFound() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 4); | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(5, Name = @"game_finish")] | ||||
|         public GameFinish GameFinish | ||||
|         { | ||||
|             get { return __pbn__actual.Is(5) ? ((GameFinish)__pbn__actual.Object) : default(GameFinish); } | ||||
|             set { __pbn__actual = new global::ProtoBuf.DiscriminatedUnionObject(5, value); } | ||||
|         } | ||||
|         public bool ShouldSerializeGameFinish() => __pbn__actual.Is(5); | ||||
|         public void ResetGameFinish() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__actual, 5); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class PlayerEnter | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"room_id")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string RoomId { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"uid")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Uid { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(3, Name = @"nick_name")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string NickName { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(4, Name = @"country")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Country { get; set; } = ""; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class LevelResource | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"level_id")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string LevelId { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"android_generation")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string AndroidGeneration { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(3, Name = @"ios_generation")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string IosGeneration { get; set; } = ""; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class PlayerEntered | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"cid")] | ||||
|         public long Cid { get; set; } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class GameStart | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"level_resource")] | ||||
|         public global::System.Collections.Generic.List<LevelResource> LevelResources { get; } = new global::System.Collections.Generic.List<LevelResource>(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class LevelPrepared | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"cid")] | ||||
|         public long Cid { get; set; } | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"level")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Level { get; set; } = ""; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class LevelStart | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"level")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Level { get; set; } = ""; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class PointFound | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"cid")] | ||||
|         public long Cid { get; set; } | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"level")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Level { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(3, Name = @"point_id")] | ||||
|         public int PointId { get; set; } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class LevelEnd | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"cid")] | ||||
|         public long Cid { get; set; } | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"level")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Level { get; set; } = ""; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class AllLevelEnd | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"cid")] | ||||
|         public long Cid { get; set; } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class GameScore | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"uid")] | ||||
|         [global::System.ComponentModel.DefaultValue("")] | ||||
|         public string Uid { get; set; } = ""; | ||||
| 
 | ||||
|         [global::ProtoBuf.ProtoMember(2, Name = @"score")] | ||||
|         public int Score { get; set; } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class GameFinish | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"scores")] | ||||
|         public global::System.Collections.Generic.List<GameScore> Scores { get; } = new global::System.Collections.Generic.List<GameScore>(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [global::ProtoBuf.ProtoContract()] | ||||
|     public partial class PlayerLeave | ||||
|     { | ||||
|         [global::ProtoBuf.ProtoMember(1, Name = @"cid")] | ||||
|         public long Cid { get; set; } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #pragma warning restore CS1591, CS0612, CS3021 | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: d2ca874f5de9f40e0bf69a03d2921c90 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,61 @@ | |||
| using System; | ||||
| using System.IO; | ||||
| using ProtoBuf.Meta; | ||||
| 
 | ||||
| namespace Guru | ||||
| { | ||||
|     public interface ISupportInitialize | ||||
|     { | ||||
|         void BeginInit(); | ||||
|         void EndInit(); | ||||
|     } | ||||
| 
 | ||||
|     public interface IDisposable | ||||
|     { | ||||
|         void Dispose(); | ||||
|     } | ||||
| 
 | ||||
|     public static class ProtobufHelper | ||||
|     { | ||||
| 	    public static void Init() | ||||
|         { | ||||
|         } | ||||
| 
 | ||||
|         public static object FromBytes(Type type, byte[] bytes, int index, int count) | ||||
|         { | ||||
| 	        using (MemoryStream stream = new MemoryStream(bytes, index, count)) | ||||
| 	        { | ||||
| 		        object o = RuntimeTypeModel.Default.Deserialize(stream, null, type); | ||||
| 		        if (o is ISupportInitialize supportInitialize) | ||||
| 		        { | ||||
| 			        supportInitialize.EndInit(); | ||||
| 		        } | ||||
| 		        return o; | ||||
| 	        } | ||||
|         } | ||||
| 
 | ||||
|         public static byte[] ToBytes(object message) | ||||
|         { | ||||
| 	        using (MemoryStream stream = new MemoryStream()) | ||||
| 	        { | ||||
| 		        ProtoBuf.Serializer.Serialize(stream, message); | ||||
| 		        return stream.ToArray(); | ||||
| 	        } | ||||
|         } | ||||
| 
 | ||||
|         public static void ToStream(object message, MemoryStream stream) | ||||
|         { | ||||
|             ProtoBuf.Serializer.Serialize(stream, message); | ||||
|         } | ||||
| 
 | ||||
|         public static object FromStream(Type type, MemoryStream stream) | ||||
|         { | ||||
| 	        object o = RuntimeTypeModel.Default.Deserialize(stream, null, type); | ||||
| 	        if (o is ISupportInitialize supportInitialize) | ||||
| 	        { | ||||
| 		        supportInitialize.EndInit(); | ||||
| 	        } | ||||
| 	        return o; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 87fa43cd32c964f6a9353a6400490b67 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
|  | @ -0,0 +1,11 @@ | |||
| { | ||||
|   "name": "com.guru.unity.gurudoflib", | ||||
|   "displayName": "GuruDofLib", | ||||
|   "version": "0.0.1", | ||||
|   "description": "基于Guru kcp库实现的客户端接口封装", | ||||
|   "unity": "2020.3", | ||||
|   "license": "MIT", | ||||
|   "category": "Game tool", | ||||
|   "dependencies": { | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,7 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 41df6effece9c454c85e2908e1b00150 | ||||
| PackageManifestImporter: | ||||
|   externalObjects: {} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
		Loading…
	
		Reference in New Issue