unity3d Unity -使用事件管理器类型类进行解耦

kb5ga3dv  于 2022-11-15  发布在  其他
关注(0)|答案(1)|浏览(146)

我一直在尝试尽可能地使用事件来分离组件,最近开始阅读更多关于“事件管理器”类的内容。然而,我所看到的实现解决了一个关于分离的问题,但却产生了另一个问题。让我给予一个我在网上找到的一些代码的例子,以及我对它的疑问。
在这个例子中,当收集硬币时,会触发一个事件(使用静态EventManager类),我们有一个事件的生成者和一个订阅者。生成者代码:

public class Producer : MonoBehaviour {
  void OnTriggerEnter2D(Collider2D other) {
    EventManager.TriggerEvent("collectCoins", new Dictionary<string, object> { { "amount", 1 } });
  }
}

消费者/用户:

public class Consumer : MonoBehaviour {
  private int coins;

  void OnEnable() {
    EventManager.StartListening("collectCoins", OnCollectCoins);
  }

  void OnDisable() {
    EventManager.StopListening("collectCoins", OnCollectCoins);
  }
  
  void OnCollectCoins(Dictionary<string, object> message) {
    var amount = (int) message["amount"];
    coins += amount;
  }
}

现在假设事件的消费者是一个UIManager,它是一个顶级类,非常特定于单个游戏,我们不太担心可重用性。这将工作得很好,对EventManager类的依赖可能不是什么大问题。
但是对于事件的生成者来说,如果它是我写的一个“Collectable”类,那么我希望它尽可能独立,并且能够在不重写它的情况下将它弹出到另一个项目中。但是我不能。现在我将该类带到的任何项目都需要一个具有相同方法和参数的静态EventManager类,否则它将无法工作。
如果我们看一下不使用EventManager设置事件的更普通的风格,我通常会这样做:

public class Producer : MonoBehaviour
{
    public static event Action<int> OnCoinsCollected;
    void OnTriggerEnter2D(Collider2D other)
    {
        OnCoinsCollected?.Invoke(1);
    }
}

在这里,我们有一个类,可以很容易地转移到另一个项目,而不需要返工。
当我第一次读到EventManager时,我认为它的目的是让EventManager处理订阅/取消订阅逻辑并触发事件,这意味着生产者和消费者都不知道彼此的任何信息,也不知道EventManager的任何信息。在我看来,这是真正的分离,也是解耦的意义所在。
我希望能在这方面得到一些建议。以问题的形式来表达--假设EventManager类是解决这个问题的最佳方法,我应该如何构建一个EventManager类,它可以处理事件的调用以及订阅方面的事情,这样它下面的类就不知道彼此,也不知道EventManager?

2wnc66cl

2wnc66cl1#

像这样的双向解耦可以通过依赖注入来实现。

using System;
using UnityEngine;

sealed class EventManager : MonoBehaviour
{
    private (Action<Action>, Action<Action>, Action)[] config;

    void Awake() => config = new (Action<Action>, Action<Action>, Action)[]
    {
        (x => Coin.Collected += x, x => Coin.Collected -= x, Inventory.Instance.AddCoin)
    };

    void OnEnable()
    {
        foreach((var add, _, var listener) in config)
        {
            add(listener);
        }
    }

    void OnDisable()
    {
        foreach((_, var remove, var listener) in config)
        {
            remove(listener);
        }
    }
}

构建这样一个事件管理器的一种可能方法是创建一个EditorWindow,它允许使用弹出窗口将任意事件链接到任意方法,然后让事件管理器在运行时将它们绑定在一起(可能在DI框架的帮助下)。
您也可以使用以惯例为基础的系结。例如,您可以自动将所有使用命名惯例“On{Type}{Name}”的方法系结至在名称为“{Type}”的型别上找到的名称为“{Name}”的静态事件(如果它们的参数清单相同)。以下是一个简单的范例:

sealed class EventManager : MonoBehaviour
{
    readonly List<(Action<Action>, Action<Action>, Action)> config = new List<(Action<Action>, Action<Action>, Action)>();

    void Awake() => BindStaticEventsToMethodsFollowingOnTypeMethodNamingConvention();

    private void BindStaticEventsToMethodsFollowingOnTypeMethodNamingConvention()
    {
        var types = GetType().Assembly.GetTypes();
        var events = types.SelectMany(t => t.GetEvents(BindingFlags.Static | BindingFlags.Public));
        var listenerNamesToEvents = new Dictionary<string, EventInfo>(events.Select(e => new KeyValuePair<string, EventInfo>("On" + e.DeclaringType.Name + e.Name, e)));
        foreach(var method in types.SelectMany(t => t.GetMethods()))
        {
            if(listenerNamesToEvents.TryGetValue(method.Name, out EventInfo eventInfo))
            {
                foreach(var instance in FindObjectsOfType(method.DeclaringType))
                {
                    var listenerComponent = instance;
                    var listenerMethod = method;
                    config.Add((x => eventInfo.AddEventHandler(null, x), x => eventInfo.RemoveEventHandler(null, x), () => listenerMethod.Invoke(listenerComponent, null)));
                }
            }
        }
    }

    void OnEnable()
    {
        foreach((var add, _, var listener) in config)
        {
            add(listener);
        }
    }

    void OnDisable()
    {
        foreach((_, var remove, var listener) in config)
        {
            remove(listener);
        }
    }
}

相关问题