项目资源到一万个文件以后,打开编辑器从三秒变成三分钟,团队里一定有人开始骂"Godot 工程越大越卡"。冤枉了一半:卡是真的,但不是遍历文件本身慢,而是导入慢。编辑器每次启动都要确认磁盘上的资源和 .godot/imported/ 里的导入产物是否还对得上,这个确认过程的成本结构决定了你能优化什么、优化不了什么。
启动时 EditorFileSystem 干了什么
编辑器启动后 EditorFileSystem(editor/filesystem/)负责建立资源视图,顺序是两段:
- 扫描:递归遍历
res://下所有文件和目录,确定每个文件的存在性、类型、修改时间; - 导入检查:对每个资源文件,判断它的导入产物(
.godot/imported/里的.ctex/.res等)是否仍然有效。
扫描产物写进 .godot/filesystem_cache(二进制)。下次启动时先读缓存,然后只对自己记录的每个文件做一次 stat(拿 mtime 和 size),和缓存比对:没变就跳过导入检查,变了才重新导入。这就是增量扫描。
成本模型很清楚。设工程有 $N$ 个资源文件,$t_{stat}$ 是一次 stat 的耗时(本地 SSD 上微秒级,网络盘是毫秒级),$t_{import}$ 是单个文件的导入耗时(贴图压缩是毫秒到百毫秒级,模型重一些):
$$ T_{cold} \approx N \cdot (t_{stat} + t_{import}), \qquad T_{warm} \approx N \cdot t_{stat} $$
冷启动(没有缓存或缓存整体失效)是左边,日常启动是右边。$N = 10^4$ 时, warm 启动哪怕走网络盘也就几十秒上限,而 cold 启动里 $N \cdot t_{import}$ 这一项可以把时间拉到分钟级——一万张贴图各压一次 ASTC,就是这个数量级。所以优化只有两个方向:避免 cold 启动,和压低 $t_{import}$。想优化 $N \cdot t_{stat}$ 没有意义,那是 $O(N)$ 里便宜的那半。
缓存什么时候整体失效
filesystem_cache 失效一次,全工程重新导入一次,这是大工程编辑体验的头号杀手。失效的触发条件值得背下来:
- 删了
.godot/目录:常见于"清理一下试试"、.gitignore配错导致缓存没进版本库、换分支时工作区被整目录替换; - 切换分支后 mtime 大面积变化:git checkout 会更新文件 mtime,哪怕内容没变,缓存按 mtime 比对也会判定全部过期——CI 产物解压、rsync 同步同理;
- 导入器版本升级:引擎小版本升级可能 bump 导入器版本号,产物后缀的 hash 变化,旧产物整体作废(这是正确行为,防止格式错配);
- 渲染平台切换:贴图压缩格式跟着目标平台变,产物不同。
第二个是最阴的:代码一行没改,就因为切了个分支,全工程重新导入。对策是把 .godot/(至少 filesystem_cache 和 uid_cache)提交进版本库,很多团队不知道这一点——官方 .gitignore 模板默认忽略整个 .godot/,但对大工程,缓存入库换来的是全队 warm 启动,值。
mtime 不可信的环境(网络盘、某些 CI)还有一个核选项:导入器有按文件内容 hash 比对的开关(editor/filesystem/import_use_multiple_threads 之外的 use_file_hash 类设置),把比对基准从 mtime 换成内容 hash。代价是每次比对要多读一遍文件内容,IO 换正确性,网络盘上一般不划算,本地盘可开。
导入本身怎么压
$t_{import}$ 的大头是贴图压缩。几个实测有效的杠杆:
import/use_multiple_threads:多线程导入,默认开。万级工程别关;- 压缩格式选对:给 Android 出包配 ASTC 预览时,编辑器里每次重导都是慢路径。日常开发期把部分贴图的压缩格式降到 Basis Universal 甚至不压缩,发布前再批量切回;
- 跳过用不到的导入:PSD/TIFF 源文件别进
res://,引擎会老老实实给它们跑导入。源资产放res://外面,导出前用脚本同步; - 分目录排除:
res://.gdignore文件可以让 EditorFileSystem 整目录跳过扫描——第三方资产包、参考素材这些不进引擎视野的,放被忽略的目录里,直接从 $N$ 里扣掉。
注意一个相关但独立的成本:全量扫描时 Godot 还会解析每个脚本文件提取全局类信息(class_name)生成脚本类缓存,GDScript 文件多了这一步也不便宜。它有自己的缓存文件,失效条件和上面同一批触发源。
工程实践清单
.godot/filesystem_cache、uid_cache(4.4 之前的工程)进版本库,全队 warm 启动;- CI 里用
--headless --editor --quit做导入校验时,给 CI 缓存目录($HOME/.cache/godot及工程的.godot/)做持久化,否则每次流水线都是一次冷启动; - 大贴图的压缩参数调整是团队级操作——一人改
import配置,全队下次启动全体重导那张图,通知一声能省一堆"我的编辑器怎么又在导入"的疑问; - 真到了十万级资源,考虑按模块拆成多个工程 + 导出 PCK 组合(六-9 热更那篇的分包思路反过来用),单工程 EditorFileSystem 的成本线性增长,架构上该躲就躲。
本文基于 Godot 4.2–4.4 编辑器行为;filesystem_cache 为二进制格式,不要手改,坏了就删(代价是一次冷启动)。