跳到主要内容

卡牌、手牌、卡组、弃牌与消耗

GCS · Runtime

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

卡牌进了错误的牌堆、生成的卡没出现在手牌里、UI 显示的手牌内容过期时,来查这一页。

运行时卡牌区域是 CardInstance 列表。卡牌实例是 GameCard 资产的一份战斗副本,拥有自己的 instance id、升级标记和 master deck cost delta。Game Card Monitor 的 Pile 页签会实时列出所有区域的内容。

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)
注意

不要直接编辑 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 告诉你当前的事实。