跳到主要内容

状态系统

Guide

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

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

创作状态字段

字段运行时含义
DisplayNameInspector 与游戏界面中显示的名称
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 缓存无法可靠反映同一结算中的 Hook 和其他修改

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