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