为Unity3d编写自己的扩展事件聚合器的想法早该实现了。 在阅读了有关该主题的几篇文章之后,我意识到(在Unity3d中)没有足够的“正确”和聚合器,这对我来说是必需的,所有解决方案均已精简并且没有必需的功能。
所需功能:
- 任何类别都可以订阅任何事件(单位中的聚合者通常会指定特定的Gameobject订阅者)
- 应排除针对特定事件重复订阅特定实例的可能性(在标准工具中,您需要自己遵循此规定)
- 在删除实例/禁用monobekh的情况下,应该同时具有手动退订和自动功能(我要订阅,而不要花费订阅者突然扔掉蹄子的蒸汽浴)
- 事件应该能够传输任何复杂性的数据/链接(我想在一行中订阅并获得全部数据而没有麻烦)
在哪里申请
- 当需要从任何对象转发数据而没有任何连接时,这是UI的理想选择。
- 有关数据更改的消息,类似于响应代码。
- 对于依赖注入
- 全局回调
弱点
- 由于检查了无效的订户和需要(我将在后面介绍),因此代码比类似的解决方案要慢
- 类/结构用作事件的核心,以免分配内存+最严重的问题,不建议在更新中发送垃圾事件)
一般意识形态
一般的意识形态是,对于我们而言,事件是一个特定且相关的数据包。 假设我们按下了界面/操纵杆上的按钮。 而且,我们希望发送带有按下特定按钮的迹象的事件以进行进一步处理。 处理点击的结果是界面的视觉变化和逻辑中的某种动作。 因此,可能在两个不同的地方进行处理/订阅。
在我的情况下,事件主体/数据包是什么样的:事件正文示例public struct ClickOnButtonEvent { public int ButtonID;
事件订阅的外观如下: public static void AddListener<T>(object listener, Action<T> action)
要订阅,我们需要指定:
作为订阅者的对象(通常是订阅的类本身,但这不是必须的,订阅者可以从类字段中指定一个类实例。
我们正在订阅的类型/事件。 这是聚合器的关键要素,对我们来说,某种类型的类是我们要监听和处理的事件。
最好订阅Awake和OnEnable;
例子 public class Example : MonoBehaviour { private void Awake() { EventAggregator.AddListener<ClickOnButtonEvent>(this, ClickButtonListener); } private void ClickButtonListener(ClickOnButtonEvent obj) { Debug.Log(" " + obj.ButtonID); } }
为了弄清楚什么是芯片,考虑一个更复杂的情况
我们具有以下字符图标:- 知道他们附加到哪个字符。
- 反映法力,生命值,经验值和状态(昏迷,失明,恐惧,疯狂)的数量
在这里您可以做几件事更改指标:
public struct CharacterStateChanges { public Character Character; public float Hp; public float Mp; public float Xp; }
更改否定状态:
public struct CharacterNegativeStatusEvent { public Character Character; public Statuses Statuses;
为什么在两种情况下我们都通过角色类? 这是事件订阅者及其处理程序:
private void Awake() { EventAggregator.AddListener<CharacterNegativeStatusEvent> (this, CharacterNegativeStatusListener); } private void CharacterNegativeStatusListener(CharacterNegativeStatusEvent obj) { if (obj.Character != _character) return; _currentStatus = obj.Statuses; }
这是我们处理事件并了解我们真正需要它的标记。
为什么不假设您直接订阅Character类呢? 垃圾邮件吗?首次亮相将很困难,最好是一组班级/事件创建自己的单独事件。
再次为什么在事件内部,只是不放置角色并从中获取一切?因此,这是可能的,但是通常在班级中存在可见性限制,并且从外部可能看不到事件的必要数据。
如果课程太重而无法用作标记?实际上,在大多数情况下,不需要标记;一组更新的类很少见。 通常,一个特定的实体需要一个事件-控制器/视图模型,该事件通常显示第一个字符的状态。 因此,总会有一个普通的解决方案-不同类型的ID(从inam到复杂的哈希等)。
到底是什么,它如何工作?
直接聚合代码 namespace GlobalEventAggregator public delegate void EventHandler<T>(T e); { public class EventContainer<T> : IDebugable { private event EventHandler<T> _eventKeeper; private readonly Dictionary<WeakReference, EventHandler<T>> _activeListenersOfThisType = new Dictionary<WeakReference, EventHandler<T>>(); private const string Error = "null"; public bool HasDuplicates(object listener) { return _activeListenersOfThisType.Keys.Any(k => k.Target == listener); } public void AddToEvent(object listener, EventHandler<T> action) { var newAction = new WeakReference(listener); _activeListenersOfThisType.Add(newAction, action); _eventKeeper += _activeListenersOfThisType[newAction]; } public void RemoveFromEvent(object listener) { var currentEvent = _activeListenersOfThisType.Keys.FirstOrDefault(k => k.Target == listener); if (currentEvent != null) { _eventKeeper -= _activeListenersOfThisType[currentEvent]; _activeListenersOfThisType.Remove(currentEvent); } } public EventContainer(object listener, EventHandler<T> action) { _eventKeeper += action; _activeListenersOfThisType.Add(new WeakReference(listener), action); } public void Invoke(T t) { if (_activeListenersOfThisType.Keys.Any(k => k.Target.ToString() == Error)) { var failObjList = _activeListenersOfThisType.Keys.Where(k => k.Target.ToString() == Error).ToList(); foreach (var fail in failObjList) { _eventKeeper -= _activeListenersOfThisType[fail]; _activeListenersOfThisType.Remove(fail); } } if (_eventKeeper != null) _eventKeeper(t); return; } public string DebugInfo() { string info = string.Empty; foreach (var c in _activeListenersOfThisType.Keys) { info += c.Target.ToString() + "\n"; } return info; } } public static class EventAggregator { private static Dictionary<Type, object> GlobalListeners = new Dictionary<Type, object>(); static EventAggregator() { SceneManager.sceneUnloaded += ClearGlobalListeners; } private static void ClearGlobalListeners(Scene scene) { GlobalListeners.Clear(); } public static void AddListener<T>(object listener, Action<T> action) { var key = typeof(T); EventHandler<T> handler = new EventHandler<T>(action); if (GlobalListeners.ContainsKey(key)) { var lr = (EventContainer<T>)GlobalListeners[key]; if (lr.HasDuplicates(listener)) return; lr.AddToEvent(listener, handler); return; } GlobalListeners.Add(key, new EventContainer<T>(listener, handler)); } public static void Invoke<T>(T data) { var key = typeof(T); if (!GlobalListeners.ContainsKey(key)) return; var eventContainer = (EventContainer<T>)GlobalListeners[key]; eventContainer.Invoke(data); } public static void RemoveListener<T>(object listener) { var key = typeof(T); if (GlobalListeners.ContainsKey(key)) { var eventContainer = (EventContainer<T>)GlobalListeners[key]; eventContainer.RemoveFromEvent(listener); } } public static string DebugInfo() { string info = string.Empty; foreach (var listener in GlobalListeners) { info += " " + listener.Key.ToString() + "\n"; var t = (IDebugable)listener.Value; info += t.DebugInfo() + "\n"; } return info; } } public interface IDebugable { string DebugInfo(); } }
让我们从主要开始这是一本字典,其中的键是类型,值是容器
public class EventContainer<T> : IDebugable
private static Dictionary<Type, object> GlobalListeners = new Dictionary<Type, object>();
为什么我们将容器存储为对象? 字典不知道如何存储泛型。 但是由于有了键,我们才能够快速将对象带到所需的类型。
容器包含什么? private event EventHandler<T> _eventKeeper; private readonly Dictionary<WeakReference, EventHandler<T>> _activeListenersOfThisType = new Dictionary<WeakReference, EventHandler<T>>();
它包含一个通用的多委托和一个集合,其中的键是作为订户的对象,而值是相同的处理程序方法。 实际上,该词典包含属于该类型的所有对象和方法。 结果,我们调用了一个多代表,而他调用了所有订阅者,这是一个“诚实”的事件系统,其中对订阅者没有任何限制,但是在大多数其他聚合器的幕后,这些类的集合可以通过特殊接口进行泛化,或者从实现系统的类继承消息。
调用多代表时,将进行检查以查看是否存在死键,清除集合中的尸体,然后接收具有相关订户的多代表。 这需要时间,但实际上,如果事件的功能分开,那么一个事件将有3-5个订阅者,因此检查不是那么可怕,舒适性的好处更加明显。 对于可能有一千个或更多订户的网络故事,最好不要使用此聚合器。 尽管问题仍然悬而未决-如果您删除对尸体的检查,这会更快-请从1k的订阅者数组中进行迭代,或从1k的订阅者中调用多代理。
使用特点
最好将订阅推送到Awake中。
如果某个对象正在主动打开/关闭,则最好订阅Awake和OnEnable,它不会进行两次签名,但是将排除无效的GameObject被淘汰的可能性。
开票活动最好不要在开始之前创建所有订户并注册。
聚合器在场景卸载时清除列表。 在某些聚合器中,建议清理场景加载-这是一个文件,场景加载事件在Awake / OnEnable之后发生,添加的订户将被删除。
聚合器具有-公共静态字符串DebugInfo(),您可以查看哪些类订阅了哪些事件。
GitHub存储库