TEngine 事件模块提供两种互补的事件模式:int/string 事件(委托回调,轻量灵活)和接口事件(Source Generator 生成,类型安全)。两种模式共享同一套 GameEvent 静态门面,可混用。
配合 UI 模块实现 MVE(Model - View - Event)事件驱动架构,模块间通过 GameEvent 解耦,UI 内部通过 AddUIEvent 自动管理事件生命周期。
TEngine 事件系统由三个核心组件构成:
| 组件 | 类型 | 职责 |
|---|---|---|
| GameEvent | 全局静态门面 | 持有 static readonly EventMgr _eventMgr,所有方法委托给内部 EventMgr 实例 |
| GameEventMgr | 局部作用域管理器 | 实现 IMemory,用于非 UI 类需要批量管理事件的场景,仅有 AddEvent + Clear() |
| GameEventHelper | Source Generator 生成类 | 源码中无 .cs 文件,由 [EventInterface] 特性在编译时自动生成,提供 Init() 注册所有接口事件 |
| 特性 | int/string 事件 | 接口事件 |
|---|---|---|
| 定义方式 | const int 或 string |
带 [EventInterface] 的接口 |
| 发送 | GameEvent.Send(int/string, ...) |
GameEvent.Get<IXxx>().OnXxx(...) |
| 监听 | GameEvent.AddEventListener / AddUIEvent |
实现接口 + 自动注册(Source Generator) |
| 类型安全 | 无编译检查 | 编译期检查 |
| 事件 ID 来源 | 自定义常量 | Source Generator 自动生成 IXxx_Event.OnXxx |
| 适用场景 | 简单通知、UI 内部事件 | 模块间通信、多参数复杂事件 |
// 接口事件版本 推荐使用!
GameEvent.Get<T>().DoSomeThing();
// int 版本:支持 0~6 个泛型参数
GameEvent.Send(int eventType);
GameEvent.Send<T1>(int eventType, T1 arg1);
GameEvent.Send<T1, T2>(int eventType, T1 arg1, T2 arg2);
// ... 最多 Send<T1,T2,T3,T4,T5,T6>
// string 版本:支持 0~5 个泛型参数
GameEvent.Send(string eventType);
GameEvent.Send<T1>(string eventType, T1 arg1);
// ... 最多 Send<T1,T2,T3,T4,T5>返回 bool(是否监听成功)。UI 内推荐用 AddUIEvent(自动清理,无需关心返回值)。
// int 版本:支持 0~6 个泛型参数
bool GameEvent.AddEventListener(int eventType, Action handler);
bool GameEvent.AddEventListener<T1>(int eventType, Action<T1> handler);
// ... 最多 AddEventListener<T1,T2,T3,T4,T5,T6>
// string 版本:支持 0~5 个泛型参数
bool GameEvent.AddEventListener(string eventType, Action handler);
bool GameEvent.AddEventListener<T1>(string eventType, Action<T1> handler);
// ... 最多 AddEventListener<T1,T2,T3,T4,T5>// int 版本:支持 0~5 个泛型参数 + Delegate 重载
GameEvent.RemoveEventListener(int eventType, Action handler);
GameEvent.RemoveEventListener<T1>(int eventType, Action<T1> handler);
// ... 最多 RemoveEventListener<T1,T2,T3,T4,T5>
GameEvent.RemoveEventListener(int eventType, Delegate handler); // Delegate 重载
// string 版本:支持 0~5 个泛型参数
GameEvent.RemoveEventListener(string eventType, Action handler);// 返回接口实例,内部调用 _eventMgr.GetInterface<T>()
T GameEvent.Get<T>();// 仅在游戏退出时调用,重置所有事件
GameEvent.Shutdown();注意:源码中没有
UnRegisterAll<T>()或UnRegisterAll()方法。清除事件请用RemoveEventListener(单个)、GameEventMgr.Clear()(局部批量)或GameEvent.Shutdown()(全局,仅退出时)。
实现 IMemory,仅支持 int eventType,最多 5 个泛型参数。没有 RemoveEvent 方法,通过 Clear() 一次性移除所有已注册事件。
// 正确初始化方式
private readonly GameEventMgr _eventMgr = new();
// 注册
_eventMgr.AddEvent(int eventType, Action handler);
_eventMgr.AddEvent<T1>(int eventType, Action<T1> handler);
// ... 最多 AddEvent<T1,T2,T3,T4,T5>
// 一次性移除所有
_eventMgr.Clear();在 UIBase 子类(UIWindow/UIWidget)中使用,注册的事件随窗口销毁自动清理,无需手动 RemoveEventListener:
// 在 RegisterEvent() 中使用,支持 0~4 个泛型参数
AddUIEvent(int eventType, Action handler);
AddUIEvent<T>(int eventType, Action<T> handler);
AddUIEvent<T, U>(int eventType, Action<T, U> handler);
AddUIEvent<T, U, V>(int eventType, Action<T, U, V> handler);
AddUIEvent<T, U, V, W>(int eventType, Action<T, U, V, W> handler);注意:尽量使用接口事件时,事件 ID 由 Source Generator 自动生成(见接口事件章节),无需手动定义。
接口事件通过 [EventInterface] + Source Generator 实现编译期类型检查。
1. 定义事件接口
// 必须标记 [EventInterface] 并指定事件组
[EventInterface(EEventGroup.GroupUI)]
public interface IBattleEvent
{
void OnHpChanged(int hp);
void OnGoldChanged();
void OnBattleEnded(bool isWin);
}2. Source Generator 自动生成(编译时)
编译后自动生成两个类(源码中不存在 .cs 文件,勿手动创建):
// 自动生成:事件 ID 常量类
public static class IBattleEvent_Event
{
public static readonly int OnHpChanged = RuntimeId.ToRuntimeId("IBattleEvent_Event.OnHpChanged");
public static readonly int OnGoldChanged = RuntimeId.ToRuntimeId("IBattleEvent_Event.OnGoldChanged");
public static readonly int OnBattleEnded = RuntimeId.ToRuntimeId("IBattleEvent_Event.OnBattleEnded");
}
// 自动生成:接口实现类(内部分发)
public class IBattleEvent_Gen : IBattleEvent
{
public void OnHpChanged(int hp) { /* 自动分发给所有监听者 */ }
public void OnGoldChanged() { /* ... */ }
public void OnBattleEnded(bool isWin) { /* ... */ }
}3. 必须在 GameApp.Entrance 最先调用 GameEventHelper.Init()
// GameApp.cs
public static void Entrance(object[] objects)
{
GameEventHelper.Init(); // ⚠️ 必须第一行!缺少此调用将导致所有接口事件无响应(无报错,极难排查)
_hotfixAssembly = (List<Assembly>)objects[0];
Utility.Unity.AddDestroyListener(Release);
StartGameLogic();
}4. 发送事件(通过接口方法调用)
// 通过 GameEvent.Get<T>() 获取接口实例并调用
GameEvent.Get<IBattleEvent>().OnHpChanged(newHp);
GameEvent.Get<IBattleEvent>().OnGoldChanged();
GameEvent.Get<IBattleEvent>().OnBattleEnded(true);5. 监听事件(通过生成的 ID 常量 + AddUIEvent/GameEventMgr)
// UI 内(AddUIEvent 自动清理)
protected override void RegisterEvent()
{
AddUIEvent<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);
AddUIEvent(IBattleEvent_Event.OnGoldChanged, OnGoldChanged);
AddUIEvent<bool>(IBattleEvent_Event.OnBattleEnded, OnBattleEnded);
}
// 非 UI 类(GameEventMgr 批量管理)
public void Init() => GameEvent.AddEventListener<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);
public void Dispose() => GameEvent.RemoveEventListener<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);// 发送
GameEvent.Send("OnGoldChanged");
GameEvent.Send<int>("OnHpChanged", 50);
// 监听(UI 内)
AddUIEvent("OnGoldChanged", OnGoldChanged);
AddUIEvent<int>("OnHpChanged", OnHpChanged);
// 非 UI 类(手动管理)
GameEvent.AddEventListener<int>("OnHpChanged", OnHpChanged);
GameEvent.RemoveEventListener<int>("OnHpChanged", OnHpChanged);string 版本内部通过 RuntimeId.ToRuntimeId 转为 int 处理,性能略低。优先使用 int/接口事件。
// 1. 定义接口事件
[EventInterface(EEventGroup.GroupUI)]
public interface IBattleEvent
{
void OnHpChanged(int hp);
}
// 2. 服务器消息处理层 - 发送事件
class BattleNetworkHandler
{
private void HandleHpPacket(HpPacket packet)
{
// 收到服务器消息,分发给所有关心血量变化的模块
GameEvent.Get<IBattleEvent>().OnHpChanged(packet.CurrentHp);
}
}
// 3. 战斗 UI - AddUIEvent 自动生命周期管理
[Window(UILayer.UI, "BattleMainUI")]
public class BattleMainUI : UIWindow
{
protected override void RegisterEvent()
{
// 自动随窗口销毁清理,无需手动 RemoveEventListener
AddUIEvent<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);
}
private void OnHpChanged(int hp)
{
_txtHp.text = $"HP: {hp}";
}
}| 类/方法 | int 事件 | string 事件 |
|---|---|---|
GameEvent.Send |
0~6 个泛型参数 | 0~5 个泛型参数 |
GameEvent.AddEventListener |
0~6 个泛型参数 | 0~5 个泛型参数 |
GameEvent.RemoveEventListener |
0~5 个泛型参数 + Delegate | 0~5 个泛型参数 + Delegate |
AddUIEvent(UIBase) |
0~4 个泛型参数 | 不推荐(用 int) |
GameEventMgr.AddEvent |
0~5 个泛型参数 | 不支持 |
// ❌ 错误:接口事件全部无响应,无任何报错,极难排查
public static void Entrance(object[] objects)
{
// GameEventHelper.Init(); ← 缺少!
StartGameLogic();
}
// ✅ 正确:必须第一行
public static void Entrance(object[] objects)
{
GameEventHelper.Init(); // 第一行,不可省略
_hotfixAssembly = (List<Assembly>)objects[0];
Utility.Unity.AddDestroyListener(Release);
StartGameLogic();
}// ❌ 错误:退出窗口不会自动清理,造成内存泄漏
public override void OnCreate()
{
GameEvent.AddEventListener<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);
}
// ✅ 正确:UIWindow/UIWidget 中用 AddUIEvent,随窗口销毁自动清理
protected override void RegisterEvent()
{
AddUIEvent<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);
}// ❌ 错误:手写 int 常量,容易重复或拼错
public const int OnHpChanged = 1001;
// ✅ 正确:使用接口事件的 Source Generator 生成 ID,或用 RuntimeId.ToRuntimeId
public static readonly int OnHpChanged = RuntimeId.ToRuntimeId("IXxx_Event.OnHpChanged");
// 更推荐:直接使用 IBattleEvent_Event.OnHpChanged(Source Generator 自动生成)// ❌ 错误:发送 int,回调接收 string → 运行时异常
GameEvent.Send<int>(IBattleEvent_Event.OnHpChanged, hp);
AddUIEvent<string>(IBattleEvent_Event.OnHpChanged, OnHp); // 类型不匹配!
// ✅ 正确:接口事件模式可编译期检查,推荐使用
GameEvent.Get<IBattleEvent>().OnHpChanged(hp); // 类型安全// ❌ 错误:Init 中注册但 Dispose 中没有移除,导致回调引用已销毁对象
public class PlayerSystem
{
public void Init() => GameEvent.AddEventListener(GameEventDef.OnGoldChanged, OnGoldChanged);
// 缺少 RemoveEventListener → 内存泄漏
}
// ✅ 正确:使用 GameEventMgr 批量清理
public class PlayerSystem
{
public void Init() => GameEvent.AddEventListener(GameEventDef.OnGoldChanged, OnGoldChanged);
public void Dispose() => GameEvent.RemoveEventListener(GameEventDef.OnGoldChanged, OnGoldChanged);
}// ❌ 错误:源码中不存在这些方法
GameEvent.UnRegisterAll<int>(eventType);
GameEvent.UnRegisterAll();
// ✅ 正确:按需使用以下方式
GameEvent.RemoveEventListener(eventType, handler); // 移除单个
_eventMgr.Clear(); // 局部批量(GameEventMgr)
GameEvent.Shutdown(); // 全局(仅游戏退出时)// ❌ 错误:GameEvent 中不存在 RegisterListener<T>() 方法
GameEvent.RegisterListener<IBattleEvent>(implementation);
// ✅ 正确:接口事件的实现类由 Source Generator 自动生成和注册
// 只需确保 GameEventHelper.Init() 在 GameApp.Entrance 中最先调用
GameEventHelper.Init();// ✅ 推荐:接口事件,编译期类型安全
[EventInterface(EEventGroup.GroupUI)]
public interface IBattleEvent { void OnHpChanged(int hp); }
GameEvent.Get<IBattleEvent>().OnHpChanged(hp); // 发送
AddUIEvent<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged); // 监听// ✅ 推荐:RegisterEvent 中 AddUIEvent,自动生命周期管理
protected override void RegisterEvent()
{
AddUIEvent<int>(IBattleEvent_Event.OnHpChanged, OnHpChanged);
// 无需手动 RemoveEventListener,UI 销毁时自动清理
}| 规则 | 说明 |
|---|---|
| 方法命名 | On + 过去式动词 + 名词:OnGoldChanged、OnBattleEnded |
| 接口命名 | I + 动词/业务名词:IBattleEvent、ITradeEvent |
| 禁止自定义 ID | 使用接口事件 |
| 禁止 | 手写 int 常量作为事件 ID,应用 Source Generator 生成的常量 |