杂记随笔
← 返回全部杂记
随笔标识: godot-project-structure-by-feature
[ SYS.NOTES // ESSAY-GODOT-PROJECT-STRUCTURE-BY-FEATURE ]
杂谈随笔 · · 9 分钟阅读

从「Code Code」说起:为什么你的 Godot 工程不该按文件类型分类

从 Reddit 上热议的空目录结构,聊聊为什么按文件类型分类是反模式,以及我们在《发条乐园》中验证过的目录组织方式。

今天在 Reddit 的 r/godot 看到一个帖子。有人发帖问:“组织 Godot 项目的最佳方式是什么?”

顺便附上了他建好的工程目录截图:

Reddit 上的 Godot 目录结构讨论截图

结构很有喜感:

  • Code 里面分了 Code Code 和 Shader Code
  • Resources 里放 Actual Resources
  • Images 只有一个默认的 icon.svg
  • Scenes 拆成了 Object Scenes 和 Scene Scenes

评论区有人问是不是在整活,因为全都是空的。楼主回复挺实诚:“也不全是因为空的,我只是习惯很早就把空间规划好。”

还有人说:“最管用的办法是给文件夹标不同颜色。”

我也在底下回了条评论。这个帖子之所以有共鸣,是因为许多人刚做项目时都干过这事:代码还没写几行,先把文件夹建得整整齐齐,但过两周就全乱套了。


为什么总忍不住先建文件夹

点开新建项目,玩法没定,手感没调,代码窗口一片空白。

这时候写逻辑费脑子,建文件夹却不费劲。按几次回车,建好代码、场景、音效和模型目录,看起来就像架构已经搭好了。

但这种靠脑补建出来的分类,往往撑不过三天。

第一天你根本不知道背包系统最后会写成一个场景、一个自定义 Resource,还是挂在主角身上的组件。提早建好的空目录,最后多半会变成乱放东西的垃圾桶。


按文件类型分有什么问题

很多人习惯按文件后缀建目录:

res://
├── scripts/
│   ├── player.gd
│   ├── enemy.gd
│   └── chest.gd
├── scenes/
│   ├── player.tscn
│   ├── enemy.tscn
│   └── chest.tscn
├── materials/
│   ├── player.tres
│   └── chest.tres
└── audio/
    ├── player_jump.wav
    └── chest_open.wav

这样分看着工整,实际写起来很别扭。

找文件全靠上下翻

比如调角色跳跃手感:改代码要开 scripts/player.gd,改节点要开 scenes/player.tscn,换材质要去 materials/,换音效又要去 audio/。

改一个功能,左侧文件树就得一直展开、折叠、上下滚动,手和眼睛都很累。

违背 Godot 的场景哲学

Godot 的核心是 Everything is a Scene。一个 .tscn 场景配上根节点的 .gd 脚本,本身就是一个自包含的类。

主角、宝箱、弹出的提示框,都是独立的实体。把脚本单独扔到几层外的 scripts/,等于把本来捆绑在一起的东西强行拆开。

删东西容易漏

做游戏经常砍需求。如果某个陷阱不要了,按类型分类的项目里,你得去 scenes/ 删场景,去 scripts/ 删脚本,去 materials/ 找材质,再去 audio/ 删音效。

漏掉一个就成了死文件,几个月后没人敢动,白白占包体体积。


实战解法:《发条乐园》的目录是怎么定的

我们在做《发条乐园》(Windup Wonderland)时,工程结构是这么定的。

先看仓库根目录:

D:/WindupWonderland/
├── art_source/   # Blender、PSD 等源文件,不进 Godot
├── docs/         # 设计和技术文档
└── src/          # Godot 工程根目录(res://)

Blender 和 PSD 坚决放在工程外面,进引擎的只有导出的 .glb 和 .png。这样 Godot 切窗口不卡,Git 也不容易膨胀。

再看 Godot 里面的 src/(即 res://),只有四个顶层目录:

res://
├── addons/   # 编辑器插件
├── systems/  # 运行时纯代码系统
├── scenes/   # 场景与同目录脚本
└── assets/   # 导入的美术与音频资源

具体落地的几条规则:

脚本跟着场景走

没有全局 scripts/ 目录。

看我们的 res://scenes/player/:

res://scenes/player/
├── puppet.tscn              # 角色根场景
├── puppet.gd                # 角色主入口
├── puppet_input.gd          # 输入缓冲
├── puppet_grab.gd           # 抓取逻辑
├── puppet_fling.gd          # 蓄力弹射
├── puppet_mount.gd          # 骑乘交互
├── puppet_slap.gd           # 拍打机制
├── puppet_wind.gd           # 发条动力衰减
├── puppet_visual.gd         # 视觉抖动与动画驱动
├── arm_reach_modifier.gd    # 抓取范围修正
├── fling_preview.gd         # 弹射抛物线预览
├── player_camera.tscn       # 相机场景
└── player_camera.gd         # 跟随控制

调角色就在这个文件夹里,两边点开就能改。

道具也是一样,scenes/props/ 里:

res://scenes/props/
├── floor_button.tscn + floor_button.gd     # 地面踏板
├── seesaw.tscn + seesaw.gd                 # 跷跷板
├── snap_trap.tscn + snap_trap.gd           # 捕兽夹
├── spring_pad.tscn + spring_pad.gd         # 弹簧踏垫
├── wall_button.tscn + wall_button.gd       # 墙壁按钮
├── floating_crate.tscn                     # 浮水箱
└── prop_crate_heavy.tscn                   # 重箱

每个道具都是独立的业务单元。要复制或者要删,只动这一个地方,不会漏文件。


Systems 只放纯代码,不建 autoload/ 文件夹

不需要画面的系统放 res://systems/:

res://systems/
├── input/
│   ├── player_device.gd       # 手柄与键鼠槽位分配
│   └── input_glyphs.gd        # 按键图标映射
├── net/
│   ├── net_session.gd         # 联机管理 (Autoload 单例)
│   └── steam_lobby.gd         # Steam 大厅
└── water/
    ├── water_query_service.gd # 水体查询服务
    ├── water_river_runtime.gd # 河流解算
    ├── water_buoyancy_body.gd # 浮力物理
    ├── water_sample.gd        # 采样数据结构
    └── shaders/               # 水体 Shader

很多人喜欢建一个 autoload/ 文件夹把所有单例塞进去,最后变成大杂烩。

Autoload 只是项目设置里的一个配置项。我们的联机单例写在 systems/net/net_session.gd,在 project.godot 里直接把路径指向它:

[autoload]

NetSession="*res://systems/net/net_session.gd"

属于哪个系统的单例就放在哪个系统里,不要集中收容。


复杂系统:三层垂直拆分

像自研水体这种大系统,一个目录装不下,就按三个层次拆:

复杂系统 = addons/<x>/(编辑器工具)+ systems/<x>/(运行时代码)+ assets/<x>/(数据与资产)

来看水体系统在工程里的对应关系:

  1. 编辑器侧(EditorPlugin) → res://addons/ww_water/
    • 曲线工具、Gizmo 和烘焙面板。只在编辑器里跑,导出时不进运行时。
  2. 运行时逻辑(Systems) → res://systems/water/
    • 浮力解算 water_buoyancy_body.gd、水流查询服务 water_query_service.gd 和渲染用 Shader。纯代码,负责实时计算。
  3. 数据与资产(Assets) → res://assets/water/
    • 分两层:authored/ 是手工拉的曲线场景;baked/ 是烘焙出来的网格、流向图和元数据。

都叫 water,分工很明确:工具在 addons,计算在 systems,数据在 assets。


有文件才建文件夹

我们的规则很简单:第一个文件落地才建目录,不建空目录占位。

就算计划好以后要做存档(systems/save/)和体积云(systems/sky/),没写代码前绝不提前建文件夹。

东西少时直接放根层,感觉找东西费劲了再分目录;分目录时按功能分,不按文件类型分。


结语

刚开新坑时想把目录建漂亮,是人之常情。

但目录是放工具的架子,不是给人参观的展柜。常用的扳手和螺栓就该放在一块,废弃的零件能一把抓起来扔掉。

不用急着搭框架。什么时候觉得挤了,再顺着业务把它拆开。

所有人都明白“过早优化是万恶之源”。而在游戏开发中,我们可以补充下半句:“过早分类是混乱之母”。

技术讨论与评论

0 条讨论
登录后即可发表评论、探讨架构与技术细节
正在拉取讨论记录...