万象 · Godot 架构判据
一句话:Godot 里绝大多数「这样写对不对」的争论,官方最佳实践章节已经给了判据 ——
本技能把它们变成可当场调用的决策规则,每条都能指回原文。
来源:官方中文文档 docs.godotengine.org/zh-cn/4.x/tutorials/best_practices/
(9 篇,43485 字)。逐字引用 + 五重验证,可追溯率 100%。
取源日期 2026-09-22,源 sha1 8ec13cf64991。
一、七条判据速查
| 编号 | 判据 | 什么时候用 |
|---|---|---|
| G1 | 场景就是类,不是「预制体」 | 决定某功能做成场景还是脚本 |
| G2 | 自动加载把「一处出错」放大成「全项目排查」 | 想写 XxxManager 时 |
| G3 | 自动加载不是单例 | 依赖「全局只有一份」时 |
| G4 | preload 不是「更快的 load」,它会提前占内存 | 脚本顶部无脑 preload 时 |
| G5 | Resource = 轻量 + 能在检查器编辑 | 想用一堆子节点存数据时 |
| G6 | .gdignore 会让里面资源彻底 load() 不到 |
想用 .gdignore 整理项目时 |
| G7 | 命名规范的理由是导出平台差异,不是审美 | 要定/改项目命名规范时 |
二、逐条展开
G1 · 场景就是类
> R(原文):场景的行为与类有很多相似之处,所以把场景看成一个类也是合理的。场景是可复用、可实例化、可继承的节点组。
- I:在 Godot 里,场景不是「预制体」,而是一个类。
- A1:官方紧接着说,因为「场景的实例都是对象」,所以「许多适用于书面代码的面向对象原则也适用于场景:单一职责、封装等」。
- A2:决定某功能该做成场景还是脚本时;想把一个场景复用到别处却觉得别扭时。
- E:① 把场景当类看,问它职责是否单一 → ② 内部节点当私有成员,别让外部直接抓 → ③ 要变体就继承场景,而不是在同一个场景里加开关。
- B:场景是类,但脚本严格说不是类 —— 官方原文「这些脚本严格来说并不是类,而是一种资源」。
所以「场景=类」是个有用的心智模型,不是语言事实。
V 分 12/15(独特性 3 · 可操作 3)
G2 · 自动加载的真实代价
> R(原文):全局访问的问题在于,任何地方的代码都可能将错误的数据传递给我们例子中的 Sound 自动加载。因此,为了修复这个 bug,你需要检索的区域涵盖了整个项目。
- I:全局单例的代价不是性能,是定位成本。
- A1:官方举的音效管理器例子:为了播多个音效而写全局
Sound类,结果带来全局状态、全局访问、全局资源分配三个问题。 - A2:想写
XxxManager/XxxSingleton的时候。 - E:① 先问「这功能能不能待在某个场景里」→ ② 函数用
class_name定义新 Node 类型 → ③ 共享数据用Resource→ ④ 只有「必须跨场景存活」的才上自动加载。 - B:官方不是反对自动加载 —— 它明说自动加载是「使节点作为场景树的根的子节点自动加载的工具」。
判据是是否必须跨场景存活,不是「全局方便」。
V 分 13/15(独特性 3 · 可操作 3)
G3 · 自动加载不是单例
> R(原文):自动加载不完全是一个单例。没有什么可以阻止你实例化自动加载的节点的副本。
- I:引擎不阻止你把它实例化出第二份 —— 依赖「只有一份」的代码会出错。
- A1:官方同段补一句:它「只是一个使节点作为场景树的根的子节点自动加载的工具」。
- A2:把自动加载当单例用、并依赖「全局只有一份」这个假设时。
- E:① 别依赖「只有一份」;② 需要单例语义就自己加保护;③ 不要在自动加载里写「假设自己是唯一实例」的初始化逻辑。
- B:这条只在你把自动加载当单例用时才咬人;只是借它存个持久数据时不构成问题。
V 分 10/15(独特性 3 · 可证伪 3)
G4 · preload 的隐藏代价
> R(原文):如果无法确定何时可以加载脚本, 那么预加载资源(尤其是场景或脚本)则有可能会导致非预料的多余加载。
- I:
preload不是「更快的 load」,它会提前把资源拉进内存。 - A1:官方接着说,这可能「导在原始脚本加载时间之上多出意(想之外的开销)」。
- A2:在脚本顶部无脑
preload一堆场景/脚本时。 - E:① 能确定何时加载 →
preload(提前、避免运行期卡顿);② 不确定 →load(用到才拉);③ 大资源优先load+ 缓存。 - B:
preload的收益是「避免运行期卡顿」,代价是「可能白加载」。
两者谁大取决于资源大小与是否真的用到 —— 没有普适答案。
V 分 13/15(独特性 3 · 可操作 3 · 可证伪 3)
G5 · Resource 的定位
> R(原文):虽然几乎和 Object/Reference 一样轻量,它们仍然可以在检查器中显示并导出属性。这使它们在易用性上,可以媲美使用子节点的方式,而且如果有人计划在其场景中包含许多这类 Resource/Node,它们还可以提高性能。
- I:
Resource是「轻量的数据 + 能在检查器里编辑」的交集。 - A1:官方同段给的定位:
Object是终极轻量对象但要手动内存管理;RefCounted自动计数;Resource比RefCounted多一个「天然可序列化」。 - A2:想用「一堆子节点」来存数据/配置的时候。
- E:① 纯数据 →
Resource;② 只在运行期用的临时对象 →RefCounted;③ 需要行为 → 才用Node。 - B:
Object的引用「可能在没有警告的情况下失效」—— 官方明确警告。
要长时间持有就用RefCounted/Resource,别裸用Object。
V 分 11/15
G6 · .gdignore 的副作用(陷阱)
> R(原文):一旦文件夹被忽略,其中资源就不能再使用 load() 和 preload() 方法加载。被忽略文件夹会从文件系统栏目中隐藏,从而减少混乱。
- I:
.gdignore不只是「不导入」—— 它会让里面的资源彻底加载不到。 - A1:官方用法是「为防止 Godot 导入特定文件夹中的文件,请在文件夹中创建一个名为 .gdignore 的空文件」。文档里
/docs/就是这么标的。 - A2:想用
.gdignore把某个文件夹藏起来整理项目时。 - E:① 文档/设计稿这类引擎用不到的 →
.gdignore;② 凡是运行期要load()的 → 绝不能放进去;③ Windows 上建点开头文件要在文件名前后都写点(.gdignore.)再确认。 - B:把资源放错进被忽略的目录,表现是「运行时加载失败,而文件明明在那儿」—— 很难查。
V 分 13/15(独特性 3 · 可操作 3 · 可证伪 3)
G7 · 命名规范的理由
> R(原文):使用 snake_case 风格为文件夹和文件命名(除了c#脚本). 这避免了在 Windows 上导出项目时可能出现的大小写敏感问题.
- I:规范要连理由一起记 —— 不然换个环境就会「好心地把它改掉」。
- A1:官方的理由是导出平台差异,并明确 C# 脚本例外(用 PascalCase 类名)。
- A2:要定项目命名规范、或看到别人规范与自己不同想统一时。
- E:① 文件夹/文件名 →
snake_case;② 节点名 →PascalCase(与内置节点一致);③ C# 脚本 → 按类名PascalCase(明确例外);④ 第三方资源 → 统一放顶级addons/。 - B:「不一致的规范」比「不同的规范」危害大 —— 只要全项目一致、并知道理由,就不必强行改成主流写法。
V 分 13/15(跨域迁移 3 · 可操作 3)
三、怎么用(三条动作)
- 新建 Godot 工程结构时:拿 G1 切场景、G5 定数据放哪、G7 定命名。
- 想写全局单例时:先过 G2 的三问,再看 G3 有没有依赖「只有一份」。
- 审查已有工程:按 G4(preload 滥用)· G6(.gdignore 误伤)· G7(规范一致性)三项扫一遍。
四、边界(本技能不覆盖什么)
- 不覆盖 API 用法:具体类/方法怎么调,查官方 API 参考。
- 不覆盖性能调优:那是 profiler 的活,本技能只给结构判据。
- 不覆盖版本迁移:Godot 3→4 的 API 对照不在此列。
- 原文是
latest(不稳定)分支的文档:官方页面自带
「Attention: Here be dragons / This is the latest (unstable) version」提示。
⇒ 判据在 4.x 各小版本间稳定,但具体 API 签名要以你的实际版本为准。
五、引用可追溯
- 源:
docs.godotengine.org/zh-cn/4.x/tutorials/best_practices/共 9 篇 - 落盘基准:
sources/godot-best-practices/text.md(43485 字,sha18ec13cf64991) - 五重验证:7 个单元全部 accepted,可追溯率 100%,0 rejected
- 核验命令(可重跑):
python <技能库>/omni-knowledge-distill/scripts/od_distill.py verify \ --sources sources --work work
常见误用与常见问题(FAQ)
常见误用(反模式)
| 误用 | 后果 | 正解 |
|---|---|---|
| 把「自动加载」当成单例 | 自动加载不是单例(G3) | 需要单例语义就自己加保护 |
| 在自动加载里写「假设自己是唯一实例」的初始化逻辑 | 多实例时静默出错 | ① 别依赖「只有一份」;② 需要单例语义就自己加保护;③ 不要在自动加载里写「假设自己是唯一实例」的初始化逻辑 |
| 未评估代价就用自动加载 | 有真实代价(G2) | 先看「G2 · 自动加载的真实代价」再决定 |
未评估代价就到处 preload |
有隐藏代价(G4) | 见「G4」 |
| 用场景承载「不是类」的东西 | 违反「场景就是类」(G1) | 按 G1 判据组织;数据该放 Resource 还是节点见 G5 |
忽略 .gdignore 的副作用 |
陷阱(G6) | 见「G6 · .gdignore 的副作用(陷阱)」 |
| 随手命名 | 命名规范有理由(G7) | 按「G7 · 命名规范的理由」 |
常见问题
- Q:本技能不覆盖什么? A:见「四、边界(本技能不覆盖什么)」。
- Q:这些判据有出处吗? A:有,见「五、引用可追溯」—— 七条判据逐字可追溯官方最佳实践。
- Q:怎么用最快? A:见「一、七条判据速查」;需要理由时再看「二、逐条展开」。
附:实测补充(已外移)
§六 及之后的实测补充、专项技巧与诊断纪律,已外移至 references/实测补充-Godot4.md(内容未改动)。