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

卡牌定义与卡牌实例
| 概念 | 类型 | 含义 |
|---|---|---|
| Card definition | GameCard | 创作资产:名称、费用、tags、稀有度、文本、目标选择和行为 |
| Card instance | CardInstance | 战斗牌堆中的运行时副本,它指向一个 GameCard,并追踪战斗专属状态 |
| Active card | CardInstance.GetActiveCard() 或 GCSApi.GetActiveCard(instance) | 实例已升级时为升级变体,否则为基础卡 |
| Effective cost | GCSApi.GetEffectiveEnergyCost(instance) | 经过卡牌定义、master deck delta、临时修正和 cost hooks 后的费用 |
运行时动作用实例,内容浏览、卡组构建和数据库查询用定义
运行时牌堆
| 牌堆 | API | 含义 |
|---|---|---|
| Hand | GCSApi.Hand() 或 GCSApi.CurrentHand | 玩家手牌中当前可用的卡牌 |
| Draw pile | GCSApi.DrawPile() | 等待被抽到的卡牌,最后一个元素下一张被抽 |
| Discard pile | GCSApi.DiscardPile() | 本场战斗中被弃掉、可被洗回的卡牌 |
| Exhaust pile | GCSApi.ExhaustPile() | 本场战斗剩余时间内移出游戏的卡牌 |
| Played this turn | GCSApi.PlayedThisTurn() | 当前回合成功打出的卡牌,按打出顺序排列 |
| Drawn this turn | GCSApi.DrawnThisTurn() | 当前回合抽到的卡牌,按抽牌顺序排列 |
Hand()、DrawPile()、DiscardPile()、ExhaustPile()、PlayedThisTurn() 和 DrawnThisTurn() 永远不返回 null,没有活动战斗时返回空列表,CurrentHand 镜像 manager,战斗外返回 null
卡牌在四个区域之间的流转路径很少:
抽牌堆顺序
抽牌堆的顺序约定是最后一个元素位于堆顶,文档里说"抽牌堆顶部"时,指的就是下一张会被抽到的那个元素,添加卡牌时通过 DrawPilePosition 决定新卡进入抽牌堆的位置:
| 位置 | 含义 |
|---|---|
| Top | 先于当前牌堆内容被抽到 |
| Bottom | 后于当前牌堆内容被抽到 |
| Random | 插入随机位置 |
开局手牌与每回合抽牌
战斗开始时:
- GCS 从 run master deck 或玩家初始卡组构建抽牌堆
- 洗抽牌堆
- 优先把 Innate 卡放入开局手牌
- 抽起始手牌
之后的玩家回合:
- 清空
DrawnThisTurn - 触发(Raise)
OnDrawPhase - 运行 draw-phase 状态行为
- GCS 按
BattleRules.DrawPerTurn抽牌
抽牌时如果抽牌堆已空,弃牌堆会自动洗回抽牌堆并触发 OnDrawPileShuffled,两个堆都空时,抽牌就此停止
手牌上限是 BattleRules.MaxHandSize(默认 10;战斗中 Hand Size Hook 可以改这个上限),手牌已满时,每回合抽牌会把多出来的牌从抽牌堆顶直接翻进弃牌堆,并逐张触发 OnCardDiscarded;直接调用 GCSApi.DrawCards 则提前停止,AddCardToHand 或往已满手牌移动卡牌,同样会把牌送进弃牌堆
DrawnThisTurn 只覆盖当前回合,之前回合抽到的牌不包含在内,即使它们还留在手牌里
打出卡牌
GCSApi.TryPlayCard(card, target) 成功时:
- 卡牌必须在当前手牌中
- 解析 active card definition
- 检查有效费用
- 运行 playability hooks
- 消耗能量
- 把卡牌实例加入
PlayedThisTurn - 触发
OnCardPlayed - 运行卡牌的 on-play FlowGraph
- 注册卡牌 hooks
- 卡牌离开手牌
- 如果 active card 带有
Exhaustkeyword,移入消耗堆并触发OnCardExhausted;否则移入弃牌堆
卡牌会在行为结算后由运行时移出手牌,自定义 UI 只需响应牌堆事件并刷新视图,手动移除会与运行时的移动操作重复
回合结束的手牌处理
玩家回合结束时,GCS 处理剩余手牌:
| 卡牌行为 | 运行时结果 |
|---|---|
| 普通手牌 | 移入弃牌堆 |
带 Retain keyword 的卡牌 | 留在手牌中,并触发 OnCardRetained |
带 Ethereal keyword 的卡牌 | 移入消耗堆,并触发 OnCardExhausted |
| 已打出且会消耗的卡牌 | 在打出时就移入消耗堆,早于回合结束 |
上表基于默认的 HandDiscardRule.DiscardOnTurnEnd,遭遇战改用 Persist 时会跳过弃牌步骤:留下的每张牌都触发 OnCardRetained,Ethereal 卡牌仍会消耗
如果你的 UI 想播放手牌清理动画,订阅卡牌生命周期事件,然后从 GCSApi.Hand() 刷新
创建和移动卡牌
卡牌移动请使用 controller 背书的 API
| 任务 | API |
|---|---|
| 把一张卡加入手牌 | GCSApi.AddCardToHand(card) |
| 移动已有实例 | GCSApi.MoveCardToPile(instance, PileScope.Discard) |
| 洗抽牌堆 | GCSApi.ShuffleDrawPile() |
| 抽牌 | GCSApi.DrawCards(count) |
| 读取 active card definition | GCSApi.GetActiveCard(instance) |
| 读取有效费用 | GCSApi.GetEffectiveEnergyCost(instance) |
PileScope 同时用于读取范围和移动目标,但 Master 与 All 在两种操作中的含义不同:
| 值 | 作为读取范围 | 作为移动目标 |
|---|---|---|
Hand、Draw、Discard、Exhaust | 读取对应的单个战斗牌堆 | 把卡移入对应牌堆,手牌已满时进入弃牌堆 |
Master | 把 run master deck 读取为临时卡牌实例,并保留升级状态和费用变化 | 从四个战斗牌堆移除卡牌,不修改 run master deck |
All | 合并读取手牌、抽牌堆、弃牌堆和消耗堆,不包含 master deck | 从四个战斗牌堆移除卡牌,与 Master 相同 |
牌堆修改应使用 GCSApi 或 controller,直接编辑 CardPile.Cards 会跳过事件、牌堆规则和 UI 刷新
Master deck 与战斗牌堆
Run master deck 是用于启动战斗的持久卡组列表,运行时牌堆是战斗状态
| 需求 | API |
|---|---|
| 查看 run deck | GCSApi.GetMasterDeck() |
| 替换 run deck | GCSApi.SetMasterDeck(entries) |
| 向 run deck 添加卡牌 | GCSApi.AddCardToMasterDeck(card, isUpgraded) |
| 从 run deck 移除 | GCSApi.RemoveCardFromMasterDeck(cardGuid, removeFirstOnly) |
| 在 run deck 中升级 | GCSApi.UpgradeCardInMasterDeck(cardGuid) |
| 清空 run deck | GCSApi.ClearMasterDeck() |
修改 master deck 不会追溯改变已经在实时战斗中的卡牌,除非你同时修改运行时牌堆
卡牌事件
| 事件 | Payload | 用于 |
|---|---|---|
| OnCardPlayed | CardPlayedEventArgs | 出牌 UI、统计、连击计数 |
| OnCardModified | CardModifiedEventArgs | 费用或升级刷新 |
| OnCardDrawn | CardLifecycleEventArgs | 抽牌动画和手牌 widget 创建 |
| OnCardDiscarded | CardLifecycleEventArgs | 弃牌动画 |
| OnCardExhausted | CardLifecycleEventArgs | 消耗动画 |
| OnCardRetained | CardLifecycleEventArgs | 保留反馈 |
| OnCardAddedToHand | CardLifecycleEventArgs | 生成卡牌的 UI |
| OnCardAddedToDeck | CardLifecycleEventArgs | Run deck 更新 |
| OnDrawPileShuffled | 无 payload | 洗牌反馈 |
处理完事件后始终从当前牌堆刷新,事件告诉你什么变了,API 告诉你当前的事实