mcpField
S
scenario-godot-architecture

scenario-godot-architecture

github.com/scenario-labs开发1.2k163 次安装1 天前更新
代码仓库

关于 scenario-godot-architecture

面向 Godot 4.7.2 游戏架构工作的技能指南,适用于 gameplay 程序员和架构师。它结合经过验证的核心系统、工具与测试规则,强调场景独立运行、数据与状态分离及边界验证。

主要功能

  1. 01独立运行的场景架构
  2. 02基于 Resource 的数据定义
  3. 03存档、输入与导出规则
  4. 04架构审计和类型检查
  5. 05GUT 测试与验证流程

适用场景

  1. 01规划可复用的 Godot 场景
  2. 02设计库存、生命值与伤害系统
  3. 03实现安全的存档和加载
  4. 04检查输入重绑定及导出问题

MCP 相关

此技能要求具备 scenario-godot-expert 背景;若项目 GUI 编辑器已打开,指南建议使用该技能的 MCP channel。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-godot-architecture

安装后在 Claude Code 中输入/scenario-godot-architecture确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
sources.md
scripts/
gd_architecture.py
scripts/agentkit/architecture/
arch_audit.gd
bench.gd
build_catalog.gd
pack_probe.gd
scaffold.gd
setup_input.gd
validate_data.gd
scripts/templates/core/combat/
damage_info.gd
health_component.gd
hitbox_3d.gd
hurtbox_3d.gd
scripts/templates/core/data/
item_catalog.gd
item_definition.gd
scripts/templates/core/events/
events.gd
scripts/templates/core/input/
input_actions.gd
input_remap.gd
scripts/templates/core/inventory/
inventory.gd
item_stack.gd
scripts/templates/core/main/
base_level.gd
main_game.gd
scripts/templates/core/save/
persist_component.gd
save_service.gd

详细介绍

Godot 架构(玩法程序员与架构师)

目标:Godot 4.7.2.stable,macOS Apple Silicon。

Godot 专家级架构意味着场景可以独立运行、数据存放在 Resource 中、行为由小型组件构成,并且每个边界(存档文件、输入、导出)都经过测试验证。这项技能汇集了来源中的专家规则,并修正了 Godot 4.7.2 中行为有所不同的部分,还包含经过测试的 G1 核心(物品栏、生命值与伤害、保存与加载、可重新绑定的输入)以及用于构建和审查它的工具。如果此处提到的同级技能不在你的可用技能中,请让用户安装它(npx skills add scenario-labs/skills --skill <name>);若无人值守,请根据工具 schema 继续,并标明这一缺口。

必备背景: scenario-godot-expert(通道、gd_run、审查循环、4.7 陷阱)。

状态(2026-10-02,第 2 轮): 以下内容均已在此处通过 Godot 4.7.2 运行。12 项实时测试中 11 项通过,C# 检查尚未运行。30 项 GUT 测试和 17 项离线 lint 检查也均已通过;第 2 轮探测记录在 references/procedures.md 的 P15 至 P17。

