跳到主要内容

事件使用指南

Guide

使用类型化快捷方法或事件名订阅战斗变化,并在正确生命周期中刷新 UI、释放监听

事件模型​

事件层由四部分组成

部分类型角色
事件名GCSEventNames47 个内置事件的稳定字符串常量
PayloadGCSEventArgs.cs 中的 class随事件送达的类型化数据
ChannelIGCSEventChannel事件发布与订阅接口
统一入口GCSApi类型化快捷方法与通用订阅方法,项目代码从这里开始

事件只负责通知变化及其关联对象,不保存当前事实;先用 payload 定位对象,再从 GCSApi 读取最新状态

上图展示了事件从战斗结算到界面刷新的完整路径,Channel 负责派发,GCSApi 提供类型化或按名订阅入口,回调收到通知后再读取实时状态,避免把 Payload 误当成长期保存的战斗副本

使用快捷方法订阅​

每个内置事件都有对应的 GCSApi 快捷方法,事件名与 Payload 类型已经由方法签名确定

private IDisposable _energySubscription;

void OnEnable()
{
_energySubscription = GCSApi.OnEnergyChanged(args =>
{
energyLabel.text = $"{args.CurrentEnergy}/{args.MaxEnergy}";
});
}

void OnDisable()
{
_energySubscription?.Dispose();
}

每个快捷方法都返回 IDisposable,应保存这份句柄,并在监听对象停用或销毁时释放

按事件名订阅​

Subscribe<TArgs> 接收事件名和类型化回调,适合动态构建订阅或监听自定义事件

private IDisposable _damageSubscription;

void OnEnable()
{
_damageSubscription = GCSApi.Subscribe<DamageEventArgs>(
GCSEventNames.OnDamageDealt,
args => AddCombatLog(args.SourceUnitId, args.TargetUnitId, args.Amount));
}

没有 Payload 的事件使用无参数重载

private IDisposable _shuffleSubscription;

void OnEnable()
{
_shuffleSubscription = GCSApi.Subscribe(
GCSEventNames.OnDrawPileShuffled,
RefreshDrawPileWidget);
}

随包 Demo 使用 OnEnemyUnitActing 快捷方法播放攻击者扑向玩家的突进动画

_actingSubscription = GCSApi.OnEnemyUnitActing(
args => PlayAttackWindup(args.UnitId, args.Intent));
事件名和 payload 类型必须同时匹配

Channel 按「事件名 + 参数类型」共同派发,TArgs 不匹配的回调不会触发,同一个事件名先后使用两种不同参数类型订阅时,Console 会输出 [GCSEvents] 错误并忽略第二次订阅,无法确认类型时,应使用快捷方法或查阅事件参考

选对事件​

UI 或系统需求合适事件
刷新完整战斗 HUDOnBattleStarted、OnTurnStarted、OnTurnEnded、OnEnemyTurnStarted、OnBattleEnded
刷新能量OnEnergyChanged、OnEnergyGained、OnEnergySpent
刷新手牌OnCardDrawn、OnCardDiscarded、OnCardExhausted、OnCardRetained、OnCardAddedToHand、OnCardModified
刷新单位 HP 与护甲OnUnitHpChanged、OnArmorGained、OnArmorLost、OnUnitDied、OnUnitRevived
创建、替换或移除队伍面板OnPlayerUnitAdded、OnUnitRemoved、OnActivePlayerUnitChanged
刷新状态 widgetOnStatusChanged、OnStatusTicked、OnStatusInflicted、OnStatusExpired、OnStatusRemoved
刷新敌人意图OnEnemyUnitIntentChanged、OnEnemyPhaseChanged
敌人行动时播放攻击前摇OnEnemyUnitActing
显示选择OnEffectChoiceOffered、OnEffectChoiceSelected、OnEffectChoiceSkipped
显示奖励OnBattleRewardOffered、OnBattleRewardSelected、OnBattleRewardSkipped
记录战斗细节OnCardPlayed、OnDamageDealt、OnDamageTaken、OnStatusInflicted、OnUnitDied

