跳到主要内容

状态系统

GCS · Runtime

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

状态在错误的时机 tick、UI 显示的层数过期、伤害 hook 一直不触发时,来查这一页。

状态以 GameStatus 资产的形式创作,在运行时以层数的形式存在于单位身上。一个状态可以显示图标、追踪层数、随时间衰减、贡献 hooks,并运行流图(FlowGraph)行为。Game Card Monitor 的 Unit 页签会实时显示每个单位的当前层数。

Game Card Monitor 的 Unit 页签,显示玩家和敌方单位的 HP、护甲与当前状态层数

创作状态字段

字段运行时含义
DisplayName面向用户的名称。
Description面向玩家的描述,可带 token。
IconUI 图标。
IsDebuff把状态标记为 debuff,用于过滤和表现。
StackRule新层数与已有层数如何叠加:Additive 相加,Max 取较高值,Replace 直接覆盖。
MaxStacks最大层数;为负表示没有实际上限。
DecayRule层数什么时候衰减:PerTurn 在持有者回合结束时减一层,Never 只等显式移除。
ShowStackCountUI 是否显示层数数字。
Tags创作和 FlowGraph 过滤。
Behavior响应状态生命周期、hooks 和时机的 FlowGraph。

Behavior 图定义状态的实际行为。以 demo 的 Poison 为例:在持有者回合开始时每层造成 1 点无视护甲的伤害,然后减一层:

FlowGraph 编辑器中的 Poison 状态图:回合开始入口按层数造成伤害,随后减少层数


运行时存储

每个 UnitState 存储这些状态数据:

运行时数据含义
StatusesGameStatus 到层数的字典。
StatusAppliersGameStatus 到施加单位的字典(已知时记录)。
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用于
OnStatusChangedStatusChangedEventArgs层数变化后刷新单位状态 widget。
OnStatusTickedStatusTickedEventArgs播放来自 tick 的伤害、治疗或层数变化动画。
OnStatusInflictedStatusTransitionEventArgs响应新施加的状态。
OnStatusExpiredStatusTransitionEventArgs播放自然到期动画。
OnStatusRemovedStatusTransitionEventArgs播放净化或手动移除动画。

StatusChangedEventArgs 是最实用的 UI 刷新信号。Transition 事件更适合专门的效果和玩法集成。


伤害与状态归因

状态造成的伤害会通过运行时 hooks 和伤害事件携带来源归因。状态施加伤害时,DamageEventArgs 告诉订阅者:

字段含义
SourceUnitId对伤害负责的单位(已知时)。
TargetUnitId承受伤害的单位。
Amount经过运行时规则后的最终数值。
Lethal目标是否因这次伤害死亡。

把它用在 UI 标签、统计和战斗日志上。不要靠比较过期的 HP 值来推断致命伤害。


UI 显示规则

状态 widget 的做法:

  1. 按需监听 OnStatusChangedOnStatusTickedOnUnitHpChangedOnUnitDied
  2. GCSApi.StatusesOn(unit) 刷新单位的当前状态 map。
  3. GameStatus.IconDisplayNameIsDebuffShowStackCount 和层数做显示。
  4. 隐藏 0 层的状态。

Demo 的 UnitView 也靠监听状态事件驱动程序化的状态视觉。自定义 UI 可以使用同一条事件通道,不必依赖 demo 的 view 组件。


常见错误

错误更好的做法
直接编辑 UnitState.StatusesApplyStatusRemoveStatusModifyStatusStacks
用生命周期入口去修改还没发生的伤害。用对应的 hook。
只拿事件里的层数做显示。GCSApi.StatusesOn(unit) 刷新。
假设所有状态都是 debuff。检查 GameStatus.IsDebuff 或 tags。
把 removed 和 expired 当同一种事件。手动移除用 OnStatusRemoved,自然到期用 OnStatusExpired