mcpField
G
godot-programming-patterns

godot-programming-patterns

github.com/pbzona开发252 次安装15 周前更新
代码仓库

关于 godot-programming-patterns

Godot Programming Patterns 是面向 Godot 项目开发者的技能,用于应对明确的设计或性能压力。它帮助选择并验证组件、信号、命令和对象池,避免默认引入额外抽象。

主要功能

  1. 01评估 SOLID 原则与耦合
  2. 02设计类型化信号及连接
  3. 03用命令封装可撤销或排队操作
  4. 04按明确生命周期构建对象池
  5. 05验证行为、生命周期与性能

适用场景

  1. 01拆分耦合紧密脚本中的职责
  2. 02向多个监听者通知已完成事件
  3. 03为特定操作添加撤销或重放
  4. 04降低重复实例化带来的实测开销

安装

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

$npx skills add https://github.com/pbzona/godot-skills --skill godot-programming-patterns

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

包含的文件

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

references/
object-pooling.md
signals-and-commands.md
solid-and-coupling.md

详细介绍

Godot 编程模式

仅在存在明确的设计或性能压力时应用模式。直接调用类型化方法往往是最佳设计。

适用范围

在以下情况下使用此技能:

  • 将 SOLID 用作诊断工具,检查 Godot 脚本和场景组合。
  • 将行为拆分为专注的组件,避免不必要的继承。
  • 设计、发出、连接和测试类型化信号。
  • 将可撤销、可排队、可重放或可重新映射的操作封装为命令。
  • 实现具有明确获取、激活、释放和重置契约的对象池。

不要使用此技能来学习节点、场景、资源、输入映射或编辑器基础知识;请改用基础知识技能。不要用它来选择权威游戏状态、自动加载管理器、场景切换或持久化格式;请改用游戏架构技能。本地模式可以为这些系统提供支持,但此技能不决定它们的归属。

重构前先检查

  1. 找到 project.godot,并阅读 config/features,以确定确切的 Godot 4.x 基线。
  2. 检查相邻脚本和场景。遵循其静态类型、class_name、命名、文件夹、组件、连接和测试惯例。
  3. 复现当前行为并记录基线。对于对象池,应收集分析器或监视器证据,而不是假设分配是瓶颈。
  4. 梳理当前协作者:调用方、被调用方、信号、组、节点路径、导出引用和运行时创建的节点。
  5. 标明生命周期和所有权。信号连接、命令目标或池化对象不得比其所有者存活更久。
  6. 添加新的抽象之前,先搜索是否已有组件、事件、命令基类、历史服务或对象池。
  7. 确定确切的压力来源:多种变更原因、一对多通知、操作历史或经测量确认的频繁创建销毁。

除非请求本身包含更改契约,否则应保留公开行为和编辑器连线。

选择最精简的模式

  • 对于一个已知的协作者,如果调用方需要返回结果或控制顺序,请使用直接方法调用。
  • 对于已经发生且可能有零个或多个本地监听者感兴趣的事实,请使用信号。
  • 当行为具有自己的状态、生命周期或复用边界时,请使用专注的组件。
  • 当操作需要独立于执行过程进行存储、撤销、排队、重放、记录或生成时,请使用命令。
  • 仅当反复实例化和销毁经测量确认成本高昂,且对象可以可靠重置时,才使用对象池。
  • 仅当每个子类型都遵守基类契约时,才使用继承。对于可选能力,优先采用场景组合。

默认情况下不要组合多种模式。例如,一次性的按钮操作不需要命令对象、事件总线和对象池。

实施工作流

  1. 写下现有的可观察契约,或添加有针对性的回归测试。
  2. 引入一个接缝:类型化方法、组件、信号、命令或对象池边界。
  3. 迁移一个生产者和一个消费者,同时确保项目仍可运行。
  4. 通过导出引用、设置参数或所属场景,明确所有权。
  5. 只有在所有调用方迁移完成后,才移除过时路径。
  6. 测试生命周期边界:场景退出、目标删除、重复连接、删除后撤销以及对象池耗尽。
  7. 将行为和性能与基线进行比较。

模式选择