立场(专家增量)

  1. 每个场景都能独立运行;由拥有者注入依赖。 向上查找节点的场景(get_node("/root/...")、 ../)会与特定场景树耦合。使用“向下调用、向上发信号、向外发事件”(Eric Peterson,yB3Wv-Lr7pg [00:05:54];Godot 文档,场景组织)。事件总线 autoload 只持有信号,不持有状态 (Peterson [00:17:36])。状态由场景树较高层级拥有并向下注入,因为 _ready 会先在子节点上运行(Mark Wilson,BgrHnJDt2ww [00:12:29])。 arch_audit.gd 会单独实例化每个场景,并 标记错误、无法解析的 NodePath 以及留空的节点导出属性。

  2. 定义使用 Resource;每个实例的状态则不使用。 将物品数据库存放在类型化的自定义 Resource 中,而不是 Dictionary 中:这样不会出现 ID 拼写错误,路径也能随文件移动而更新(Godotneers,4vAkTHeoORk [00:12:57])。已加载的 Resource 是共享实例:通过一个引用进行的修改会被下一次 load() 看到 (已验证),因此应将定义视为只读。堆叠、生命值和冷却时间应放在 RefCounted 或 Node 对象中。若要为每个实例创建副本(耐久度、随机属性),当 Resource 是扁平结构时使用 duplicate();当它包含外部子资源时使用 duplicate_deep(Resource.DEEP_DUPLICATE_ALL):duplicate(true) 仍会共享这些子资源(已验证)。每个文件只放一个 Resource 类,并为每个 _init 参数提供默认值(Godot 文档,Resources):内部类 Resource 重新加载后其字段变成了 null,而没有默认值的 _init(p_hp) 会丢失字段(已验证)。

  3. 运行时绝不枚举文件夹;应在编辑时构建注册表。 文件夹扫描在编辑器中有效,但在导出版本中会失效(Godotneers [01:13:35])。在导出的 .pck 中,DirAccess.get_files_at 只返回了 *.tres.remap(简单扫描未能加载 3 个中的任何一个),而 list_directory 和来自 build_catalog 的 ItemCatalog 找到了全部 3 个(已验证)。资源包区分大小写:大小写错误的路径在 macOS 上加载时只显示“Case mismatch”警告,但在资源包中并不存在,因此所有文件和文件夹名称都应使用小写 snake_case(Godot 文档,项目组织; lint A11、A12)。.gdignore 必须为空,且不接受任何模式(lint A15)。它会将文件夹从导入器中隐藏,但不会影响 load():其中的 .svg 因“No loader found”而加载失败,而文本 .tres 仍可加载(已验证 [补充])。

  4. 存档文件是数据,绝不是代码。 已在 Godot 4.7.2 中验证 [补充]:

    • 写入 user:// 的 .tres 若嵌入了 GDScript,该脚本会在 load() 时运行;
    • str_to_var 和 ConfigFile.parse 都能从 Object(Node,...) 文本构建 Node。 使用 JSON.from_native 采用 JSON 信封(format、version、data)。它会保留 int、 StringName 和 Vector3,而普通 JSON 会将 3 返回为 3.0。它不接受对象:会记录错误并写入 null。以原子方式写入(.tmp、.bak、重命名),并按版本迁移。从一开始就规划好保存机制(Wilson [00:17:03])。
  5. 稳定的根节点、单层入口点,以及在稳定父节点下生成对象。 MainGame 协调固定容器,关卡提供生成标记,且绝不拥有玩家(FAT Earth Studios,V4SO7foDoW4 [00:04:56])。弹丸应放在 EntityRoot 或 EffectRoot 下,因为释放塔时也会释放其子节点(Godotneers,W8gYHTjDCic [01:05:16])。在空闲时切换关卡(FAT Earth [00:10:13])。在 body_entered 内切换关卡会记录“Removing a CollisionObject node during a physics callback”;request_level() 会延迟执行,处理方式干净。修正源代码中的 instantiate() as BaseLevel [已添加]:类型错误会返回 null,并在旧关卡消失后泄漏该实例,因此应先检查 is BaseLevel,拒绝时释放实例(已验证)。

  6. 输入应集中在一张表中,并由工具写入。 InputActions.defaults() 会传入 setup_input,后者将 [input] 写入 project.godot。每个事件都使用 device = -1。已验证:在代码中构建的手柄事件默认使用设备 0,与手柄 1 不匹配。Godot 4.7 中,键盘报告设备 16,鼠标报告设备 32(版本差异)。按 physical_keycode 绑定按键 [已添加]。将运行时重新绑定保存为 user:// 中的普通 JSON,绝不要保存为 InputEvent 资源。

  7. 静态类型检查是门槛,而非风格选择。 类型警告默认关闭(Godot 文档,Static typing)。在警告级别(1)下,无头模式不会输出任何内容,因此 typing_gate 会在 APFS 克隆上将其设为错误(2)。类型化代码的速度提升低于 Boon 的 116%(mNa0m2fvGOc [00:01:39];见 Numbers)。转换策略(文档,Static typing):x as T 会静默返回 null,而类型化赋值 var l: Label = node 会明确报错(“Trying to assign value of type 'Button' to a variable of type 'Label'”),并中止函数。使用 is 和类型化局部变量;如果类型错误属于 bug,也可以使用类型化赋值;仅在需要处理 null 时使用 as。var n := get_node("X") 会推断为普通 Node,类型检查门槛会拒绝 n.position(“not present on the inferred type Node”):应写成 var n: Node2D = $X。嵌套类型化集合无法编译(“Nested typed collections are not supported”);Array[Array] 和 Dictionary[StringName, Array] 则可以。使用类型化数学函数:在 100 万次调用中,clampf 比 clamp 快 1.70 倍至 1.74 倍,而且对 Colors 使用 clamp() 会导致解析错误,因此应使用 Color.clamp()(已验证)。

  8. 翻转精灵,而不是物理体。 FAT Earth 的规则(zLdTvkLsmgA [00:04:30])仍然成立,但原因不同。已在 2D 中验证:形状会跟随镜像,但 move_and_slide 会将缩放(-1, 1)改写为旋转 180 度、缩放 y 为 -1,因此每帧设置 scale.x = -1 会导致物理体每帧翻转。使用 flip_h 并镜像标记的位置。

  9. preload 是由脚本持有的强引用(FAT Earth,zLdTvkLsmgA [00:05:34])。已验证:预加载的 .tres 会在其持有脚本加载时立即缓存,无需创建实例;即使探测器释放了该脚本,它仍会留在缓存中;在 _ready 中调用 load() 的资源则会在节点释放时被释放。一个永久节点若预加载菜单 > 过场动画 > 关卡,会让整个引用链保持存活:将小型片段预加载在其使用者附近,按需加载关卡和菜单(lint A09)。

