这是完全借助AI设计实现的一个Unity中的事件系统。
零、仓库
一、需求
- 方便的、统一的调用入口。
- 每个事件可以被写作一个Struct或者Class,事件的参数就在Struct或Class中声明。
- 基础的观察者模式事件相关功能。观察者订阅,被观察者发布事件,一个事件总线管理类负责管理。
- 事件系统的发布(Publish)过程 0GC,订阅阶段允许初始化 GC(反射扫描、创建订阅对象),过程中没有装箱拆箱。
- 通常的订阅和取消订阅的代码会卸载OnEnable和OnDisable中,但是这些代码是和业务逻辑无关的,占用书写类代码的空间让阅读代码不友好。让订阅、取消代码放在一个公共父类中或同GameObject的其他类中自动扫描。
二、具体设计
2.1 方便、统一的调用入口
cs
/// <summary>
/// 提供一个全局静态事件总线。
/// 大多数情况下你可以直接用 SiYangEventBus.Global。
/// </summary>
public static class SiYangEventBus
{
/// <summary>
/// 全局事件总线实例。
/// </summary>
public static readonly IEventBus Global = new EventBus();
}
使用这个框架时候的代码都是类似:
2.2 观察者模式实现事件管理
2.2.1 一个事件总线管理类负责管理
核心是:
private readonly Dictionary<Type, ISubscriptionList> _subscriptions = new(32);
Key是Type,即事件Class或者Struct的Type,这样每个事件就对应了一组这个事件的观察者列表。
/// <summary> /// 某个具体事件类型 T 的订阅列表。 /// </summary> private sealed class SubscriptionList<T> : ISubscriptionList{
private readonly List<EventSubscription<T>> _list = new(32);}
List<EventSubscription<T>> _list 是真正的观察者列表。
2.2.2 订阅、发布、清理
定义接口
cs
public interface IEventBus
{
IDisposable Subscribe<T>(Action<T> handler, object owner = null, int priority = 0);
void Publish<T>(T evt);
/// <summary>
/// 清除所有订阅。
/// 通常只在切场景或重置系统时使用。
/// </summary>
void Clear();
}
总线管理实现:
cs
public sealed class EventBus : IEventBus
{
private readonly Dictionary<Type, ISubscriptionList> _subscriptions = new(32);
public IDisposable Subscribe<T>(Action<T> handler, object owner = null, int priority = 0) {
// 注册事件,向_subscriptions 添加 观察者,保存handler。
}
public void Publish<T>(T evt)
{
// 发布事件,从_subscriptions 拿 观察者list,执行观察者记录的Action handler
}
// 开发主动调用
public void Clear()
{
_subscriptions.Clear();
}
}
2.3 低GC,在订阅、发布流程中0GC
-
使用泛型,没有装箱拆箱。
-
List只扩容不缩容。
-
不适用linq/Foreach,只用while或for。
2.4 不在类的Onenable和OnDisable中写
注册时间和取消注册时间都是业务逻辑无关代码,不写在类中。但是事件的逻辑要卸载类中。
最好达到下面的效果:
class A
{
一个标签代表这是个事件的观察者
private void SomeEventListener(SomeEvent event)
{
// 事件触发的逻辑
}
}
-
思路1 继承一个父类,父类收集子类的标签方法,然后将其注册到事件总线。
-
思路2 让A继承一个接口I,挂在A方法的物体上挂载一个脚本Host,Host脚本收集所有的component,如果是继承接口I的,收集标签方法然后注册到事件总线。
这样注册和停止注册的代码就转移到了其他地方。