Skip to content

触发器与事件系统 ​

触发器(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&lt;TEvent&gt;(推荐) ​

处理单一事件类型的触发器,类型安全且性能优于 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技能事件
BuffBuff 事件
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),事件会依次传播到:

  1. 实例级(注册到该 Unit)— 仅当这个特定单位死亡时触发
  2. 类型级(注册到该单位的 GameDataUnit)— 当所有同类型单位死亡时触发
  3. 全局级(注册到 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停止移动

客户端通知事件 ​

事件属性说明
EventDamageNotificationDamageInstance, IsCritical, IsFatal, Caster, Target伤害通知(用于飘字/UI)
EventHealNotificationHealingInstance, HealAmount, IsCritical, Caster, Target, VitalData治疗通知

单位事件 ​

事件可用端属性说明
EventUnitCreate双端Unit单位创建(客户端:进入视野)
EventUnitRevive双端Unit单位复活
EventUnitPropertyChange双端Unit, Property单位属性变更
EventUnitPropertyComplexFinalChange双端Unit, Property, NewValue复合属性最终值变更
EventAbilityAttached双端Ability技能附加到单位
EventAbilityRemoved双端Ability技能从单位移除
EventAbilityPropertyChange双端Ability, Property技能属性变更
EventBuffAttached双端BuffBuff 附加到单位
EventBuffRemoved双端BuffBuff 从单位移除
EventBuffPropertyChange双端Buff, PropertyBuff 属性变更

玩家事件 ​

事件可用端属性说明
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, PropertyUI 属性变更

物品事件 ​

事件可用端属性说明
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 后,触发器自动清理失效的订阅

相关文档 ​