其他来源中的内容(简短规则)

规则来源此处的检查
开放集合通过鸭子类型进行交互:has_method(&"take_damage"),将群组用作分类器,使用 propagate_call(&"on_game_over")、call_group。自己的组件使用 is Hurtbox3DGodotneers,W8gYHTjDCic [00:19:36, 00:38:12]propagate_call 到达了孙节点
碰撞无响应,首先应检查图层或遮罩是否配置错误W8gYHTjDCic [01:03:09]arch_audit 标记图层 0、遮罩 0;unmatched_masks(P16)标记没有物体将其用作图层的遮罩位
将状态 Resource 作为事实来源,便可导出并重新加载整个游戏,以便重新测试 Bug。仅限开发版本:绝不加载玩家提供的 .tres(姿态 4)Szuflita,1qHPKg_Xovs [00:06:11]导出到 user://debug/,使用 CACHE_MODE_IGNORE 重新加载:值保持不变
top_level = true,或在两个 Node2D 之间放置一个普通 Node,都会切断变换继承Godot 文档,场景组织父节点移动到 x=100:两个子节点都留在 x=10,普通子节点则移动到 110
ItemList.select() 不会发出 item_selected:请调用处理函数。列表请求的尺寸为 0x0:设置 custom_minimum_size(来源中为 250 px)4vAkTHeoORk [00:53:45, 00:47:32]0 个信号,最小尺寸为 (0, 0)
制作检查要先复制配方,再从中删除物品4vAkTHeoORk [01:07:14]Inventory.has_all 测试
在鼠标被捕获的 3D 游戏中打开对话框时,应设置 MOUSE_MODE_VISIBLE,并在关闭时恢复之前的模式4vAkTHeoORk [00:37:01]无论如何设置,无头模式读取到的模式都是 0:请在窗口模式下测试或手动测试
尽早构建调试用的上帝模式(无重力、无受伤判定盒、速度更快)zLdTvkLsmgA [00:02:34]概念,未运行
Git:忽略 .godot/,提交 .uid 旁文件,在 .gitattributes 中配置 LFS 模式,并在添加插件或升级 Godot 前进行干净提交BatteryAcidDev,c3Jf-av_5NE [00:02:40, 00:22:20]git check-attr 将 png 路由到 lfs;脚本会获得 .gd.uid,文本 .tres 文件则不会(未安装 git-lfs)

