跳到主要内容

卡牌创作

GCS · Authoring

定义一张卡的费用、手牌中的呈现,以及结算时运行哪张 FlowGraph。

适合在 Workbench Card 模式中创建或审阅卡牌内容的开发者。

卡牌是玩家的主要行动来源。一个卡牌定义控制它在手牌中的显示、打出费用,以及结算时运行的行为图。这些内容都在同一个窗口中编辑:打开 Tools > TinyGiants > GCS > Game Card Editor,切换到 Card 页签。

Workbench Card 模式:左侧卡牌列表,中间 Inspector,右侧实时卡牌预览

Demo 自带的 80 张卡全部使用这套流程制作。字段含义不明确时,可以打开一张 Demo 卡(例如 Poison Arrow 或 Shield Bash)与自定义卡牌对照。


创作顺序

从零创建一张卡时,建议按以下顺序处理:

  1. 先用一句话写清这张卡对玩家承诺的效果。
  2. 填写 NameDescriptionCostTypeRarity
  3. 如果表现资源已经存在,设置 IconTemplate
  4. 添加 Tags 用于过滤和联动;只有需要内置规则行为时才添加 Keywords
  5. 打开 Edit FlowGraph,连好行为。
  6. 把卡放进一个卡组,进战斗测试。

卡牌文本尚未明确前,不建议搭建行为图。行为图应实现卡面文字承诺的效果;第 1 步写下的句子,是之后校对文字与行为是否一致的基准。


基础字段

字段作用
Name手牌和列表行中的显示名称。非空时 Workbench 会同步重命名卡牌资产。
Description显示在卡面上的规则文本。保持与行为图一致。
Cost打出这张卡所需的能量。只接受整数,编辑器会把它限制在 0 或更高。这是印在卡上的基础费用;一旦 Card Cost Hook 或 Modify Card Cost 效果介入,玩家实际支付的费用可以不同。
Type用于卡框颜色、过滤和分组的卡牌分类。下拉提供 BasicSkillCurseSupportBurst,外加数据库中已经用过的所有类型;输入新标签即创建。
Rarity奖励与分组标签。下拉提供 CommonRareEpicLegendary,外加已用过的自定义值。留空会回退到 Common
Tags项目自用的自由标签,用于过滤和联动。本身没有内置行为。
Keywords带内置规则行为的引擎关键词,从下拉列表中选取。见下表。
Upgrade升级后变成的卡。None 表示没有升级路径。

卡牌 inspector 的 Basic 折叠面板:Name、Description、Cost,以及 Type、Rarity、Tags、Keywords、Upgrade 字段,多数为下拉

Tags 和 Keywords 在 Inspector 中形式相似,但行为不同。Tags 是项目自定义标签,在 FlowGraph 或代码读取它之前,引擎不会处理它。Keywords 来自卡牌关键词注册表,带有内置规则行为。需要内置行为时,应从 Keywords 下拉中选择,而不是填入 tag。

Demo 的 Poison Arrow 展示了完整字段配置:Cost 1、Type Skill、Rarity Common、一个 Attack 标签,描述与行为图保持一致。

Workbench Card 模式中选中 Poison Arrow,Basic、Visual、Behavior 折叠区填写完整,预览列渲染出成品卡

内置关键词

