战斗生命周期
你需要从初始化到玩家回合、敌人行动和战斗结果,准确理解每个阶段允许的操作与执行顺序
一场战斗从 Initialize 建立单位与牌堆,经过玩家和敌人回合持续推进,最后进入胜利或失败,Game Card Monitor 的 Phase 标签页会把当前阶段与已经发生的转换放在同一份运行记录中

上图中的 Phase 快照用于确认战斗此刻允许什么操作,History 则用于回看阶段是否按预期到达,理解这两项信息前,需要先明确每个 BattlePhase 的职责
战斗阶段
GCSApi.Phase 返回当前 BattlePhase,没有活动战斗时返回 BattlePhase.None
| 阶段 | 运行内容 | 玩家输入 |
|---|---|---|
None | 没有活动战斗 | 不可出牌 |
Initialize | 创建单位、卡牌、牌堆和初始状态 | 不可出牌 |
PlayerTurnStart | 处理护甲、能量、抽牌、状态与延迟触发 | 不可出牌 |
PlayerPhase | 等待玩家出牌或结束回合 | 可以出牌 |
PlayerTurnEnd | 处理弃牌、回合结束状态、衰减与延迟触发 | 不可出牌 |
EnemyPhase | 按顺序结算每个存活敌人的状态与行为 | 不可出牌 |
BattleWon | 生成并等待战斗奖励 | 只能处理奖励 |
BattleLost | 结束失败战斗 | 不可出牌 |
上图展示了战斗的主循环,玩家只能在 PlayerPhase 打出卡牌或结束回合;状态、敌人行为和延迟触发可以改变后续路径,但不会把玩家输入窗口移动到其他阶段
初始化战斗
调用 GCSApi.StartBattle(encounter) 后,GCS 会在方法返回前完成以下工作:
- 清理上一场仍存在的战斗状态
- 根据 Encounter 创建玩家和敌人的运行实例
- 根据 Run Master Deck 或 Starting Deck 创建并洗牌抽牌堆
- 优先安排
Innate卡,再抽取起手牌 - 将
TurnNumber设为1并发布OnBattleStarted - 解析第一组敌人 Intent,执行战斗开始状态行为
- 进入
PlayerTurnStart,随后开放首个PlayerPhase
OnBattleStarted 在初始化流程中同步发布,当订阅回调开始执行时,Phase 仍为 Initialize,首组敌人 Intent 也可能尚未完成解析,需要刷新 Intent 的 UI 应监听 OnEnemyUnitIntentChanged,或者在 StartBattle 返回后读取当前状态
Encounter 为 null 时,战斗不会启动,场景启动方式见战斗启动
玩家回合开始
PlayerTurnStart 按以下顺序准备玩家操作:
- 移除已经到期的
Scope = TurnsCard Hook - 按
BattleRules.ArmorDecayRule处理玩家护甲 - 计算并补充本回合能量
- 执行安排在
PlayerTurnStart的延迟触发 - 清空本回合出牌记录
- 执行玩家状态和手牌卡牌的回合开始行为
- 从第二回合起进入抽牌阶段并按规则抽牌
- 发布
OnTurnStarted,再进入PlayerPhase
第一回合的起手牌已经在初始化期间抽取,因此不会再次执行常规抽牌阶段
玩家操作阶段
GCSApi.TryPlayCard 只有在可交互的 PlayerPhase 才可能成功,运行时会检查卡牌是否仍在手牌、是否存在有效定义、能量是否足够、目标是否符合卡牌的目标模式,以及出牌 Hook、Unplayable 和可打出 Hook 是否允许本次操作
出牌成功前,GCS 会先解析并验证目标;提交后先把实例移出手牌,再扣除能量、在有效费用为正时发布 OnEnergySpent、记录本回合出牌并发布 OnCardPlayed,因此 On Play Behavior 读取到的是出牌后的手牌;Behavior 已明确移动这张实例时会保留该位置,否则普通卡牌进入 Discard,但不触发弃牌生命周期,带 Exhaust 的卡牌则通过事件与 Behavior 完成消耗生命周期,行为图请求结束回合时会在当前完整效果波次结算后进入 PlayerTurnEnd
自定义 UI 应同时满足以下条件:
bool canPlay =
GCSApi.Phase == BattlePhase.PlayerPhase &&
GCSApi.CanPlayCard(card);
需要玩家选择单个敌人时,再用 GCSApi.RequiresTarget(GCSApi.GetActiveCard(card)) 打开目标选择器,提交目标后仍应检查 TryPlayCard 的返回值,因为战斗状态可能在玩家确认前已经变化
玩家回合结束
在 PlayerPhase 调用 GCSApi.EndPlayerTurn() 后,运行时会按以下顺序收束玩家回合
- 发布
OnDiscardPhase并执行对应状态行为 - 按 Hand Rule 处理剩余手牌
- 执行玩家状态的回合结束行为
- 处理状态衰减、临时费用规则与延迟触发
- 发布
OnTurnEnded - 有额外回合时返回
PlayerTurnStart,否则进入EnemyPhase
带 Retain 的卡牌通常留在手牌;带 Ethereal 的卡牌进入消耗堆;On Turn End Behavior 已经移动的实例会跳过默认清理,保证最终只属于一个牌堆,完整规则见卡牌与牌堆生命周期
敌人阶段
EnemyPhase 会按 Encounter 中的顺序处理每个存活敌人:
- 处理该敌人的护甲和回合开始状态
- 检查跳过回合等控制状态
- 执行已规划的 Intent Behavior
- 处理回合结束状态、状态衰减和 Hook
- 完成所有敌人后,执行敌人阶段结束的延迟触发
- 规划下一轮 Intent,增加回合数并返回
PlayerTurnStart
敌人攻击前冲、动作间隔和受击动画属于表现层,它们只有显式使用 Wait 或 Animation Gate 时才会延迟阶段推进
状态层数或 Card 注册的活动 Hook 发生变化时,运行时会立即重新解析所有存活敌人的公开 Intent,新召唤的敌人也会在加入战场后立即得到第一组 Intent;UI 应监听 OnEnemyUnitIntentChanged 并读取新的 PendingIntents
Choice、Wait 与流程等待
FlowGraph 的 Choice 与 Wait 会暂停当前入口的完整效果波次,而不只是节点后的一条分支;同一 Sequence、Loop 或 Foreach 中尚未执行的路径、卡牌与状态生命周期以及已经排队的阶段转换都会等待恢复分支完成,Choice 没有 Presenter 或没有候选项时会立即走 Skipped 分支

