主题
触发器与事件系统
触发器(Trigger)是事件系统的核心组件,用于订阅和响应游戏中的各种事件。系统基于弱引用和 EventAggregator 实现,支持自动垃圾回收和生命周期管理。
核心类型
| 类型 | 说明 |
|---|---|
ITriggerEvent<TSelf> | 事件标记接口,所有可被触发器订阅的事件必须实现 |
Trigger<TEvent> | 泛型触发器,订阅并处理单一类型事件(推荐) |
TriggerDynamic | 动态触发器,可同时订阅多种事件类型 |
EventAggregator | 事件聚合器,管理订阅者的弱引用和事件分发 |
IEventSender | 事件发送者接口,拥有 EventAggregator 的对象可发布事件 |
TriggerEventHandle<TEvent> | 事件处理器委托:Task<bool> (object sender, TEvent eventArgs) |
架构
IEventSender (Entity, Unit, Game, Item, Buff, Ability, ...)
│
├── Aggregator: EventAggregator
│ │
│ ├── Subscribe<TEvent>(wrTrigger) ← 注册弱引用
│ ├── Unsubscribe<TEvent>(wrTrigger) ← 注销
│ └── Publish<TEvent>(sender, event) ← 分发事件
│
└── Publish<TEvent>(event) ← 便捷发布方法
Trigger<TEvent> / TriggerDynamic
│
├── Register(sender) ← 订阅事件发送者
├── Unregister(sender) ← 取消订阅
├── Execute(sender, eventArgs) ← 处理事件
├── IsEnabled ← 启用/禁用
└── Destroy() ← 销毁并释放资源Trigger<TEvent>(推荐)
处理单一事件类型的触发器,类型安全且性能优于 TriggerDynamic。
构造
csharp
// 完整版:需返回 bool 表示处理结果
var trigger = new Trigger<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("实体死亡: {Entity}", e.Entity);
return true;
});
// 简化版:无需返回值
var trigger = new Trigger<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("实体死亡: {Entity}", e.Entity);
});keepReference 参数(默认 true)控制是否保持硬引用防止被 GC 回收。
注册与注销
csharp
// 注册到事件发送者
trigger.Register(someEntity);
// 从事件发送者注销
trigger.Unregister(someEntity);
// 可注册到多个发送者
trigger.Register(entity1);
trigger.Register(entity2);控制与销毁
csharp
trigger.IsEnabled = false; // 暂停处理,保持订阅
trigger.IsEnabled = true; // 恢复处理
trigger.Destroy(); // 销毁,释放所有订阅Subscribe 便捷方法
IEventSender 的扩展方法,创建触发器并自动注册:
csharp
// 等价于 new Trigger<T>(handler) + trigger.Register(sender)
var trigger = someEntity.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("单位被击杀: {Entity}, 凶手: {Killer}", e.Entity, e.Killer);
return true;
});
// 简化版:无需返回布尔值
var trigger = someEntity.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("单位被击杀: {Entity}, 凶手: {Killer}", e.Entity, e.Killer);
});RegisterGlobal 便捷方法
将触发器注册到全局事件总线(Game.Instance):
csharp
// 等价于 trigger.Register(Game.Instance)
new Trigger<EventEntityDeath>(async (s, e) =>
{
Game.Logger.LogInformation("实体死亡: {Entity}", e.Entity);
}).RegisterGlobal();
// 已有触发器实例也可以使用
trigger.RegisterGlobal();TriggerDynamic
动态触发器,可同时处理多种事件类型。处理器接收 dynamic 类型参数。
csharp
var trigger = new TriggerDynamic(async (sender, eventArgs) =>
{
Game.Logger.LogInformation("收到事件: {Type}", eventArgs.GetType().Name);
return true;
});
// 注册多种事件
trigger.AddEvent<EventEntityDeath>(someEntity);
trigger.AddEvent<EventUnitCreate>(someEntity);
// 注销特定事件
trigger.RemoveEvent<EventEntityDeath>(someEntity);
trigger.Destroy();> 仅在需要处理多种事件类型时使用 TriggerDynamic,单一事件类型请用 Trigger<TEvent>。
Game.Subscribe 静态方法
直接在 Game 上订阅全局事件,最简洁的全局事件订阅方式:
csharp
// 等价于 Game.Instance.Subscribe<T>(handler)
Game.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("实体死亡: {Entity}", e.Entity);
return true;
});
// 简化版:无需返回布尔值
Game.Subscribe<EventUnitCreate>(async (sender, e) =>
{
Game.Logger.LogInformation("单位创建: {Unit}", e.Unit);
});IEventSender
实现 IEventSender 的对象可以发布事件。框架中主要的事件发送者:
| 发送者 | 说明 |
|---|---|
Game.Instance | 全局游戏事件(EventGameStart、EventGameTick 等) |
Entity / Unit | 实体/单位事件(死亡、创建、属性变更等) |
Ability | 技能事件 |
Buff | Buff 事件 |
Item | 物品事件 |
Player | 玩家事件 |
发布事件:
csharp
sender.Publish(new EventEntityDeath(entity, DeathType.Kill, context));高性能发布(缓存委托,避免重复查找订阅者):
csharp
var publisher = sender.GetPublisher<EventEntityDeath>();
publisher?.Invoke(new EventEntityDeath(entity, DeathType.Kill, context));事件分级传播机制
当游戏对象发布事件时,事件会自动沿层级向上传播到多个 IEventSender。开发者可以在不同层级注册触发器,从而选择监听的粒度。
传播层级
| 对象类型 | 传播路径(从特定到全局) | 说明 |
|---|---|---|
Unit | 实例 → GameDataUnit → Game.Instance | 三级:特定单位、同类型所有单位、全局 |
Item | 实例 → GameDataItem → Game.Instance | 三级:特定物品、同类型所有物品、全局 |
Player | 实例 → Game.Instance | 二级:特定玩家、全局 |
Scene | 实例 → Game.Instance | 二级:特定场景、全局 |
实际含义
以 Unit 为例,当一个单位死亡时(发布 EventEntityDeath),事件会依次传播到:
- 实例级(注册到该
Unit)— 仅当这个特定单位死亡时触发 - 类型级(注册到该单位的
GameDataUnit)— 当所有同类型单位死亡时触发 - 全局级(注册到
Game.Instance)— 当任何单位死亡时触发
使用示例
csharp
// 1. 全局级:监听所有单位死亡
Game.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("全局:实体 {Entity} 死亡", e.Entity);
});
// 2. 类型级:仅监听某种类型单位的死亡(如所有"小兵"类型单位)
var minionData = ScopeData.GameDataUnit.Minion;
minionData.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("小兵类型单位死亡: {Entity}", e.Entity);
});
// 3. 实例级:仅监听特定单位的死亡
specificUnit.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("这个单位死亡了");
});> 注意:GameDataUnit、GameDataItem 等数据定义类型本身也实现了 IEventSender 接口,因此可以直接对其调用 Subscribe 或 Register。
事件类型速查
游戏全局事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventGameStart | 双端 | — | 游戏开始 |
EventGameTick | 双端 | RealTimeDeltaInMilliseconds | 每帧更新 |
EventGameCmdResultNotify | 双端 | CmdResult | 命令执行结果通知 |
EventGamePropertyChange | 双端 | Property | 游戏属性变更 |
实体事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventEntityDeath | 双端 | Entity, DeathType, Killer, KillerUnit | 实体死亡 |
EventEntityRemove | 双端 | Entity | 实体移除(客户端:离开视野) |
EventEntityDamageTaken | 服务端 | Entity, EffectDamage, DamageCurrent, DamageOriginal | 受到伤害 |
EventEntityOrderStage | 服务端 | Entity, Order, Stage, StagePrevious, Target | 指令阶段变更 |
EventEntityPositionChangeStart | 服务端 | Entity, IsMoverMove | 开始移动 |
EventEntityPositionChangeEnd | 服务端 | Entity, IsMoverMove | 停止移动 |
客户端通知事件
| 事件 | 属性 | 说明 |
|---|---|---|
EventDamageNotification | DamageInstance, IsCritical, IsFatal, Caster, Target | 伤害通知(用于飘字/UI) |
EventHealNotification | HealingInstance, HealAmount, IsCritical, Caster, Target, VitalData | 治疗通知 |
单位事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventUnitCreate | 双端 | Unit | 单位创建(客户端:进入视野) |
EventUnitRevive | 双端 | Unit | 单位复活 |
EventUnitPropertyChange | 双端 | Unit, Property | 单位属性变更 |
EventUnitPropertyComplexFinalChange | 双端 | Unit, Property, NewValue | 复合属性最终值变更 |
EventAbilityAttached | 双端 | Ability | 技能附加到单位 |
EventAbilityRemoved | 双端 | Ability | 技能从单位移除 |
EventAbilityPropertyChange | 双端 | Ability, Property | 技能属性变更 |
EventBuffAttached | 双端 | Buff | Buff 附加到单位 |
EventBuffRemoved | 双端 | Buff | Buff 从单位移除 |
EventBuffPropertyChange | 双端 | Buff, Property | Buff 属性变更 |
玩家事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventPlayerPropertyChange | 双端 | Player, Property | 玩家属性变更 |
EventPlayerUserConnected | 服务端 | Player, User, OldUser | 用户连接 |
EventPlayerUserDisconnected | 服务端 | Player, User | 用户断开 |
EventPlayerConnectionUnresponsive | 服务端 | Player, User | 连接暂时失去响应 |
EventPlayerConnectionRestored | 服务端 | Player, User | 连接恢复响应 |
EventPlayerUserNameFetched | 服务端 | Player, User, Name | 获取到用户名 |
EventPlayerUserActivelyQuit | 服务端 | Player, User | 用户主动退出 |
EventPlayerStatusChanged | 服务端 | Player, Status, OldStatus | 玩家状态变更 |
EventPlayerKeyDown | 服务端 | Player, Key, Modifiers, IsRepeat | 按键按下 |
EventPlayerKeyUp | 服务端 | Player, Key, Modifiers | 按键抬起 |
EventPlayerMainUnitChanged | 客户端 | Player, Unit | 主控单位变更 |
EventPlayerUIPropertyChange | 双端 | Player, Property | UI 属性变更 |
物品事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventItemSlotChange | 双端 | Item, Slot, SlotPrevious | 物品槽位变更 |
EventItemStackChange | 双端 | Item, Stack, StackPrevious | 堆叠数变更 |
EventItemLevelChange | 双端 | Item, Level, LevelPrevious | 等级变更 |
EventItemQualityChange | 双端 | Item, Quality, QualityPrevious | 品质变更 |
场景与区域事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventSceneLoadStart | 双端 | Scene | 场景开始加载 |
EventSceneLoadEnd | 双端 | Scene | 场景加载完成 |
EventRegionEnter | 服务端 | Region, Entity | 实体进入区域 |
EventRegionExit | 服务端 | Region, Entity | 实体离开区域 |
其他事件
| 事件 | 可用端 | 属性 | 说明 |
|---|---|---|---|
EventEffectExecuted | 服务端 | Effect | 效果执行完成 |
EventRewardExecuted | 服务端 | Reward | 奖励执行完成 |
EventComponentReplicated | 客户端 | Component, Host | 组件同步完成 |
EventComponentDestroying | 客户端 | Component, Host | 组件即将销毁 |
注册模式
在 IGameClass 中注册触发器应使用 OnGameTriggerInitialization(不是 OnGameDataInitialization):
csharp
public static void OnRegisterGameClass()
{
Game.OnGameTriggerInitialization += OnGameTriggerInitialization;
}
private static void OnGameTriggerInitialization()
{
if (Game.GameModeLink != ScopeData.GameDataGameMode.MapGameMode) return;
#if SERVER
// 全局事件:使用 Game.Subscribe 订阅
Game.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("实体 {Entity} 死亡,击杀者: {Killer}",
e.Entity, e.Killer);
});
// 玩家连接
Game.Subscribe<EventPlayerUserConnected>(async (sender, e) =>
{
Game.Logger.LogInformation("玩家 {Player} 连接,用户: {UserId}",
e.Player, e.User.UserId);
});
// 区域事件:注册到特定区域
myRegion.Subscribe<EventRegionEnter>(async (sender, e) =>
{
Game.Logger.LogInformation("实体 {Entity} 进入区域", e.Entity);
return true;
});
#endif
#if CLIENT
// 客户端伤害飘字
Game.Subscribe<EventDamageNotification>(async (sender, e) =>
{
if (e.Target != null)
{
Game.Logger.LogInformation("伤害: {Damage}, 暴击: {IsCrit}",
e.DamageCurrent, e.IsCritical);
}
});
#endif
}实体级触发器
注册到特定实体,仅在该实体发布事件时触发:
csharp
#if SERVER
var trigger = unit.Subscribe<EventEntityDeath>(async (sender, e) =>
{
Game.Logger.LogInformation("此单位死亡");
return true;
});
#endif动态触发器示例
csharp
var dynTrigger = new TriggerDynamic(async (sender, eventArgs) =>
{
switch (eventArgs)
{
case EventEntityDeath death:
Game.Logger.LogInformation("死亡: {Entity}", death.Entity);
break;
case EventUnitCreate create:
Game.Logger.LogInformation("创建: {Unit}", create.Unit);
break;
}
return true;
});
dynTrigger.AddEvent<EventEntityDeath>(Game.Instance);
dynTrigger.AddEvent<EventUnitCreate>(Game.Instance);自定义事件
定义自定义事件类型:
csharp
public class EventMyCustomEvent(Unit unit, int score)
: ITriggerEvent<EventMyCustomEvent>
{
public Unit Unit { get; } = unit;
public int Score { get; } = score;
}在 IEventSender 上发布:
csharp
unit.Publish(new EventMyCustomEvent(unit, 100));订阅自定义事件:
csharp
unit.Subscribe<EventMyCustomEvent>(async (sender, e) =>
{
Game.Logger.LogInformation("自定义事件: 分数 {Score}", e.Score);
return true;
});注意事项
- 触发器默认
keepReference = true,不会被 GC 回收,使用完毕后应调用Destroy() - 事件处理器中不要使用
Task.Delay(),应使用Game.Delay() - 事件处理器中不要使用字符串插值日志,使用参数化模板:
Game.Logger.LogInformation("{Name}", name) Trigger<T>的事件处理是异步的(fire-and-forget),异常通过EventAggregator.TaskUnhandledException处理- 弱引用机制确保事件发送者被 GC 后,触发器自动清理失效的订阅