状态系统
状态层数在运行时如何存在于单位上、什么时候 tick 和衰减,以及哪些事件能让你的 UI 保持准确。
状态在错误的时机 tick、UI 显示的层数过期、伤害 hook 一直不触发时,来查这一页。状态以 GameStatus 资产的形式创作,在运行时以层数的形式存在于单位身上。一个状态可以显示图标、追踪层数、随时间衰减、贡献 hooks,并运行流图(FlowGraph)行为。Game Card Monitor 的 Unit 页签会实时显示每个单位的当前层数。

创作状态字段
| 字段 | 运行时含义 |
|---|---|
DisplayName | 面向用户的名称。 |
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 值来推断致命伤害。
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。 |