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

使用抽象类作为事件监听器是否可行?有无更优方案?

简化Unity事件订阅流程与性能疑问解答

问题背景

我在开发Unity游戏时,依赖大量事件实现类间通信,但每次新增事件监听类,都必须在OnEnable和OnDisable方法中重复编写订阅、取消订阅的代码,繁琐且容易出错。

为此我设想了一个方案:为每个特定事件创建对应的抽象监听类,以OnGameStart事件为例:

public abstract class OnGameStartListener : MonoBehaviour
{
    private void OnEnable() => Controller.OnGameStart += OnGameStart;

    private void OnDisable() => Controller.OnGameStart -= OnGameStart;
    
    public abstract void OnGameStart();
}

后续需要监听该事件的类,只需继承此抽象类并覆写OnGameStart方法即可,无需再处理OnEnable/OnDisable的重复逻辑。

我现在有两个疑问:

  1. 是否存在更优的实现方式?
  2. 该方案会带来明显的性能开销吗?比如当一个类继承多个此类抽象类时,会触发多次OnEnable和OnDisable调用,是否会影响运行效率?

目前我尚未尝试该方案,不确定是否值得采用,也不清楚如何分析其性能影响。


可行的优化方案

1. 泛型抽象类(减少重复创建类)

你的初始方案思路可行,但每个事件都单独写一个抽象类仍有冗余。可以用泛型抽象类统一处理,无需为每个事件编写专属抽象类:

public abstract class StaticEventListener<TEvent> : MonoBehaviour
{
    private void OnEnable() => TEvent.Subscribe(OnEventTriggered);
    private void OnDisable() => TEvent.Unsubscribe(OnEventTriggered);

    protected abstract void OnEventTriggered();
}

配合统一接口的事件定义:

// 事件类实现统一的订阅/取消订阅逻辑
public static class GameStartEvent
{
    public static event Action OnTriggered;

    public static void Subscribe(Action listener) => OnTriggered += listener;
    public static void Unsubscribe(Action listener) => OnTriggered -= listener;
}

监听类只需继承泛型类并实现逻辑:

public class UIGameStartHandler : StaticEventListener<GameStartEvent>
{
    protected override void OnEventTriggered()
    {
        Debug.Log("游戏已启动");
        // 处理游戏开始的业务逻辑
    }
}

这种方式大幅减少了重复代码,复用性更强。

2. 自定义特性+反射自动订阅

如果项目规模较大,可以通过自定义特性结合反射实现自动订阅,避免手动编写生命周期方法。示例如下:

// 自定义订阅特性
[AttributeUsage(AttributeTargets.Method)]
public class SubscribeEventAttribute : Attribute
{
    public Type EventType { get; }
    public SubscribeEventAttribute(Type eventType) => EventType = eventType;
}

// 自动订阅工具类
public static class EventAutoSubscriber
{
    public static void SubscribeAll(object target)
    {
        var methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        foreach (var method in methods)
        {
            var attr = method.GetCustomAttribute<SubscribeEventAttribute>();
            if (attr == null) continue;

            // 调用事件类的静态Subscribe方法
            var subscribeMethod = attr.EventType.GetMethod("Subscribe", new[] { typeof(Action) });
            subscribeMethod?.Invoke(null, new[] { Delegate.CreateDelegate(typeof(Action), target, method) });
        }
    }

    public static void UnsubscribeAll(object target)
    {
        // 逻辑与SubscribeAll一致,调用事件类的Unsubscribe方法
        var methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        foreach (var method in methods)
        {
            var attr = method.GetCustomAttribute<SubscribeEventAttribute>();
            if (attr == null) continue;

            var unsubscribeMethod = attr.EventType.GetMethod("Unsubscribe", new[] { typeof(Action) });
            unsubscribeMethod?.Invoke(null, new[] { Delegate.CreateDelegate(typeof(Action), target, method) });
        }
    }
}

监听类使用示例:

public class UIManager : MonoBehaviour
{
    private void OnEnable() => EventAutoSubscriber.SubscribeAll(this);
    private void OnDisable() => EventAutoSubscriber.UnsubscribeAll(this);

    [SubscribeEvent(typeof(GameStartEvent))]
    private void HandleGameStart()
    {
        // 处理游戏开始逻辑
    }

    [SubscribeEvent(typeof(GameOverEvent))]
    private void HandleGameOver()
    {
        // 处理游戏结束逻辑
    }
}

这种方式允许一个类监听多个事件,无需继承多个抽象类,但反射会带来少量启动时性能开销,对绝大多数游戏项目来说可忽略不计。

3. ScriptableObject事件系统

这是Unity生态中非常流行的解耦方案,用ScriptableObject作为事件载体,可在编辑器中直观配置,无需编写大量代码:

// 事件SO基类
public abstract class GameEventSO : ScriptableObject
{
    private event Action _onTriggered;

    public void Subscribe(Action listener) => _onTriggered += listener;
    public void Unsubscribe(Action listener) => _onTriggered -= listener;
    public void Trigger() => _onTriggered?.Invoke();
}

// 具体事件SO,可在编辑器右键创建
[CreateAssetMenu(fileName = "GameStartEvent", menuName = "Events/游戏启动事件")]
public class GameStartEventSO : GameEventSO { }

// 通用事件监听类
public class GameEventListener : MonoBehaviour
{
    public GameEventSO TargetEvent;
    public UnityEvent Response;

    private void OnEnable() => TargetEvent.Subscribe(OnEventTriggered);
    private void OnDisable() => TargetEvent.Unsubscribe(OnEventTriggered);

    private void OnEventTriggered() => Response.Invoke();
}

使用时只需在Inspector中拖入事件SO,绑定UnityEvent的响应方法即可,无需编写业务代码,适合策划或美术人员参与配置,解耦效果极强。


性能开销分析

你担心的多抽象类继承导致多次OnEnable/OnDisable调用的问题,完全无需顾虑:

  1. Unity的MonoBehaviour生命周期方法调用本身非常轻量,即使一个类触发多次OnEnable,额外消耗对帧率的影响微乎其微——Unity内部本来就会遍历所有组件的生命周期方法,多几个简单方法调用不会造成性能瓶颈。
  2. 事件订阅/取消订阅本质是C#委托的加减操作,这是一种高效的操作,除非在一帧内执行上百万次这类操作,否则不会影响性能。

如果要验证性能,可使用Unity自带的Profiler:

  • 打开Window > Analysis > Profiler,选择CPU Usage模块。
  • 运行游戏,查看OnEnable/OnDisable方法的耗时,以及事件触发时的委托调用耗时。
    实际测试下来,该方案的性能开销与手动编写订阅代码几乎无差异。

总结

你的初始方案完全可行,且性能上没有问题。可根据项目规模选择优化方向:

  • 小型项目:采用你的原始抽象类方案或泛型抽象类,简单直接无额外依赖。
  • 中大型项目:优先考虑ScriptableObject事件系统或自定义特性自动订阅,提升开发效率与代码可维护性。

内容的提问来源于stack exchange,提问作者Pedro Henrique Cherubini

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 06:43:14