You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity事件管理器设计:如何实现无依赖的组件解耦?

解决方案:抽象接口+依赖注入实现无依赖的事件总线

要解决你遇到的「EventManager带来硬依赖、原生事件耦合」的两难问题,核心思路是通过抽象事件总线接口消除对具体实现的依赖,同时用强类型事件定义替代字符串和松散字典,兼顾解耦、可复用性与类型安全。

1. 定义抽象事件总线接口

先把事件的核心操作(订阅、取消订阅、触发)抽象成接口,让生产者和消费者只依赖这个抽象层,而非具体的EventManager类:

// 所有事件的标记接口
public interface IEvent { }

// 事件总线的抽象接口
public interface IEventBus
{
    void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : IEvent;
    void Unsubscribe<TEvent>(Action<TEvent> handler) where TEvent : IEvent;
    void Publish<TEvent>(TEvent eventData) where TEvent : IEvent;
}

2. 实现通用事件总线类

基于接口实现一个通用的事件总线,替代原来的静态EventManager,用泛型和强类型避免字符串魔法值与类型转换问题:

public class EventBus : IEventBus
{
    private readonly Dictionary<Type, Delegate> _eventHandlers = new();

    public void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : IEvent
    {
        var eventType = typeof(TEvent);
        if (!_eventHandlers.ContainsKey(eventType))
        {
            _eventHandlers[eventType] = handler;
        }
        else
        {
            _eventHandlers[eventType] = Delegate.Combine(_eventHandlers[eventType], handler);
        }
    }

    public void Unsubscribe<TEvent>(Action<TEvent> handler) where TEvent : IEvent
    {
        var eventType = typeof(TEvent);
        if (_eventHandlers.TryGetValue(eventType, out var existingHandlers))
        {
            var newHandlers = Delegate.Remove(existingHandlers, handler);
            if (newHandlers == null)
            {
                _eventHandlers.Remove(eventType);
            }
            else
            {
                _eventHandlers[eventType] = newHandlers;
            }
        }
    }

    public void Publish<TEvent>(TEvent eventData) where TEvent : IEvent
    {
        if (_eventHandlers.TryGetValue(typeof(TEvent), out var handler))
        {
            ((Action<TEvent>)handler)?.Invoke(eventData);
        }
    }
}

3. 定义强类型事件

用具体类定义事件数据,替代原来的字符串和字典,保证类型安全:

// 金币收集事件
public class CoinCollectedEvent : IEvent
{
    public int Amount { get; }

    public CoinCollectedEvent(int amount)
    {
        Amount = amount;
    }
}

4. 改造生产者与消费者

现在生产者和消费者只依赖IEventBus接口,不再绑定具体实现:

生产者代码

public class CollectableProducer : MonoBehaviour
{
    [SerializeField] private IEventBus _eventBus;

    void Awake()
    {
        // 若Inspector未赋值,自动获取全局事件总线实例
        _eventBus ??= EventBusHolder.Instance;
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        _eventBus.Publish(new CoinCollectedEvent(1));
    }
}

消费者代码

public class CoinConsumer : MonoBehaviour
{
    private int _coins;
    [SerializeField] private IEventBus _eventBus;

    void Awake()
    {
        _eventBus ??= EventBusHolder.Instance;
    }

    void OnEnable()
    {
        _eventBus.Subscribe<CoinCollectedEvent>(OnCoinCollected);
    }

    void OnDisable()
    {
        _eventBus.Unsubscribe<CoinCollectedEvent>(OnCoinCollected);
    }

    private void OnCoinCollected(CoinCollectedEvent eventData)
    {
        _coins += eventData.Amount;
    }
}

5. 提供全局事件总线实例

在Unity中创建一个单例持有者,方便全局获取事件总线:

public class EventBusHolder : MonoBehaviour
{
    public static IEventBus Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = new EventBus();
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

核心优势

  • 无硬依赖:生产者和消费者仅依赖抽象的IEventBus,可直接移植到任何实现了该接口的项目中
  • 完全解耦:生产者不知道谁订阅事件,消费者不知道谁触发事件
  • 类型安全:强类型事件避免了字符串写错、字典类型转换错误等问题
  • 灵活扩展:后续可给EventBus添加线程安全、事件日志等功能,无需修改生产者/消费者代码

内容的提问来源于stack exchange,提问作者KarlB25

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 20:35:18