两种事件总线的体会 2025-05-11 14 笔记 在Unity中原版自带的事件系统不能够满足我的需求,所以在一开始我写了一个通过委托类传递EventArg的事件中心。 ```csharp using System; using Client.Event.EventArgs; using Client.Event.EventHandler; using Client.Object; using UnityEngine; namespace Client.Event { public class EventCenter : MonoBehaviour { #region 单例 private static readonly object Lock = new(); private static EventCenter _instance; public static EventCenter Instance { get { lock (Lock) { if (_instance != null) return _instance; _instance = FindObjectOfType() ?? new GameObject("EventCenter").AddComponent(); return _instance; } } } private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); } else { _instance = this; } } #endregion //定义事件 //todo enemydie event public event PlayerHpChangeEventHandler OnPlayerHpChange; public event PlayerDieEventHandler OnPlayerDie; public event TouchPlayerEventHandler OnTouchPlayer; public event TouchBaseEventHandler OnTouchBase; public event BaseHpChangeEventHandler OnBaseHpChange; public event BaseRuinedEventHandler OnBaseRuined; //玩家HP变化 血量归零时执行PlayerDie,触发OnPlayerDie public int? PlayerHpChange(int damage, ulong playerID) { Debug.Log(playerID); var newHp = OnPlayerHpChange?.Invoke(new PlayerHpChangeEventArgs(damage, playerID)); return newHp; } public int? BaseHpChange(int damage, Base @base) { var newHp = OnBaseHpChange?.Invoke(new BaseHpChangeEventArgs(damage,@base)); return newHp; } public ulong? PlayerDie(ulong playerID) { var temp = OnPlayerDie?.Invoke(new PlayerDieEventArgs(playerID)); return temp; } public Base TouchBase(Base @base) { return OnTouchBase?.Invoke(new TouchBaseEventArgs(@base)); } public Base BaseRuined(Base @base) { return OnBaseRuined?.Invoke(new BaseRuinedEventArgs(@base)); } //触摸玩家 public Player TouchPlayer(Player player, ulong playerID) { return OnTouchPlayer?.Invoke(new TouchPlayerEventArgs(player, playerID)); } } } ``` 但是我在使用过程中很快就发现了这么写的弊端,即每次添加新事件都要创建新的委托和新的EventArg,同时所有事件都由事件中心定义也并不是很符合SRP。 在后面的学习中,我了解到了一个通过字典来维护事件的事件系统。 ```csharp using System; using System.Collections.Generic; using System.Reflection; using Handler; using UnityEngine; using Utils; namespace Manager { /// /// 全局事件管理系统(单例模式) /// 支持泛型事件参数和特性驱动的事件订阅 /// public class EventManager : Singleton { // 使用泛型事件字典来存储事件和处理程序 private readonly Dictionary _eventHandlers = new(); /// /// 注册事件处理方法 /// ⚠️ 同一事件的多个处理程序按注册顺序执行 /// /// 事件参数类型 /// 事件唯一标识 /// 事件处理方法(必须返回 object) public void RegisterEvent(string eventName, Func handler) { if (!_eventHandlers.TryAdd(eventName, handler)) _eventHandlers[eventName] = Delegate.Combine(_eventHandlers[eventName], handler); } /// /// 注销指定事件的单个处理程序 /// ⚠️ 需要提供与注册时完全相同的委托实例 /// /// 事件参数类型 /// 目标事件名称 /// 要移除的处理程序 public void UnregisterEvent(string eventName, Func handler) { if (_eventHandlers.ContainsKey(eventName)) _eventHandlers[eventName] = Delegate.Remove(_eventHandlers[eventName], handler); } /// /// 触发指定事件并收集所有处理程序返回值 /// ⚠️ 事件参数类型必须与注册时一致 /// /// 事件参数类型 /// 目标事件名称 /// 事件参数对象 /// 所有处理程序返回值的列表(可能包含 null) public List TriggerEvent(string eventName, T args) { if (!_eventHandlers.TryGetValue(eventName, out var eventHandler)) return new List(); List results = new List(); foreach (Delegate handler in eventHandler.GetInvocationList()) { if (handler is Func typedHandler) { try { results.Add(typedHandler(args)); } catch (Exception ex) { if (Debugger.IsDebugging) { Debug.LogError($"执行事件 {eventName} 时发生异常: {ex}"); } } } else { if (Debugger.IsDebugging) { Debug.LogError($"事件 {eventName} 的处理程序类型不匹配"); } } } return results; } /// /// 取消指定事件的所有处理程序 /// ⚠️ 立即生效且不可逆 /// /// 要取消的事件名称 public void CancelEvent(string eventName) { if (!_eventHandlers.ContainsKey(eventName)) return; _eventHandlers[eventName] = null; if (Debugger.IsDebugging) { Debug.Log($"事件 {eventName} 已取消。"); } } /// /// 注销指定对象的所有事件订阅 /// 用于对象销毁时自动清理订阅 /// /// 要清理的订阅者对象 /// /// void OnDestroy() => EventManager.Instance.UnregisterAllEventsForObject(this); /// public void UnregisterAllEventsForObject(object targetObject) { if (targetObject is null) { if (Debugger.IsDebugging) { Debug.Log("已销毁物体无法取消订阅。"); } return; } // 将事件名称存入列表以避免遍历时修改集合 var eventNames = new List(_eventHandlers.Keys); foreach (var eventName in eventNames) { var eventHandler = _eventHandlers[eventName]; if (eventHandler == null) continue; var handlers = eventHandler.GetInvocationList(); // 遍历并移除与目标对象相关的处理程序 foreach (var handler in handlers) if (handler.Target == targetObject) _eventHandlers[eventName] = Delegate.Remove(_eventHandlers[eventName], handler); // 检查并移除空的事件 if (_eventHandlers[eventName] == null || _eventHandlers[eventName].GetInvocationList().Length == 0) _eventHandlers.Remove(eventName); } if (Debugger.IsDebugging) { Debug.Log($"已为 {targetObject} 注销所有事件订阅。"); } } /// /// 清空所有事件注册信息 /// ⚠️ 通常在场景切换时调用 /// public void UnregisterAllEvents() { _eventHandlers.Clear(); if (Debugger.IsDebugging) { Debug.Log("所有事件订阅已被注销。"); } } /// /// 通过反射自动注册带 [EventSubscribe] 特性的方法 /// 要求方法格式:object Method(T args) /// /// 包含订阅方法的对象实例 /// /// public class Player { /// [EventSubscribe("PlayerHurt")] /// private object OnHurt(HurtEventArgs args) { ... } /// } /// EventManager.Instance.RegisterEventHandlersFromAttributes(player); /// public void RegisterEventHandlersFromAttributes(object target) { var methods = target.GetType() .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var method in methods) { var attributes = method.GetCustomAttributes(typeof(EventSubscribeAttribute), false); foreach (var attribute in attributes) { if (attribute is not EventSubscribeAttribute eventSubscribe) continue; var eventName = eventSubscribe.EventName; var handler = Delegate.CreateDelegate( typeof(Func<,>).MakeGenericType(method.GetParameters()[0].ParameterType, method.ReturnType), target, method); if (!_eventHandlers.TryAdd(eventName, handler)) _eventHandlers[eventName] = Delegate.Combine(_eventHandlers[eventName], handler); } } } } /// /// 事件订阅特性(用于自动注册处理方法) /// 标记在符合格式的方法上:public object Method(T args) /// [AttributeUsage(AttributeTargets.Method)] public class EventSubscribeAttribute : Attribute { /// /// 创建事件订阅特性实例 /// /// 要订阅的事件名称 public EventSubscribeAttribute(string eventName) { EventName = eventName; } /// /// 目标事件名称 /// public string EventName { get; } } } ``` 这么写在其他具体类中很方便使用 ```csharp // 注册事件 EventManager.Instance.RegisterEvent("PlayerAttack", damage => { Debug.Log($"造成伤害: {damage}"); return null; }); // 触发事件 EventManager.Instance.TriggerEvent("PlayerAttack", 50); // 注销事件 EventManager.Instance.UnregisterEvent("PlayerAttack", handler); ``` 但是后来在调试时我发现使用字典维护的事件系统根本获取不到完整的调用链,向上查看只能查看到事件系统,不能查看完整的发布者和订阅者的完整调用。 那该怎么办呢,我的水平还不能支持我进一步探讨。先摆了:innocent: 本文链接: https://shrinken.pw/crash-2025-05-11_100-fml.html