Choice 打开后,GCSApi.IsWaitingForChoice 为 true;Choice、Wait 或 Animation Gate 尚未完成时,CanPlayCard 与 TryPlayCard 会拒绝新出牌,EndPlayerTurn 也不会推进阶段,自定义 UI 应在效果恢复前遮罩手牌、结束回合和其他无关输入
Wait 与跨回合 Delayed continuation 会冻结暂停时捕获的值槽、节点结果与当前效果范围变量,但单位、卡牌、状态与其他对象槽仍保留原对象引用;恢复后的分支会得到相同的选中对象,并读取这些对象此刻的实时状态;嵌套 Loop、Foreach 与 While 会在当前轮次恢复完成后才进入下一轮,同一次触发命中的多个同类型 Entry 也属于同一个有序批次;整批完成后,延迟波次才报告一次完成并允许阶段推进
Wait 的节点参数见流程节点,Animation Gate 对阶段推进的影响见运行时概览
战斗结果与奖励
- 战斗胜利后,GCS 会等待全部战斗结束状态分支及其
Wait或Choice恢复路径完成,再生成奖励候选并发布OnBattleRewardOffered,此时GCSApi.IsWaitingForReward为true,只有调用ApplyReward或SkipReward后,运行时才会发布最终的OnBattleEnded - 如果战斗失败,便不会进入奖励流程,运行时会等待全部战斗结束状态分支及其延迟恢复路径完成,再发布
OnBattleEnded,其中Won = false
延迟分支尚未结束时,胜负条件只代表中间状态;完整分支与匹配 Entry 批次完成后,GCS 会在提交 BattleWon 或 BattleLost 前重新读取当前战场,因此,Kill → Wait → Spawn 与 Death → Wait → Revive 在后续动作逆转中间状态时不会进入终局阶段
UI 阶段检查
| UI 操作 | 推荐检查 |
|---|---|
| 显示战斗 HUD | GCSApi.IsBattleActive |
| 启用卡牌 | Phase == PlayerPhase 且 CanPlayCard(card) |
| 启用结束回合按钮 | Phase == PlayerPhase 且当前效果波次未等待 Choice、Wait 或 Animation Gate |
| 显示选择界面 | GCSApi.IsWaitingForChoice |
| 显示奖励界面 | GCSApi.IsWaitingForReward |
| 显示最终结果 | OnBattleEnded 的 payload |
事件说明某项变化已经发生,界面当前应显示什么,仍以 GCSApi.Phase 和实时状态为准