今天在 Reddit 的 r/godot 看到一个帖子。有人发帖问:“组织 Godot 项目的最佳方式是什么?”
顺便附上了他建好的工程目录截图:

结构很有喜感:
Code里面分了Code Code和Shader CodeResources里放Actual ResourcesImages只有一个默认的icon.svgScenes拆成了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>/(数据与资产)
来看水体系统在工程里的对应关系:
- 编辑器侧(EditorPlugin) →
res://addons/ww_water/- 曲线工具、Gizmo 和烘焙面板。只在编辑器里跑,导出时不进运行时。
- 运行时逻辑(Systems) →
res://systems/water/- 浮力解算
water_buoyancy_body.gd、水流查询服务water_query_service.gd和渲染用 Shader。纯代码,负责实时计算。
- 浮力解算
- 数据与资产(Assets) →
res://assets/water/- 分两层:
authored/是手工拉的曲线场景;baked/是烘焙出来的网格、流向图和元数据。
- 分两层:
都叫 water,分工很明确:工具在 addons,计算在 systems,数据在 assets。
有文件才建文件夹
我们的规则很简单:第一个文件落地才建目录,不建空目录占位。
就算计划好以后要做存档(systems/save/)和体积云(systems/sky/),没写代码前绝不提前建文件夹。
东西少时直接放根层,感觉找东西费劲了再分目录;分目录时按功能分,不按文件类型分。
结语
刚开新坑时想把目录建漂亮,是人之常情。
但目录是放工具的架子,不是给人参观的展柜。常用的扳手和螺栓就该放在一块,废弃的零件能一把抓起来扔掉。
不用急着搭框架。什么时候觉得挤了,再顺着业务把它拆开。
所有人都明白“过早优化是万恶之源”。而在游戏开发中,我们可以补充下半句:“过早分类是混乱之母”。
技术讨论与评论