只使用 Payload 的场景​

一次性表现可以直接使用 Payload 中的本次变化数据

事件只用 payload 的用途
OnDamageDealt生成伤害数字或战斗日志行
OnDamageTaken在目标身上播放受击反馈
OnArmorGained显示护甲获得数字
OnEnergyGained显示能量脉冲
OnEffectChoiceOffered用 payload 渲染候选项
OnBattleRewardOffered用 payload 渲染奖励卡牌
提示

一次性表现完成后仍应从 GCSApi 刷新状态,使飘字和血条落在同一结果上

订阅生命周期​

订阅与释放需要成对出现,适合放在以下生命周期:

  • OnEnable / OnDisable

  • View Model 的 Initialize / Dispose

  • Scene Controller 的 Setup 与 Teardown

不要在以下重复调用位置建立未释放的订阅:

  • Update

  • 每次按钮渲染

  • 每次重建手牌却不释放旧订阅

提示

同一个回调在一次事件中执行多次,通常说明旧订阅没有释放

无 payload 的事件​

四个内置事件不携带 Payload:

  • OnBattleStarted

  • OnBattleRewardSkipped

  • OnEffectChoiceSkipped

  • OnDrawPileShuffled

它们用普通 Action 订阅:

_sub = GCSApi.OnDrawPileShuffled(RefreshPiles);

自定义命名事件​

GCSApi.RaiseGameEvent 可以用任意名字触发事件,并附带可选的参数字典,FlowGraph 节点 Raise Internal Event 在图里做同一件事

GCSApi.RaiseGameEvent("PlayerMarkedTarget", new Dictionary<string, object>
{
["TargetId"] = target.UnitId
});

订阅的形态必须和触发的形态一致,带参数字典的触发以 IReadOnlyDictionary<string, object> 类型送达;不带参数的触发走无参数路径:

_sub = GCSApi.Subscribe<IReadOnlyDictionary<string, object>>(
"PlayerMarkedTarget",
args => HighlightUnit((int)args["TargetId"]));

自定义事件还会触发 Card、Status 与 Enemy Behavior 中同名的 On Internal Event 入口,因此,项目代码定义的业务时机也可以直接进入既有 FlowGraph

提示

只有在内置事件无法准确表达业务时机时才应增加自定义名称,重复发布已有状态事件会形成两套语义相同的订阅入口,增加维护和排查成本

在 Monitor 中检查事件通道​

Game Card Monitor 的 Event 页签实时展示整条通道,GCS Channel 区按顺序列出每次广播,Dispatcher Probes 区显示每次派发的订阅者数量,两项证据可以区分事件没有触发,以及事件已经触发但当时 subs 0 这两种情况

Game Card Monitor 的 Event 页签,按时间排列的 GCS Channel 日志与显示订阅者数量的 Dispatcher Probes

上图把可读的事件详情与底层派发记录放在一起,先确认目标事件已经进入 GCS Channel,再检查对应 probe 的订阅者数量,便能判断问题位于玩法时机还是订阅生命周期

提示

项目同时使用 Game Event System 时,可以把战斗时机转发到 GES,也可以让 GES 事件启动 FlowGraph 入口,具体接法见 GES 集成,行为完全留在 GCS 内部时,继续使用 GCSApi.Subscribe 与 GCSApi.RaiseGameEvent 即可

故障排查​

症状检查项
Handler 每次事件跑两遍以上重复订阅了,且没释放旧句柄
Handler 从不触发事件名和 payload 类型必须同时匹配,见事件参考;同时留意 Console 里的 [GCSEvents] 参数类型错误
UI 显示旧值事件之后从 GCSApi 刷新,payload 只用于确认发生了什么
选择期间卡牌按钮仍可点击用 GCSApi.IsWaitingForChoice 把按钮关掉
奖励 UI 一直不关闭用 GCSApi.ApplyReward 或 GCSApi.SkipReward 结束流程
GES 监听器收不到任何事件Bridge 必须已安装,节点上必须选好事件,见 GES 集成