状态系统
Guide
状态层数在运行时如何存在于单位上、什么时候 tick 和衰减,以及哪些事件能让你的 UI 保持准确

创作状态字段
| 字段 | 运行时含义 |
|---|---|
DisplayName | Inspector 与游戏界面中显示的名称 |
Description | 面向玩家的描述,可带 token |
Icon | UI 图标 |
IsDebuff | 把状态标记为 debuff,用于过滤和表现 |
StackRule | 新层数与已有层数如何叠加:Additive 相加,Max 取较高值,Replace 直接覆盖 |
MaxStacks | 最大层数;为负表示没有实际上限 |
DecayRule | 层数什么时候衰减:PerTurn 在持有者回合结束时减一层,Never 只等显式移除 |
ShowStackCount | UI 是否显示层数数字 |
Tags | 创作和 FlowGraph 过滤 |
Behavior | 响应状态生命周期、hooks 和时机的 FlowGraph |
Behavior 图定义状态的实际行为,以 demo 的 Poison 为例:在持有者回合开始时每层造成 1 点无视护甲的伤害,然后减一层:

运行时存储
每个 UnitState 存储这些状态数据:
| 运行时数据 | 含义 |
|---|---|
Statuses | 从 GameStatus 到层数的字典 |
StatusAppliers | 从 GameStatus 到施加单位的字典(已知时记录) |
ActiveHooks | 状态和卡牌行为贡献的 hook 条目 |
通过以下公开 helper 读取状态,可以保持空值、层数和战斗外行为一致:
| 需求 | API |
|---|---|
| 层数 | GCSApi.GetStatusStacks(unit, status) |
| 至少有 N 层 | GCSApi.HasStatus(unit, status, minStacks) |
| 谁施加了状态 | GCSApi.GetStatusApplier(unit, status) |
| 单位身上的全部状态 | GCSApi.StatusesOn(unit) |
单位为 null 或身上没有状态时,StatusesOn 返回空 map
施加和改变状态
| 任务 | API 或节点 |
|---|---|
| 施加层数 | GCSApi.ApplyStatus(target, status, stacks, source) 或 Change Status 节点 |
| 移除层数 | GCSApi.RemoveStatus(target, status, stacks) 或 Remove Statuses / Change Status |
| 按 delta 增减 | GCSApi.ModifyStatusStacks(target, status, delta) |
| 不改层数、重新运行行为 | Reapply Status 节点或 controller 的 RetriggerStatus |
状态变化会经过运行时规则并触发(Raise)事件,避免手动编辑 UnitState.Statuses
Hooks 与生命周期入口
状态行为可以做两类事情
| 行为类型 | 用于 | 示例 |
|---|---|---|
| Lifecycle entries | 在某件事发生之后响应 | On Status Received, On Status Removed, On Turn Start |
| Hooks | 在数值提交之前改变它 | Damage Taken Hook, Card Cost Hook, Draw Count Hook |
打算改数值的 hook 应以 Write Hook 节点收尾,生命周期入口用普通的 action 节点即可
衰减与回合时机
状态衰减和 tick 由战斗状态机驱动,带 PerTurn 衰减的状态从施加到到期走这样一个循环:
| 时机 | 可能发生什么 |
|---|---|
| PlayerTurnStart | 玩家护甲衰减、玩家状态的回合开始行为、延迟触发 |
| PlayerTurnEnd | 弃牌阶段、玩家状态的回合结束行为、状态 tick/衰减、hook ticking |
| EnemyPhase(逐个敌人) | 敌人状态的回合开始行为、敌人行动、敌人状态的回合结束行为、状态 tick/衰减 |
| BattleEnd | 所有单位的 battle-end 状态行为 |
自定义 UI 需要播放 tick 或衰减动画时,用 StatusTickedEventArgs,它同时携带 tick 前后的层数和 HP
状态事件
| 事件 | Payload | 用于 |
|---|---|---|
| OnStatusChanged | StatusChangedEventArgs | 层数变化后刷新单位状态 widget |
| OnStatusTicked | StatusTickedEventArgs | 播放来自 tick 的伤害、治疗或层数变化动画 |
| OnStatusInflicted | StatusTransitionEventArgs | 响应新施加的状态 |
| OnStatusExpired | StatusTransitionEventArgs | 播放自然到期动画 |
| OnStatusRemoved | StatusTransitionEventArgs | 播放净化或手动移除动画 |
StatusChangedEventArgs 是最实用的 UI 刷新信号,Transition 事件更适合专门的效果和玩法集成
伤害与状态归因
状态造成的伤害会通过运行时 hooks 和伤害事件携带来源归因,状态施加伤害时,DamageEventArgs 告诉订阅者:
| 字段 | 含义 |
|---|---|
SourceUnitId | 对伤害负责的单位(已知时) |
TargetUnitId | 承受伤害的单位 |
Amount | 经过运行时规则后的最终数值 |
Lethal | 目标是否因这次伤害死亡 |
该结果适合用于 UI 标签、统计和战斗日志,致命伤害应读取事件结果,旧 HP 缓存无法可靠反映同一结算中的 Hook 和其他修改
UI 显示规则
状态 widget 的做法:
- 按需监听
OnStatusChanged、OnStatusTicked、OnUnitHpChanged和OnUnitDied - 从
GCSApi.StatusesOn(unit)刷新单位的当前状态 map - 用
GameStatus.Icon、DisplayName、IsDebuff、ShowStackCount和层数做显示 - 隐藏 0 层的状态
Demo 的 UnitView 也靠监听状态事件驱动程序化的状态视觉,自定义 UI 可以使用同一条事件通道,不必依赖 demo 的 view 组件
常见错误
| 错误 | 更好的做法 |
|---|---|
直接编辑 UnitState.Statuses | 用 ApplyStatus、RemoveStatus 或 ModifyStatusStacks |
| 用生命周期入口去修改还没发生的伤害 | 用对应的 hook |
| 只拿事件里的层数做显示 | 从 GCSApi.StatusesOn(unit) 刷新 |
| 假设所有状态都是 debuff | 检查 GameStatus.IsDebuff 或 tags |
| 把 removed 和 expired 当同一种事件 | 手动移除用 OnStatusRemoved,自然到期用 OnStatusExpired |