场景式代码中的 SOLID
  • 让脚本的变更原因保持一致,而不是任意限制行数。
  • 当输入意图、模拟和呈现独立演进时,将它们分开。
  • 通过职责明确的组件或稳定契约来扩展,而不是不断扩大类型分支。
  • 子类型的前置条件不能比基类契约更严格,结果也不能比基类契约更弱。
  • 仅依赖协作者所需的最小 API。
  • 将依赖传入;不要让可复用节点全局查找具体实现。

不要把每个三行方法都提取到新节点中。只有在拆分能明确所有权、变更压力、测试或复用时,才有意义。

信号

将信号命名为已完成的事实,例如 value_changed 或 request_finished,并为其负载指定类型。发射方负责信号的含义;监听方负责作出响应。

class_name HeatGauge
extends Node

signal value_changed(value: float)

@export_range(0.1, 100.0, 0.1) var maximum: float = 10.0
var _value: float = 0.0

func value() -> float:
	return _value

func set_value(next_value: float) -> void:
	var clamped := clampf(next_value, 0.0, maximum)
	if is_equal_approx(clamped, _value):
		return
	_value = clamped
	value_changed.emit(_value)

信号不是存储状态。较晚加入的监听方应在初始化期间读取当前值,然后订阅后续变更。

命令

命令应包含执行所需的一切内容,以及承诺支持撤销时所需的精确先前状态。将输入收集放在命令之外。对于长时间保留在历史记录中的命令,不要存储实时节点引用,除非明确定义该节点释放时的处理方式。

将命令用于有明确含义的操作,而不是每一帧的连续移动。合并类似拖动的交互,或为其创建检查点,以便历史记录仍然实用。

对象池

编写容器前先定义生命周期:

  1. 获取一个未激活的实例。
  2. 使用所有必需的生成数据激活它。
  3. 使用期间避免每个周期都分配替代状态。
  4. 在任务结束时请求释放。
  5. 重置每个可变子系统。
  6. 恰好归还一次。

仅靠可见性不足以定义未激活状态。隐藏时,处理逻辑、监控、计时器、动画、粒子、音频和临时信号连接仍可能继续运行。

验证

运行项目既定的检查,然后直接验证所选模式:

  • SOLID/组件重构: 旧调用方仍能正常工作;提取出的单元可独立运行;未引入新的全局查找。
  • 信号: 一次变更只发射一次;值未变时不发射;负载正确;重新创建监听方不会导致重复响应。
  • 命令: 执行后再撤销可恢复精确的先前状态;重做可重新应用;新增命令会清空重做历史;空历史可安全处理;无效目标遵循明确定义的策略。
  • 对象池: 预热后达到预期大小;稳定状态下复用时不再发生分配;耗尽时遵循选定的扩容/丢弃/失败策略;释放操作可重复调用;复用的对象没有残留状态。
  • 性能: 在相同工作负载下对比性能分析器的采样结果,并报告帧耗时、分配或节点 churn 是否有所改善。
  • 生命周期: 反复切换或重新加载场景,检查孤立节点、过期回调和保留的引用。

故障模式

  • **类更多,耦合不变:**虽然重新命名了职责,但依赖仍然跨越所有边界。应重新设计契约,而不只是调整文件。
  • **把信号当作命令使用:**发射方期望恰好有一个监听器执行必要工作。应使用显式依赖和方法调用。
  • **遗漏启动时更新:**监听器在初始发射后才订阅。依赖后续信号之前,应先获取初始快照。
  • **重复回调:**初始化时重复连接,或长期存在的发射方意外保留了监听器。检查连接的所有权,并为有意的重新连接添加保护。
  • **撤销时状态偏移:**通过计算逆向操作来撤销,而不是恢复捕获的先前状态。
  • **命令目标已消失:**历史记录保留了超出生命周期的场景节点。应根据明确的策略拒绝、清理或解析稳定的目标。
  • **对象池导致重复归还:**碰撞和超时都释放了同一个对象。应明确记录活跃状态,并确保释放操作具备幂等性。
  • **隐藏的池化对象仍在运行:**未禁用碰撞、处理、计时器、动画或音频状态。
  • **对象池反而更慢:**工作负载并非受分配开销限制,池采用线性扫描,或重置开销高于实例化开销。应保留经过测量后更简单的设计。

参考资料

评论

支持 Markdown