首先确定

  • 2D 还是 3D、类型、单人还是本地多人(本地多人需要每台设备各有一组操作,而不是 device = -1)。
  • 目标平台(导出平台区分大小写;C# 无法导出到 Web)。
  • 语言:默认使用 GDScript;使用 C# 需要 .NET 构建版和 SDK(检查是否有 .csproj;参见 references/expert-notes.md)。
  • 存档需求(存档位、自动存档、版本控制、云端)、设备(手柄、重新绑定),以及 GUI 编辑器是否已打开该项目(若已打开,则使用 scenario-godot-expert 的 MCP 通道)。

如果没有其他要求,默认使用:静态类型 GDScript、分层布局、仅包含信号的 Events 自动加载项、使用 Resources 定义数据、版本为 2 的 JSON 存档、根据表格构建输入,以及 GUT 测试。

工作流

设置:import gd_architecture as A。P 是你通过 gd_env.base_project("3d", ...) 创建的克隆。

  1. 项目。 运行 A.unique_user_dir(P, "<unique name>")(所有 Base3D 克隆共用同一个 user://,除非这样处理;这是实测结果),然后运行 A.scaffold_folders(P)、A.install_core(P) 和 A.register_autoload(P, "Events", "res://core/events/events.gd"),接着运行 gd_run.import_project(P)。门禁: project.godot 包含 3d/physics_engine="Jolt Physics" 和 stretch 配置行,自动加载项不超过少数几个。
  2. 主场景。 运行 A.build_main_scene(P, props={"player_scene": ...})。它会构建 Main(始终存在)> Systems;World(可暂停)> LevelRoot、EntityRoot、EffectRoot;CanvasLayers HUD 10、PauseLayer 20(暂停时)、TransitionLayer 30 和 DebugLayer 40(始终存在),并为后续层留出间隔(FAT Earth)。门禁: A.audit_scenes(P) 没有任何标记;暂停测试显示 World 仍在运行,而 TransitionLayer 也在运行。
  3. 数据。 编写 ItemDefinition .tres 文件(id、max_stack、tags、icon、world_scene)。运行 A.build_catalog(P),然后运行 A.validate_resources(P, root=..., cls="ItemDefinition", required=(...))。门禁: 问题数为 0、id 唯一,且重新加载后的数量与文件数量相等。
  4. 组件。 HealthComponent(Node:信号、抗性,died 只触发一次)、Hurtbox3D 和 Hitbox3D(Area;hurtbox 具有导出的 health 和配置警告)、Inventory(RefCounted:堆叠、剩余物品、全有或全无的移除),以及作为胶合层的精简 Player 脚本。组件应保持简单(Firebelley,rCu8vQrdDDI [00:03:42])。门禁: GUT 测试通过,包括造成 25 点伤害的真实物理重叠测试。
  5. 存档和加载。 为每个需要存档的实体添加 PersistComponent(save_id、properties、目标的 save_state())。其余工作由 SaveService.collect、write、read 和 apply 完成。门禁: 测试类型往返、损坏后回退到 .bak、v1 到 v2 的迁移(恢复 int),以及整个游戏(物品栏、生命值、位置)。
  6. 输入。 编辑 InputActions 表,运行 A.setup_input(P),并在运行时使用 InputRemap。门禁: 13 个操作,每个事件的 device 都为 -1;测试 parse_input_event、匹配手柄 1、冲突转移,以及绑定往返。
  7. 门禁。 运行 A.typing_gate(P)、A.lint(P)、冒烟测试(破坏一个守卫条件并观察恰好一个测试失败),以及 A.pack_smoke(P, out, args)。门禁: 全部通过。参见质量门禁。

数值

数值此处测量结果(本 Mac 上的 4.7.2 编辑器二进制文件)对比基准
类型化与非类型化冒泡排序,n=200082.5 与 124.1 ms(1.50x;3 次运行中为 1.46x 到 1.50x)Boon 4.1:快 116%
Array.sort,n=200,00023.5 与 23.5 ms(1.00)类型化对内置函数没有帮助
GUT 套件,G130 个测试,129 条断言,0.8 sbite 测试:129 条中的 126 条
导出的 .pck,G11.59 MB,冒烟运行 0.3 s无需导出模板
严格门禁命中数(unsafe_call_argument)8 个文件中共 33 次命中默认门禁:22 个文件中为 0
clamp 与 clampf,1M 次调用29.5 到 30.9 与 17.1 到 17.9 ms(1.70x 到 1.74x,4 次运行)类型化数学辅助函数

质量门禁

  • 可测量:
    • typing_gate 返回 0 个警告作为错误;
    • lint 不返回任何错误发现(A03 文件夹扫描、A04 user:// .tres、A05 str_to_var、A07 内部 Resource、A08 无默认值的 _init、A11 路径大小写);
    • audit_scenes 不返回任何标记;
    • validate_resources 不返回任何问题;
    • GUT 全绿,bite 测试变红;
    • pack_smoke 显示 list_directory 和目录正在包内加载;
    • 通过文本检查 project.godot 中的 Jolt、主场景和自动加载项。
  • **视觉:**无头模式不渲染任何内容。对于此核心中的任何库存或调试 UI,使用窗口模式捕获 gd_run.capture_scene(P, "res://ui/inventory_debug.tscn", out="captures/inv.png", size=(1280, 720)), 运行 image_checks 并打开图像(P17:11 帧,0.17 s)。布局和体验归 scenario-godot-ui 与 scenario-godot-gameplay 负责。在 GUI 编辑器中,Scene 停靠面板的警告图标只会出现在预期位置。

常见错误

错误表现修复
使用普通的 JSON.parse_string 保存count 变成 3.0,Vector3 变成字符串使用 from_native 和 to_native,或在 v1 中使用 _restore_ints
从作业中调用 ProjectSettings.save()标头注释和默认键被删除(已观察到)这是预期行为;之后重新检查 Jolt 和伸缩设置
load(path).instantiate()门禁中出现不安全的方法访问使用 var ps: PackedScene = load(path) 或 preload
在 add_child 前读取 @onreadynull在构造函数中设置字段;在 add_child 后读取节点(Godotneers [00:33:06])
GUT 9.7.1 assert_engine_error(1)测试失败,提示“Use assert_engine_error_count”使用 assert_push_error("text") 或 assert_engine_error_count(n)

交接

  • 接收方: scenario-godot-expert(简报、通道、工具包);scenario-* 技能(ItemDefinition.icon 和 world_scene 引用的图标和模型)。
  • 交付给: scenario-godot-gameplay(这些组件的状态机、战斗手感); scenario-godot-ui(基于 Events 和 Inventory.changed 的 HUD,通过 InputRemap.rebind 重新绑定); scenario-godot-2d 和 scenario-godot-3d-world(带有生成标记的 BaseLevel 场景); scenario-godot-multiplayer(对生命值和物品栏的权威控制); scenario-godot-pipeline-automation(各项门禁的 CI);scenario-godot-performance-export(实际导出)。
  • 形式: core/ 文件夹、project.godot 条目、测试和证据 JSON。

Godot 4.7 说明

  • 核心中使用了类型化字典(4.4)。.uid 伴随文件(4.4)必须与其脚本一起移动 (lint A14)。ResourceLoader.list_directory(4.4)和 duplicate_deep(4.5)均已提供。
  • FileAccess.store_* 返回布尔值(4.4):重命名前请检查。
  • class_name 只有在 --import 后才会解析。脚本若预加载自身场景,在 .tscn 存在之前会解析失败(已验证)。
  • Agent 编写的 project.godot 默认使用 GodotPhysics3D。请明确写入 Jolt,并在任何 ProjectSettings.save() 后重新读取文件。

参考资料

评论

支持 Markdown