- 游戏开发
- UI组件
【免费下载链接】godot-card-game-framework
A framework which comes with prepared scenes and classes to kickstart your card game, as well as a powerful scripting engine to use to provide full rules enforcement.
本文以仓库中的 QUICKSTART.md 为主线,完整还原"从空白 Godot 项目跑起 CGF 演示、再逐步搭建出完全属于自己的卡牌游戏工作区"的全过程。文中所有操作步骤均结合 CFControl.gd、CardTemplate.gd、CardConfig.gd 等核心源码逐条印证底层调用链,读完后你能够独立完成 CGF 的初始化、场景继承定制、新卡牌类型创建与卡牌数据定义,并理解框架是如何把一个字典定义的卡牌实例化为可交互场景的。
适用前提说明:从当前仓库源码的 GDScript 语法(yield、class_name Reference、export var、PackedScene等)推断,本文流程以 Godot 3.x 系列编辑器为前提,原文档头部亦标注其基于 v1.9 编写、后针对 3.5.3.stable 版本更新。
Step 1:创建新项目并初始化 CGF
这一步的目标是让一个全新项目以 CGF 自带的演示场景为基线跑起来,验证环境正确。
- 用 Godot 创建一个新空项目。
- 下载最新版 CGF,将全部目录解压到项目根目录。解压过程中可以保持 Godot 打开,切回 Godot 后它会自动导入全部新文件。
- 进入 Project > Project Settings > Autoload,点击浏览按钮定位
res://src/core/CFControl,并在 "Node name:" 字段中输入cfc。 - 进入 Project > Project Settings > General > Application > Run,在 Main Scene 中填入
res://src/custom/CGFMain.tscn。 - 关闭设置面板,按 F5 运行项目。一个基础演示界面会出现,你可以抽卡、移动卡片等。
源码印证:当前仓库的 project.godot 中[autoload]一节已按上述第 3 步配置:cfc="*res://src/core/CFControl.gd"。CFControl是框架的全局单例,几乎所有框架行为都经由它调度——它在_ready()中完成设置加载、脚本集加载与节点映射,并向外发出all_nodes_mapped、scripts_loaded、new_card_instanced等信号(见 CFControl.gd)。
而 Main Scene 指向的 CGFMain.tscn 本身就是一个继承场景,它对 Main.tscn 做了两件事:
[node name="Main" instance=ExtResource( 1 )] board_scene = ExtResource( 2 ) info_panel_scene = ExtResource( 3 )也就是把board_scene和info_panel_scene两个导出变量分别指向CGFBoard.tscn与CGFInfoPanel.tscn。这两个变量定义在 ViewportCardFocus.gd:
export(PackedScene) var board_scene : PackedScene export(PackedScene) var info_panel_scene : PackedScene在_ready()中,主场景会把board_scene.instance()挂到游戏视口下,并把info_panel_scene交给右侧信息面板(ViewportCardFocus.gd)。理解这一点很重要:整个 Step 2 的自定义工作,本质就是在这些 PackedScene 变量上做替换,这正是原作者建议"基于继承场景开发"的原因——升级框架时你的自定义场景不会被覆盖。
Step 2:为自定义做准备(基于继承场景)
为了将来升级框架而不丢失自己的修改,强烈建议所有自定义都建立在继承场景 / 复制脚本之上。以下分四个子步骤。
2.1 创建自定义 Board 与 Info Panel 场景
- 新建目录存放你自己的游戏定义,例如
res://src/new_card_game。 - 在 FileSystem 面板中,右键 "Duplicate..." 以下文件:
res://src/custom/CGFBoard.tscn→ 重命名为Board.tscnres://src/custom/CGFBoard.gd→ 重命名为Board.gdres://src/custom/CGFInfoPanel.tscn→ 重命名为InfoPanel.tscn
- 把
Board.tscn、Board.gd、InfoPanel.tscn以及SP.gd(脚本引擎常量,SP.gd)从res://src/custom移动(或拖拽 / Ctrl 多选后 Move To)到res://src/new_card_game。 - 打开
Board.tscn,右键根节点 "Attach Script",指向res://src/new_card_game/Board.gd。 - 保存场景后关闭并重新打开项目(否则下一步可能不生效)。
- 右键
res://src/core/Main.tscn,选择 "New Inherited Scene"。 - 在 Inspector 的 "Script Variables" 中,点击 Board Scene 旁的 "[empty]" → "Load",选择
res://src/new_card_game/Board.tscn。 - 同理,为 Info Panel Scene 加载
res://src/new_card_game/InfoPanel.tscn。 - 将未保存场景另存为
res://src/new_card_game/Main.tscn。 - 按 F6 运行当前场景。如果一切正确,会得到与演示完全相同的效果。可以在新 Main 场景脚本的
_ready()末尾加一行print("something"),Output 窗口出现该字样即证明你运行的确实是新场景。
此时你已经拥有一个可定制的继承版 Main.tscn 和自定义 Board.tscn;但 Board 内部仍大量复用res://src/custom的演示代码(例如CGFDeck.gd、CGFHand.gd),接下来要把卡牌容器也换成自己的:
- 复制
res://src/custom/CGFDeck.gd为Deck.gd,移入res://src/new_card_game。 - 打开
Board.tscn,在 "Deck" 节点上 "Attach Script" →res://src/new_card_game/Deck.gd。 - 选中 "Deck" 节点,在 Inspector 中选择适合你游戏的 "Placement"。
- 对
CGFDiscard.gd和CGFHand.gd重复同样操作(新文件分别命名为Discard.gd、Hand.gd)。
注意:你也可以直接 Detach 旧脚本、再 Attach 一个继承
Pile/Hand的新脚本。但按上面四步复制演示脚本,能白拿到一批通用起步函数(如抽卡),便于在其上叠加开发。
2.2 创建自定义卡牌模板
- 复制以下文件:
res://src/custom/CGFCardManipulationButton.tscn→CardManipulationButton.tscnres://src/custom/CGFManipulationButtons.gd→ManipulationButtons.gd
- 移入
res://src/new_card_game。 - 打开
CardManipulationButton.tscn,右键 "ManipulationButton" 节点 "Attach Script" →res://src/new_card_game/ManipulationButtons.gd。 - 选中该节点,Inspector 中点击 Manipulation Button 旁的 "[empty]" → "Load" →
res://src/new_card_game/CardManipulationButton.tscn。 - 再复制:
res://src/custom/CGFCardBack.tscn→CardBack.tscnres://src/custom/CGFCardFront.tscn→CardFront.tscnres://src/custom/CGFCardFront.gd→CardFront.gd
- 移入
res://src/new_card_game。 - 打开
CardFront.tscn,右键根节点 "Attach Script" →res://src/new_card_game/CardFront.gd。 - 右键
res://src/core/CardTemplate.tscn,选择 "New Inherited Scene",点击根节点。 - 在 Inspector 中点击 Card Back Design 旁的 "[empty]" → "Load" →
res://src/new_card_game/CardBack.tscn。 - 点击 Card Front Design 旁的 "[empty]" → "Load" →
res://src/new_card_game/CardFront.tscn。 - 另存为
res://src/new_card_game/CardTemplate.tscn。
注意:如果找不到 "Card Back Design" / "Card Front Design" 字段,请在 Godot 菜单中执行 Project > Reload Current Project。这两个字段正是 CardTemplate.gd 中声明的card_back_design与card_front_design导出变量:
export(PackedScene) var card_back_design : PackedScene export(PackedScene) var card_front_design : PackedSceneCard._init_card_layout()会在卡牌实例化时分别instance()这两个场景并塞入Control/Front、Control/Back容器(见 CardTemplate.gd)。这里同样是"复制演示代码再改造"的策略:完全从零写也可以,但复制演示件能在 quickstart 阶段保持与演示一致的功能。
2.3 让游戏指向新的自定义卡牌目录
- 把
res://src/custom/CFConst.gd移动到res://src/new_card_game/。 - 新建文件夹
res://src/new_card_game/cards。 - 再新建
res://src/new_card_game/cards/sets。 - 在 FileSystem 中进入
res://src/custom/cards。 - 把
CardConfig.gd和CustomScripts.gd移动到res://src/new_card_game/cards。 - 把
res://src/custom/cards/sets/SetPreload.gd移动到res://src/new_card_game/cards/sets/。 - 编辑
res://src/new_card_game/CFConst.gd。 - 找到
const PATH_CUSTOM := "res://src/custom/",改为const PATH_CUSTOM := "res://src/new_card_game/"。
现在框架会在你的新目录结构中查找卡牌定义(Card Definitions)、卡牌脚本(Card Scripts)等。修改路径的"威力"体现在 CFConst.gd 的一组派生常量上:
const PATH_CORE := "res://src/core/" const PATH_CUSTOM := "res://src/custom/" const PATH_CARDS := PATH_CUSTOM + "cards/" const PATH_SETS := PATH_CARDS + "sets/" const CARD_SET_NAME_PREPEND := "SetDefinition_" const SCRIPT_SET_NAME_PREPEND := "SetScripts_"PATH_CARDS与PATH_SETS都由PATH_CUSTOM拼接派生,所以只改一处即可整体切换。CFControl.load_card_definitions()正是扫描PATH_SETS目录下所有以SetDefinition_前缀开头的文件来合并卡牌定义;load_script_definitions()同理扫描SetScripts_前缀文件(见 CFControl.gd)。
在res://src/new_card_game/Main.tscn上按 F6:应该出现相同的演示桌面,但牌堆是空的——因为你还没有定义任何卡牌。如果一切正常,把项目设置里 Application > Run > Main Scene 改为res://src/new_card_game/Main.tscn,按 F5 运行整个项目验证。
Step 3:创建新的卡牌类型(Creature)
本节创建一个 "creature" 类型的卡牌基线,作为一类卡牌的场景模板。
- 在 Godot 文件视图中进入
res://src/new_card_game/。 - 右键
CardFront.tscn选择 "New Inherited Scene",按 F1 确认处于 2D 视图。你会看到一张灰色卡牌——这就是来自 CGF 的卡面基线。 - 在 Scene 面板中双击 "Front" 重命名根节点,改为 "CreatureFront"(纯粹是提醒,无其他作用)。
- Inspector 中展开 Theme Overrides -> Styles,点击 StyleBoxFlat 旁的箭头选择 "Make Unique"。
- 展开 Panel 标签页(Styles 正下方,默认勾选)。
- 点击 "Bg Color",在取色器中挑选你希望作为生物卡背景的颜色。
- 基础卡模板已有生物所需的大部分信息,但生物卡不能没有生命值。先保存场景为
res://src/new_card_game/cards/CreatureFront.tscn,然后右键根节点(此时名为 CreatureFront)选择 "Remove Inheritance"——子节点全部由灰变白。接着:- 右键 "Power" 标签 > Duplicate,得到 "Power2";双击重命名为 "Health",可输入文字预览效果。
- 右键 "PowerIcon" > Duplicate,得到 "PowerIcon2";双击重命名为 "HealthIcon",拖到 "Health" 正上方(拖不动就手动改 Inspector 中 Control -> Rect -> Position,x=131、y=-18 左右合适)。
- 选中 HealthIcon,点击 "Texture" 属性旁的小箭头,选择 Quickload,输入 "hearts",选中 hearts.png(对应 assets/icons/hearts.png)。
- 保存场景为
res://src/new_card_game/cards/CreatureFront.tscn。 - 右键
res://src/new_card_game/CardTemplate.tscn选择 "New Inherited Scene"。 - 双击根节点 "Card" 重命名为 "Creature"。
- 在根节点 Inspector 中,点击 Card Front Design 旁的箭头 > Load,选择
res://src/new_card_game/cards/CreatureFront.tscn。 - 按 Ctrl+S 另存为
res://src/new_card_game/cards/Creature.tscn。至此新的卡牌类型场景模板完成,但游戏配置尚未指向它。
接下来处理卡面脚本。由于我们没有做一个完全不同的卡面,只需扩展现有默认卡面脚本:
- 右键根节点(名为 CreatureFront)选择 "Extend Script",在弹窗中把脚本保存为
res://src/new_card_game/cards/CreatureFront.gd。 - 新脚本会打开,在
_ready()方法中加入:
func _ready() -> void: card_labels["Health"] = find_node("Health")这样就把新增的 Health 标签映射进了card_labels字典,代码即可通过它找到该节点。
为什么必须做这步映射:CardFront.gd 的card_labels字典是卡牌数据与界面标签之间的"接线表"。演示版 CGFCardFront.gd 在_ready()中完成了 Name / Type / Tags / Requirements / Abilities / Cost / Power 的映射,并设置card_label_min_sizes(用于计算字号收缩的矩形上限)与original_font_sizes(标准尺寸下的起始字号,如 Cost/Power 为 15、Abilities 为 25)。当你新增 Health 标签后,若不做映射,Card.setup() 在填充属性时会走到 refresh_property_label() 的分支,因为card_labels里没有 "Health" 而打印Warning: Health does not have a matching label!并返回CFConst.ReturnCode.FAILED(见 CardTemplate.gd)——卡牌定义里的 Health 数值将无处显示。
最后告诉框架 Health 是数字属性:打开res://new_card_game/cards/CardConfig.gd(即第 2.3 步移入的新文件),修改PROPERTIES_NUMBERS为:
const PROPERTIES_NUMBERS := ["Cost","Power","Health"]此时生物卡模板就绪。
Step 4:用字典定义第一张卡牌
这一步用 CGF 的"JSON 风格字典格式"定义卡牌,框架会在请求该卡牌名时自动创建它。
- 把
res://src/custom/cards/sets/SetDefinition_Demo1.gd复制为res://src/new_card_game/cards/sets/SetDefinition_Core.gd。 - 把
res://src/custom/cards/sets/SetScripts_Demo1.gd复制为res://src/new_card_game/cards/sets/SetScripts_Core.gd。
这两步并非必需,但能拿到现成可改的字典结构。
- 编辑
res://src/new_card_game/cards/sets/SetDefinition_Core.gd。你会看到来自 Framework 演示的两张(实际更多)卡牌定义,删掉它们,改为定义两张生物卡:
extends Reference const SET = "Core Set" const CARDS := { "Battle Beast": { "Type": "Creature", "Tags": ["Brutal", "Slow"], "Requirements": "", "Abilities": " ", "Cost": 2, "Power": 3, "Health": 3, }, "Beast in Black": { "Type": "Creature", "Tags": ["Fast", "Flanking"], "Requirements": "Cannot be played on first turn", "Abilities": " ", "Cost": 1, "Power": 2, "Health": 1, }, }- 按 F5 运行,抽几张卡——此时抽到的应该只有你的 "Beast" 系列卡牌。
"卡牌自动出现在牌堆"背后的调用链:由于你复用的是CGFBoard.tscn演示代码,其 CGFBoard.gd 的_ready()会调用load_test_cards(false),遍历cfc.card_definitions中的全部卡牌(跳过 "Spawn Card"),用cfc.instance_card(name)随机实例化并塞进cfc.NMAP.deck。而instance_card()的关键逻辑只有两行(见 CFControl.gd):
func instance_card(card_name: String) -> Card: var template = load(CFConst.PATH_CARDS + card_definitions[card_name][CardConfig.SCENE_PROPERTY] + ".tscn") var card = template.instance() card.canonical_name = card_name emit_signal("new_card_instanced", card) return(card)它读取卡牌定义中CardConfig.SCENE_PROPERTY(默认值"Type",见 CardConfig.gd)字段的值,拼接到PATH_CARDS后加载.tscn并实例化。这就是为什么定义里的"Type": "Creature"必须与场景文件名Creature.tscn严格对应——Type属性直接决定"用哪个场景模板生成这张牌"。随后Card._ready()调用setup(),按CardConfig中声明的属性分类把字典值逐个填入对应标签。
正式游戏不应依赖这种随机发牌逻辑,而应自己实现卡组创建、存储与加载——这一点在原文档中也明确指出当前属于框架范围之外。
深度原理:卡牌定义如何驱动界面
前面步骤已经反复触及,这里把三个关键机制串起来,便于你后续扩展新属性、新卡面。
1. 属性分类决定显示方式。CardConfig.gd 把卡牌字典中的属性分成几类:PROPERTIES_STRINGS(原样写入标签,如 Type / Requirements / Abilities)、PROPERTIES_NUMBERS(转换为字符串后显示,如 Cost / Power)、PROPERTIES_ARRAYS(用CFConst.ARRAY_PROPERTY_JOIN即' - '连接后显示,如 Tags)。此外还有:
| 常量 | 作用 | 演示默认值 |
|---|---|---|
SCENE_PROPERTY | 该属性值决定使用哪个场景模板(须与文件名一致) | "Type" |
NUMBER_WITH_LABEL | 数值前会加"属性名: "前缀 | [] |
NUMBERS_HIDDEN_ON_0 | 为 0 时隐藏标签以腾出空间 | [] |
TYPES_TO_HIDE_IN_CARDVIEWER | 该类型卡牌不在 CardViewer 中显示 | ["Token"] |
REPLACEMENTS | 显示时替换特定取值(如'U'→'') | {} |
CARD_BBCODE | 卡面 RichTextLabel 中的 bbcode 占位符替换 | {damage}→ 宝剑图标 |
以_下划线开头的属性(如_max_allowed、_illustration、_hide_in_deckbuilder)被视为"元属性",只供逻辑使用,不会尝试显示在卡面上。
2. 标签自适应收缩。新增的 Health 标签之所以"自动"能塞进卡面,是因为 CardFront.gd 的set_label_text()会调用_adjust_font_size():只要换行后的文字高度超出card_label_min_sizes设定的矩形上限,就逐号缩小字号,直到放不下为止(下限 5 号字,见 CardFront.gd)。计算结果还会按 "卡片缩放比例 + 标签名 + 文本 MD5" 写入字体缓存(CFConst.FONT_SIZE_CACHE,即user://CGFFontCache.json),避免重复计算拖慢发卡速度。这也解释了 Addendum 1 的提示:把 Health/Power 从数字属性挪到字符串属性、或调整标签映射,都会影响这块收缩逻辑的行为。
3. 卡牌容器与全局节点映射。运行游戏后,所有 Board / Hand / Pile 都会通过cfc.map_node()注册进cfc.NMAP(小写命名访问,如cfc.NMAP.deck、cfc.NMAP.hand),映射齐备后触发all_nodes_mapped信号放行各容器的ready()(见 CFControl.gd)。你在Board.gd里写抽牌、弃牌逻辑时,直接通过 NMAP 取容器即可,无需硬编码节点路径。
Addendum:官方附注
附注 1:让卡面更整洁。你可能觉得卡面底部偏挤,甚至 Health 会溢出卡面。更好的做法是只显示数值、用 "/" 分隔 Power 与 Health。官方给出三条思考线索:
- 尝试减少或移除 CardFront.gd(你的
new_card_game/CardFront.gd)中的 "Health" 与 "Power" 映射条目,运行时标签字号会发生什么? - 检查把 "Power" 和 "Health" 从
CardConfig.PROPERTIES_NUMBERS移到CardConfig.PROPERTIES_STRINGS后的显示差异。 - 想想如何把 Health 与 Power 的节点布局改成放进同一个 HBoxContainer、中间用一个仅含 "/" 文本的新标签分隔;同时需要调整 CreatureFront.gd 中对应标签的映射方式。
附注 2:牌堆里的初始卡是哪来的?如果你好奇开局牌堆中的卡牌从何而来:这一切定义在res://src/new_card_game/Board.gd(即你复制的 CGFBoard.gd)里。_ready()中的load_test_cards()会从所有已定义卡牌中随机选取若干张塞入牌堆——这正是你在 Step 4 定义完 "Beasts" 后什么都没做、它们却自动出现在牌堆里的原因。正式游戏应当包含自己创建、存储与加载卡组(deck)的逻辑,而不是依赖随机数(当前框架尚不提供这部分)。
附注 3:核心术语。CGF 通过统一术语帮助你(和框架文档)对齐概念:
- Card:卡牌游戏的核心概念。CGF 中 Card 类持有卡牌场景移动与操控的全部逻辑。
- CardContainer:从名字可知,它是用来容纳卡牌的容器,是 Hand 与 Pile 的超集统称。
- Hand:以正面朝上、便于玩家查看全部卡牌的方式持牌的 CardContainer。
- Pile:以叠放方式持牌的 CardContainer,牌堆(deck)与弃牌堆(discard)都是 Pile。
- Board:卡牌可以被放置、主要游戏行为发生的区域。
- Manipulation Button:鼠标悬停在 Card 或 CardContainer 上时浮现的按钮,可以挂上几乎任意代码,通常用于以某种方式操控其附着对象。
- Token:放置在卡牌上的计数器,按游戏规则追踪和度量各类变化。
小结
按照 QUICKSTART.md 的四步走:初始化项目(Autoloadcfc+ Main Scene)→ 用继承场景复制出 Board / Deck / Discard / Hand / 卡模板 → 通过CFConst.PATH_CUSTOM切换自定义目录 → 新建 Creature 卡类型并定义第一张卡牌,你就拥有了一个可独立演进的 CGF 游戏工作区。后续若需要卡牌自动执行能力(抽牌后双击触发效果、目标指向、触发器链),下一步应继续阅读 SCRIPTING_ENGINE.md 与 INSTALL.md,它们与本文共享同一套CFConst/CardConfig/ Set 目录约定,衔接非常自然。
- 游戏开发
- UI组件
【免费下载链接】godot-card-game-framework
A framework which comes with prepared scenes and classes to kickstart your card game, as well as a powerful scripting engine to use to provide full rules enforcement.
相关推荐
godot-card-game-framework:从安装部署到脚本引擎的卡牌游戏框架实战指南
godot card game framework:从安装部署到脚本引擎的卡牌游戏框架实战指南 godot card game framework(下文简称 C
游戏开发UI组件Godot Card Game Framework 安装指南:从 src 目录拷贝到可升级的卡牌游戏框架
Godot Card Game Framework 安装指南:从 src 目录拷贝到可升级的卡牌游戏框架 Godot Card Game Framework(下
游戏开发UI组件xcms:现代代谢组学分析的R语言数据处理框架
xcms:现代代谢组学分析的R语言数据处理框架 在代谢组学、脂质组学和蛋白质组学研究中,液相色谱 质谱联用(LC MS)数据处理是分析流程中的关键环节。xcms
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考