关于 scenario-godot-gameplay
面向 Godot 4.7.2 的 gameplay 系统编程技能,供构建导航、AI、战斗、对话和程序化内容的开发者使用。它强调用无场景决策逻辑、数据定义和可测量的基准验证系统。
主要功能
- 01导航网格构建与同步
- 02基于规模选择 AI 方案
- 03碰撞、命中框与子弹设计
- 04存档验证和版本迁移
- 05对话、任务及程序化内容流程
适用场景
- 01测量不同规模敌群的帧耗
- 02验证 NavigationAgent3D 移动
- 03测试命中检测与高速子弹
- 04检查存档、对话和地牢生成
MCP 相关
此技能要求 scenario-godot-expert 背景;输入说明未指出它自身提供 MCP 服务或客户端功能。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-godot-gameplay安装后在 Claude Code 中输入/scenario-godot-gameplay确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/agentkit/gameplay/arena.gdbrain.gdbullet_pool.gdcombat_layers.gdcrowd_bench.gddungeon_gen.gdenemy_body.gdenemy_manager.gdhero_enemy.gdhit_log.gdhitbox_3d.gdhurtbox_3d.gdnav_tools.gdperception.gdquest_rules.gdsave_io.gdstate_machine.gdstate.gdscripts/agentkit/gameplay/inventory/inventory.gditem_stack.gditem.gdscripts/agentkit/gameplay/states/attack.gdchase.gdpatrol.gdsearch.gd详细介绍
Godot 游戏玩法系统(游戏玩法系统程序员)
目标:Godot 4.7.2.stable、Jolt、macOS Apple Silicon。
在专家级实践中,游戏玩法系统是无需场景即可测试的小型契约:纯决策函数、用于数据的 Resources、用于发现的组。它们的成本应按游戏实际需要的 Agent 数量衡量,而不是等到有人质疑时才讨论。核心做法:将规则构建为数据,在 --fixed-fps 60 下无头运行,然后查看一张截图。如果此处提到的同级技能不在你可用的技能中,请让用户安装它
(npx skills add scenario-labs/skills --skill <name>);如果无人值守,则根据工具架构继续操作,并标明这一缺口。
必备背景: scenario-godot-expert(channels、gd_run、审查循环、4.7 陷阱)。
状态(2026-10-02): 16 项实时测试、第二轮检查(P15)和离线套件均已在 Godot
4.7.2 中通过(references/procedures.md、tests/code/godot-gameplay/)。
Dialogue Manager 4.1.0、LimboAI 1.8.1 和 GUT 9.7.1 均在各自的项目副本中运行过。
做法(专家级要点)
- 使用
--fixed-fps 60进行基准测试,绝不使用普通无头模式。 无头帧会休眠 6.9 ms (low_processor_usage_mode_sleep_usec),物理帧数少于渲染帧数。Performance.TIME_*监视器大约每秒刷新一次,在固定帧率下读数为 0。测量每帧实际耗时(毫秒),取 3 次测量的中位数,并比较同一次运行中的 A/B 配对结果(实测,P0)。 - 人群规模并非基准测试所认为的瓶颈。 在 M5 Max 上,200 个启用了 NavigationAgent3D 避让的 CharacterBody3D 敌人每帧耗时约 2 ms。成本随数量线性增长,800 个 Agent 时为 7.3 ms;200 个 Agent 时,避让本身的成本处于噪声范围内。调整
max_neighbors或重新寻路间隔没有带来可测的收益。避让的价值在于正确性:启用时没有重叠对象对,不启用时则有 3108 对(P2)。只有当 Agent 不需要节点时,才改用服务端 Agent 加一个 MultiMesh(200 个耗时 0.26 ms)。 - 等待你分配的网格,而不是等待“地图”。 文档说等待一个物理帧。实际运行中,首次同步耗时 3 帧。重新烘焙后,地图 ID 已改变,但区域仍提供旧多边形。分配前读取
region_get_iteration_id,等待其变化,然后再进行一次地图同步(nav_tools.wait_applied,P1)。 - 从静态碰撞体组烘焙。 新建的 NavigationMesh 会从区域的子节点解析 Both(网格和碰撞体),因此放在区域下的 NPC 会挖出一个 3 m 的空洞(Bramwell 2W4JP48oZ8U 00:12:37;P1 实测)。
- 根据决策条件选择 AI 档位。 人群使用枚举式大脑(Shaggy Dev oqFbZoA2lnU 00:00:32),英雄或玩家角色使用节点 FSM。设计师编写行为时使用行为树(Chap.C E_FIy2dTkNc);只有在许多通用动作会重新组合时才使用 GOAP(Wahaha Yes cm5Jxo31plw)。预算参考:每个 tick 处理 40 个敌人、以 5 Hz 运行,耗时比不运行 AI 高 0.07 ms;每个 tick 处理所有敌人则高 0.45 ms(P3)。
- Hitbox 使用 mask,Hurtbox 使用 layer,每次挥击使用一个 HitLog(Queble cX-vzfmzjnE 00:06:36、00:12:55)。 新建的区域默认 layer 为 1、mask 为 1,因此会命中自身所属对象(P4)。
- 快速子弹应使用数据,或使用启用了 CCD 的刚体。 不启用
continuous_cd时,即使速度达到 30 m/s,8 个球中仍有 6 个穿过了 0.1 m 厚的墙。Jolt 会将速度限制在 500 m/s。由一个 MultiMesh 绘制的射线扫掠式数据子弹,每 1000 发耗时 0.35 ms;使用 CCD 的池化 RigidBody3D 则耗时 1.05 ms(P5)。 .tres存档就是代码。 GDScript 子资源会在加载时运行(Godotneers 43BZsLZheA4 01:03:14;P6 已复现)。加载前先扫描,使用CACHE_MODE_IGNORE加载,进行版本管理和迁移,并验证数值,而不是加密(01:01:37)。
NavigationAgent3D 约定(P15,实测)
文档警告(NavigationAgents 页面)和 Bramwell(2W4JP48oZ8U 00:14:14),除非另有标注,均在 4.7.2 中实测:
- 避障需要
target_position。 开启避障但没有目标时,safe_velocity连续 30 个 tick 都保持为 0;有目标时则返回完整的 3 m/s。 - 避障不会影响寻路。 Agent 是圆形,不了解导航网格或物理信息,因此墙壁仍需由路径处理(文档)。
- 每个物理帧调用一次
get_next_path_position(),到达终点后不再调用。is_navigation_finished()为 true 后,它仍会返回最后一个点(这里相距 0.93 m):此时应停止转向,否则 Agent 会缓慢爬行。 path_desired_distance必须大于一个 tick 的移动距离,以及导航网格高出地面的浮点误差(约 0.3 m)。 以 25 m/s(每个 tick 移动 0.42 m)移动并将其设为 0.1 时,Agent 在 1800 个 tick 中有 1782 个 tick 向后移动,始终未能到达;设为 1.0 时则在 182 个 tick 后到达。设为 0.1 且脚部测得与地面齐平时,Agent 始终无法离开第一个路径点。- 默认值:
time_horizon_agents为 1.0,time_horizon_obstacles为 0.0。如果 Agent 过早减速,请缩短时间范围。max_neighbors太小会导致避障被忽略(文档;此处未实测)。
首先确定
| 输入 | 对计划的影响 | 默认值 |
|---|---|---|
| 屏幕上的 Agent 数量 | 使用节点还是服务器 Agent、思考预算 | 200 |
| 游戏玩法的帧预算 | 每项基准测试的门槛 | 60 fps 时为 16.67 ms 的四分之一(4.2 ms)[已添加] |
| 2D 还是 3D | NavigationAgent2D 还是 3D、Area2D 还是 3D | 3D |
| 谁负责编写行为 | 代码(FSM、枚举)还是设计师(LimboAI) | 代码 |
| 存档信任度 | 仅自己的文件还是共享或经过模组修改的存档 | 共享:扫描或 JSON |
| 平台 | 输入设备、CPU 预算 | 桌面端、键盘和鼠标及手柄 |
工作流
- 设置项目。
- 使用
gd_env.base_project("3d", P)克隆 Base3D,并为其设置独立的application/config/name, 否则克隆项目会共享user://。运行gg.install(P)。 - GATE:project.godot 中有
3d/physics_engine="Jolt Physics"和明确指定的渲染器;gd_run.check_all(P)报告 0 个解析错误。
- 使用
- 关卡导航。
- 将静态几何体放入组
nav_source。使用make_navmesh(parsed colliders, source group)构建网格,然后执行bake,再执行wait_applied。 - GATE:样本点之间的
map_get_path结果非空,且穿过的障碍物数量为 0;多边形 数量大于 0;NPC 出生点处没有空洞(P1)。
- 将静态几何体放入组
- 移动与人群。
- 启用避障;使用
velocity_computed驱动move_and_slide;限制重新寻路的频率;仅沿 XZ 方向转向。 - 在 50、200 和 800 个对象时运行
gg.crowd_bench(P, n),然后运行gg.scaling_verdict。在你自己的基准测试场景中,将Performance.add_custom_monitor(&"ai/thinks", callable)和OBJECT_ORPHAN_NODE_COUNT与运行时间一并记录(创建 5 个多余的Node.new()后读数为 5,P15)。 - GATE:重叠对象对数量为 0;指数低于 1.3;目标数量下的中位数符合游戏预算(
gg.budget_share);孤立节点数量保持不变。然后打开人群的窗口化捕获结果。
- 启用避障;使用
- AI。
- 使用带滞回的
decide(state, percept)编写大脑。感知先保持轻量 (距离、视锥、一次射线检测、过期命中检查)。管理器以约 5 Hz 的频率思考,并限制每个周期的处理数量。远处的敌人降低思考频率:根据距离平方划分层级,因为VisibleOnScreenNotifier3D.is_on_screen()在无头模式下始终返回 false,基准测试会把所有对象都划入远处层级(P15)。节点 FSM 用于英雄;如果由设计师编写,则使用 LimboAI。 - GATE:GUT 状态转换表全部通过;英雄场景历史符合预期顺序(巡逻、追击、攻击、追击、搜索、巡逻);已测量思考成本的 A/B 对比(P3、 P9、P13)。
- 使用带滞回的
- 战斗。
- 为层命名(World 1、Player 2、Enemy 3、PlayerHurtbox 5、EnemyHurtbox 6、Projectile 7)。
Hitbox 和 hurtbox 使用工具包中的内容。子弹使用
bullet_pool;反弹物体使用带 CCD 的 RigidBody3D。 - GATE:共享日志记录的一次挥击命中 1 次;同阵营命中 0 次;最高速度下的穿透测试结果为 8 次中 0 次;子弹位于独立层(P4、P5)。
- 为层命名(World 1、Player 2、Enemy 3、PlayerHurtbox 5、EnemyHurtbox 6、Projectile 7)。
Hitbox 和 hurtbox 使用工具包中的内容。子弹使用
- 持久化。
- 使用带 VERSION 和
migrate()的类型化保存 Resource、save_io.save_resource_atomic、load_resource_checked;每个持有者都使用独立副本的库存。 - GATE:往返结果相等;所有旧版样本文件都能加载;恶意样本文件会被拒绝;缓存测试通过(P6、P7)。
- 使用带 VERSION 和
- 对话与任务。
- 使用 Dialogue Manager 4.x 的
.dialogue文件;任务顺序为交付,然后继续,最后提供。 - GATE:导入时编译所有文件,且错误数为 0;
gg.check_dialogue_text未发现缺失提示;无头模式下的遍历能到达每个分支并改变状态(P8)。
- 使用 Dialogue Manager 4.x 的
- 输入。
- 使用物理按键操作;在
_unhandled_input中处理游戏输入;将按键重新映射保存到 ConfigFile。 - GATE:合成按键和 just-pressed 测试通过;STOP 面板会接收并处理推送的点击;重新加载后按键映射仍然保留(P10)。
- 使用物理按键操作;在
- 程序化内容。
- 先生成带种子的数据(
dungeon_gen),再运行验证器,然后生成几何体、烘焙并寻路。 - GATE:1000 个种子均无验证失败;相同种子生成相同哈希;起点到 Boss 存在路径;已打开顶部捕获结果(P11)。
- 先生成带种子的数据(
- 收尾。
- 运行
test_gameplay_live.py并复制证据。根据references/critique.md给自己打分。
- 运行
数值
| 数值 | 测量结果 | 对比条件 |
|---|---|---|
| 200 个带避障的实体 | 每帧 1.98 ms,0 次重叠 | M5 Max,固定帧率 60,60 m 竞技场,14 个障碍物 |
| 避障时的扩展情况 | 50 个 Agent:1.07 ms,200 个:2.00,800 个:7.31(每个 Agent 约 9 µs) | 相同 |
| 200 个服务端 Agent 加 MultiMesh | 0.26 ms | 相同 |
| 思考开销,200 个敌人 | 预算 0.40 ms,每个 tick 0.78,不思考时 0.33 | 5 Hz,每个 tick 40 个 |
| 首次 navmesh 同步 | 3 个物理帧;rebake 在 2 到 16 后应用 | 开启异步迭代(默认) |
| 烘焙 | 5 ms,180 个多边形 | 60 m 竞技场,单元格 0.25 |
| 1000 颗子弹 | 数据 0.35 ms;带 CCD 的 RigidBody3D 为 1.05;不带 CCD 为 1.00(45% 未命中墙壁) | 60 m/s,存活时间 2 s |
| Jolt 速度上限 | 500 m/s | physics/jolt_physics_3d/limits/max_linear_velocity |
| 地牢生成并验证 | 中位数 88 µs,1000 个中 1000 个有效 | 主路径 14 个房间,4 个分支 |
| NavigationAgent3D 默认值 | 半径 0.5,max_neighbors 10,neighbor_distance 50,max_speed 10,关闭避障 | 4.7.2 ClassDB |
质量门槛
- 可测量:群体指标(重叠对数、最小对间距、卡住、穿过地板);
scaling_verdict;budget_share;GUT(gd_run.run_tests)在一次有意失败后通过;check_all无解析错误;对 1000 个种子运行验证器;保存测试样例;编译对话。 - 视觉:在 SubViewport 中进行窗口化截图(160x90 窗口),使用
gd_review.image_checks和 不超过 1600 px 的联系表,并打开查看。检查玩家周围的群体间距、障碍物内部没有 Agent,以及从地牢起点到首领的路径。
常见错误
| 错误 | 表现 | 修正 |
|---|---|---|
| 基准测试普通无头模式 | 无论负载如何,每“帧”约 7 ms | --fixed-fps 60 |
| 指定网格后立即查询路径 | 路径为空,Agent 在生成点待机 | wait_applied |
| 使用默认烘焙(从根节点选择 Both),且内部有 NPC | 生成点周围出现空洞 | 分组并使用静态碰撞体 |
| 为了“提速”关闭避让 | 3108 个重叠对,敌人彼此合并 | 开启避让:200 个对象时开销很小 |
| 每个敌人都用一个 Area3D 实现视野 | 宽相位处理负担加重 | 距离、锥形范围、单条射线 [新增] |
| 信任对传送后的物体进行射线检测的结果 | 敌人“看穿”墙壁 | 检查陈旧命中结果的距离 |
| 命中区域的层和遮罩都设为 1 | 命中自身,造成三倍伤害 | 只设置遮罩或层;使用 HitLog |
在 area_entered 中设置 monitoring = false | “在进出信号期间禁止调用函数” | 使用 set_deferred |
| 子弹刚体使用默认层 | 1000 个刚体发生碰撞:每帧 110 到 170 ms | 使用独立层,遮罩设为 world 和目标 |
| 高速刚体未启用 CCD | 子弹以 30 m/s 的速度穿过墙壁 | 使用 continuous_cd 或射线扫掠数据 |
加载下载的 .tres | 其中的脚本会运行 | 使用 load_resource_checked 或配合 from_native 使用 JSON |
重新保存后仍使用默认 load() | 旧值再次出现 | CACHE_MODE_IGNORE |
对 Array[String] 执行 save.items = [...] | 运行时错误,或通过 set() 静默无效 | 使用 items.assign([...]) |
每个箱子都预加载库存 .tres | 所有箱子都显示同一个箱子的战利品 | 为每个持有者调用 duplicate(true) |
| Selector 中第一个子项总是成功 | 攻击分支永远不会运行(Chap.C E_FIy2dTkNc 00:17:27) | 设置首项门控,使用 BTProbability 或 AlwaysFail |
| 动作中使用逻辑键码 | 使用 AZERTY 键盘时,WASD 操作失效 | 使用物理键码 |
| 开启物理插值时进行传送 | 刚体会在跳跃过程中滑动 [新增,未测量] | 移动后调用 reset_physics_interpolation()(该方法存在) |
对自动加载单例调用 queue_free() | 在 4.7.2 中不会崩溃;之后每次访问都会因读取 0 而报“先前已释放”错误(文档称会崩溃) | 永远不要释放它;改为重置其状态 |
交接
- **接收:*项目布局、自动加载策略和保存模型(scenario-godot-architecture);带有静态碰撞体的关卡(位于
nav_source)和控制器(scenario-godot-3d-world);TileMapLayer 导航(scenario-godot-2d);来自 scenario- 技能的美术资源,通过上述两个技能交付。 - **交付:**将
state_changed交给 scenario-godot-animation;将播放事件和优先级交给 scenario-godot-audio(混音、总线、衰减和监听器由该技能负责);将物品栏信号、对话气泡和按键重映射菜单交给 scenario-godot-ui;将命中位置和法线交给 scenario-godot-vfx;将资源和纯decide()交给 scenario-godot-multiplayer;将基准测试 JSON 交给 scenario-godot-performance-export。
Godot 4.7 注意事项
- 导航类在 4.7.2 中属于实验性功能。区域和地图迭代默认异步执行,因此同步需要耗费数帧。
region_bake_navigation_mesh已弃用:请改用parse_source_geometry_data加bake_from_source_geometry_data(或其异步版本)。region_get_iteration_id已存在。query_path(params, result, callback)会在返回前执行回调(P15):它不会将工作移出当前帧。 - MultiMesh 会将所有实例作为一个包围盒进行剔除:设置
custom_aabb(已存在),并按区域拆分大型人群。 - NavigationMesh 的默认
geometry_parsed_geometry_type为 Both(2)。默认单元格大小和高度为 0.25。 - 从 4.6 开始,新项目默认使用 Jolt 作为 3D 物理引擎,但由 Agent 编写的 project.godot 会使用 GodotPhysics3D,除非明确设置 Jolt(负责人的差异)。
duplicate(true)只会复制文件内部的子资源(4.5 及更高版本)。duplicate_deep(DEEP_DUPLICATE_ALL)也会复制外部资源。JSON.from_native和to_native已存在;普通 JSON 数字会以浮点数形式返回。InputEventKey.as_text()会打印 "W - Physical"。新建的 InputEventKey 的设备 ID 为 16 (DEVICE_ID_KEYBOARD)。- 插件:Dialogue Manager 的版本为 4.1.0,其中标题是提示语;DM3 视频内容落后一个版本。 LimboAI 1.8.1 附带一个标为 "4.6" 的 GDExtension 构建版本,可在 4.7.2 上运行。
参考资料
references/procedures.md(P0 至 P15,含代码和实时结果),expert-notes.md(专家判断及时间戳),critique.md(评估标准),gui-paths.md(编辑器菜单),sources.md(视频、文档、插件哈希值)。scripts/gd_gameplay.py:安装、run_job、crowd_bench、scaling_verdict、budget_share、对话 lint、存档扫描。scripts/agentkit/gameplay/:nav_tools、arena、crowd_bench、 enemy_body、状态机及状态、brain、perception、enemy_manager、hitbox、hurtbox、hit_log、 combat_layers、bullet_pool、save_io、inventory、quest_rules、dungeon_gen、voice_pool。