跳到主要内容

卡牌区域与牌堆

Guide

运行时卡牌区域如何工作、抽牌堆如何排序,以及哪些 API 能安全地移动卡牌

Game Card Monitor 的 Pile 页签,实时列出手牌、抽牌堆、弃牌堆和消耗堆的内容

卡牌定义与卡牌实例

概念类型含义
Card definitionGameCard创作资产:名称、费用、tags、稀有度、文本、目标选择和行为
Card instanceCardInstance战斗牌堆中的运行时副本,它指向一个 GameCard,并追踪战斗专属状态
Active cardCardInstance.GetActiveCard()GCSApi.GetActiveCard(instance)实例已升级时为升级变体,否则为基础卡
Effective costGCSApi.GetEffectiveEnergyCost(instance)经过卡牌定义、master deck delta、临时修正和 cost hooks 后的费用

运行时动作用实例,内容浏览、卡组构建和数据库查询用定义

运行时牌堆

牌堆API含义
HandGCSApi.Hand()GCSApi.CurrentHand玩家手牌中当前可用的卡牌
Draw pileGCSApi.DrawPile()等待被抽到的卡牌,最后一个元素下一张被抽
Discard pileGCSApi.DiscardPile()本场战斗中被弃掉、可被洗回的卡牌
Exhaust pileGCSApi.ExhaustPile()本场战斗剩余时间内移出游戏的卡牌
Played this turnGCSApi.PlayedThisTurn()当前回合成功打出的卡牌,按打出顺序排列
Drawn this turnGCSApi.DrawnThisTurn()当前回合抽到的卡牌,按抽牌顺序排列

Hand()DrawPile()DiscardPile()ExhaustPile()PlayedThisTurn()DrawnThisTurn() 永远不返回 null,没有活动战斗时返回空列表,CurrentHand 镜像 manager,战斗外返回 null

卡牌在四个区域之间的流转路径很少:

抽牌堆顺序

抽牌堆的顺序约定是最后一个元素位于堆顶,文档里说"抽牌堆顶部"时,指的就是下一张会被抽到的那个元素,添加卡牌时通过 DrawPilePosition 决定新卡进入抽牌堆的位置:

位置含义
Top先于当前牌堆内容被抽到
Bottom后于当前牌堆内容被抽到
Random插入随机位置

开局手牌与每回合抽牌

战斗开始时:

  1. GCS 从 run master deck 或玩家初始卡组构建抽牌堆
  2. 洗抽牌堆
  3. 优先把 Innate 卡放入开局手牌
  4. 抽起始手牌

之后的玩家回合:

  1. 清空 DrawnThisTurn
  2. 触发(Raise) OnDrawPhase
  3. 运行 draw-phase 状态行为
  4. GCS 按 BattleRules.DrawPerTurn 抽牌

抽牌时如果抽牌堆已空,弃牌堆会自动洗回抽牌堆并触发 OnDrawPileShuffled,两个堆都空时,抽牌就此停止

手牌上限是 BattleRules.MaxHandSize(默认 10;战斗中 Hand Size Hook 可以改这个上限),手牌已满时,每回合抽牌会把多出来的牌从抽牌堆顶直接翻进弃牌堆,并逐张触发 OnCardDiscarded;直接调用 GCSApi.DrawCards 则提前停止,AddCardToHand 或往已满手牌移动卡牌,同样会把牌送进弃牌堆

信息

DrawnThisTurn 只覆盖当前回合,之前回合抽到的牌不包含在内,即使它们还留在手牌里

打出卡牌

GCSApi.TryPlayCard(card, target) 成功时:

  1. 卡牌必须在当前手牌中
  2. 解析 active card definition
  3. 检查有效费用
  4. 运行 playability hooks
  5. 消耗能量
  6. 把卡牌实例加入 PlayedThisTurn
  7. 触发 OnCardPlayed
  8. 运行卡牌的 on-play FlowGraph
  9. 注册卡牌 hooks
  10. 卡牌离开手牌
  11. 如果 active card 带有 Exhaust keyword,移入消耗堆并触发 OnCardExhausted;否则移入弃牌堆