Keywords 下拉的选项来自关键词注册表(C# 中的 [CardTag] 类型)。项目程序集可以注册新关键词。GCS 内置五个关键词,每个都有明确规则:

关键词规则行为
Exhaust打出后进入消耗堆而不是弃牌堆,弃牌堆重洗时不会回来。
Ethereal回合结束时若仍在手牌中,会被消耗而不是弃掉。
Retain回合结束时保留在手牌中,不被弃掉。只在遭遇战规则设定回合末弃手牌时才有意义;保留手牌的规则下,所有卡本来就留着。
Innate战斗开始时被移到抽牌堆顶部,因此会出现在起手手牌里。Innate 卡多于起手位时,随机选出一部分。
Unplayable永远不能打出。它作为死牌留在手牌中,常用于诅咒和联动效果。

升级

一张卡和它的升级形态是两个独立的卡牌定义。先创建强化版卡牌,再把基础卡的 Upgrade 指向它。运行时某个机制升级某份卡后,该卡实例会改用升级定义的费用、文本和行为图。Demo 的 Strike 以这种方式升级为 Heavy Strike;升级卡本身不需要出现在任何卡组中。


视觉字段

Visual 区域有两个槽位。Icon 是卡面和列表行使用的美术 sprite。Template 是渲染为卡面的 prefab,留空时表现层会回退到占位显示。

提示

机制测试阶段可以使用临时美术。占位卡面足够验证伤害数字和目标选择,正式美术可以后续替换。


目标模式

目标选择是卡牌定义上的数据:TargetMode 字段决定打出这张卡时是否要求玩家指向一个单位,行为图在运行时读取这个选择。

模式玩家的体验
None不选单位直接结算。抽牌、获得能量属于这一类。
Self卡牌作用于玩家单位,无需选择。
SingleEnemyUnit玩家把卡拖到一个存活敌人身上。
AllEnemyUnits卡牌命中所有存活敌人,无需选择。

只有 SingleEnemyUnit 需要选定目标,其余三种模式由引擎自动解析。在图里,选中的单位对应 Opponent 目标选项,这就是 Poison Arrow 的 Deal Damage 节点瞄准 Opponent 的原因。从代码打出卡牌时也是同一份契约:

// TryPlayCard(CardInstance card, UnitState target): card is a hand instance.
// Single-enemy cards need a target unit; every other mode takes null.
var arrow = GCSApi.Hand()[0];
GCSApi.TryPlayCard(arrow, GCSApi.FirstAliveEnemy());
GCSApi.TryPlayCard(GCSApi.Hand()[1], null);

通过代码打出单体卡但未传入目标时,引擎会回退到第一个存活敌人,而不是让这次出牌失败。

注意

保持目标模式和行为图一致。描述写着"所有敌人"时,图应解析所有敌人,而不是只读取选中目标;需要玩家点击敌人的卡牌,目标模式不应为 None


行为图

Behavior 区域点击 Edit FlowGraph。图会在独立窗口中打开,窗口顶栏显示宿主名称;卡名为 Poison Arrow 时,标题为 Poison Arrow / FlowGraph。图中存在入口后,折叠面板会把入口列为可点击 chip,用于跳回对应位置。

卡牌 inspector 的 Behavior 区域:Edit FlowGraph 按钮,以及下方的 OnCardPlayed 入口 chip

FlowGraph 窗口打开 Poison Arrow 的行为图,顶栏显示 Poison Arrow / FlowGraph,下方是完整的卡牌图

一张有效的卡牌图需要回答三个问题:

问题示例回答
它什么时候运行?打出这张卡时。
它影响谁?选中的敌人、所有敌人、玩家自己,或解析出的一组单位。
它改变什么?伤害、护甲、抽牌、状态层数、能量、VFX 或存储值。

Demo 的 Poison Arrow 是这样回答的:一个 On Card Played 入口,一个指向选中敌人的 Deal Damage 节点(Amount 3),然后一个 Change Status 节点给目标叠 2 层 Poison,中间穿插播放箭矢命中和毒雾的效果节点。

FlowGraph 窗口中 Poison Arrow 的行为图,入口、伤害、状态和效果节点从左到右连线

第一版建议保持线性结构:入口、目标、一两个状态变更,表现节点放在最后。分支、循环和随机挑选应在简单版本验证通过后再加入。Rapid Fire 是 Demo 中的进阶示例:花 2 点能量,一个 Loop 节点执行 4 轮,每轮对随机一个敌人造成 3 点伤害。

Rapid Fire 的行为图,一个 Loop 节点把随机目标伤害块重复四次


卡牌怎样进入战斗

一张卡可以完全合法,却始终不在战斗里出现。运行时它必须走通这条链:

卡牌必须位于启用的卡牌数据库中,某个卡组必须以至少 1 份引用它,遭遇战的玩家单位必须使用该卡组(或运行期 master deck 包含这张卡),战斗还必须启动对应遭遇战。新卡不出现时,先沿这条链排查,再检查行为图。


校验

Card 模式报告两类问题:

  • Name 为空是错误。
  • FlowGraph 校验器在行为图中发现的问题是警告。

Deck 模式还有自己的检查:条目没有指定卡牌、条目 Copies 低于 1,都是错误。校验无法判断可达性:一张结构正确的卡,仍可能因为没有被启用卡组引用而无法进入战斗。


常见错误

症状可能原因
卡出现了但打不出去费用超过当前能量、战斗不在玩家阶段、卡上有 Unplayable,或某个 Card Playable Hook 拦下了它。
卡要求了错误的目标TargetMode 与图中的目标解析不一致。
卡牌文本与行为不一致修改行为图后没有同步更新描述。应立即修正文案或行为图。
升级没有效果Upgrade 指向 None,或整局中没有任何机制升级这张卡。
关键词无效它被填成了 tag,而不是从 Keywords 下拉中选取。