卡牌会在行为结算后由运行时移出手牌,自定义 UI 只需响应牌堆事件并刷新视图,手动移除会与运行时的移动操作重复

回合结束的手牌处理

玩家回合结束时,GCS 处理剩余手牌:

卡牌行为运行时结果
普通手牌移入弃牌堆
Retain keyword 的卡牌留在手牌中,并触发 OnCardRetained
Ethereal keyword 的卡牌移入消耗堆,并触发 OnCardExhausted
已打出且会消耗的卡牌在打出时就移入消耗堆,早于回合结束

上表基于默认的 HandDiscardRule.DiscardOnTurnEnd,遭遇战改用 Persist 时会跳过弃牌步骤:留下的每张牌都触发 OnCardRetainedEthereal 卡牌仍会消耗

如果你的 UI 想播放手牌清理动画,订阅卡牌生命周期事件,然后从 GCSApi.Hand() 刷新

创建和移动卡牌

卡牌移动请使用 controller 背书的 API

任务API
把一张卡加入手牌GCSApi.AddCardToHand(card)
移动已有实例GCSApi.MoveCardToPile(instance, PileScope.Discard)
洗抽牌堆GCSApi.ShuffleDrawPile()
抽牌GCSApi.DrawCards(count)
读取 active card definitionGCSApi.GetActiveCard(instance)
读取有效费用GCSApi.GetEffectiveEnergyCost(instance)

PileScope 同时用于读取范围和移动目标,但 MasterAll 在两种操作中的含义不同:

作为读取范围作为移动目标
HandDrawDiscardExhaust读取对应的单个战斗牌堆把卡移入对应牌堆,手牌已满时进入弃牌堆
Master把 run master deck 读取为临时卡牌实例,并保留升级状态和费用变化从四个战斗牌堆移除卡牌,不修改 run master deck
All合并读取手牌、抽牌堆、弃牌堆和消耗堆,不包含 master deck从四个战斗牌堆移除卡牌,与 Master 相同
注意

牌堆修改应使用 GCSApi 或 controller,直接编辑 CardPile.Cards 会跳过事件、牌堆规则和 UI 刷新

Master deck 与战斗牌堆

Run master deck 是用于启动战斗的持久卡组列表,运行时牌堆是战斗状态

需求API
查看 run deckGCSApi.GetMasterDeck()
替换 run deckGCSApi.SetMasterDeck(entries)
向 run deck 添加卡牌GCSApi.AddCardToMasterDeck(card, isUpgraded)
从 run deck 移除GCSApi.RemoveCardFromMasterDeck(cardGuid, removeFirstOnly)
在 run deck 中升级GCSApi.UpgradeCardInMasterDeck(cardGuid)
清空 run deckGCSApi.ClearMasterDeck()

修改 master deck 不会追溯改变已经在实时战斗中的卡牌,除非你同时修改运行时牌堆

卡牌事件

事件Payload用于
OnCardPlayedCardPlayedEventArgs出牌 UI、统计、连击计数
OnCardModifiedCardModifiedEventArgs费用或升级刷新
OnCardDrawnCardLifecycleEventArgs抽牌动画和手牌 widget 创建
OnCardDiscardedCardLifecycleEventArgs弃牌动画
OnCardExhaustedCardLifecycleEventArgs消耗动画
OnCardRetainedCardLifecycleEventArgs保留反馈
OnCardAddedToHandCardLifecycleEventArgs生成卡牌的 UI
OnCardAddedToDeckCardLifecycleEventArgsRun deck 更新
OnDrawPileShuffled无 payload洗牌反馈

处理完事件后始终从当前牌堆刷新,事件告诉你什么变了,API 告诉你当前的事实