1. 为什么引擎项目走到一半,最大瓶颈往往是“工具链”
先聊一个我见过很多次的场景:引擎运行时功能已经写得七七八八了,渲染、场景管理、物理、资源加载好歹能跑通一个 demo,然后突然发现——往里放内容变成了一场灾难。美术同学导出的模型要手动改 JSON,策划同学想调一个参数得翻源码,你自己想复现一个 bug 还得靠 printf 满天飞。这种时候大家才会意识到一个问题:引擎本身的代码只是发动机,真正让车子能上路的是围绕引擎的一整套工具。
“Building a Simple Engine”系列的这一篇,进入的是 Tooling(工具链) 阶段。如果你正打算自己写引擎,或者你正在一个中小团队里维护引擎,那么这个阶段的核心任务很简单:让内容制作变成一种流水线作业,而不是程序员手把手代劳。合理推断,这个阶段解决的痛点大致是三类:
- 资源导入与加工:美术、音频、配置文件从原始资产变成引擎可用的运行时数据;
- 场景装配与调试:把模型、灯光、脚本放到一个可视化或者至少可批量操作的环境里;
- 反馈回路:每次改动从“改代码—重新编译—重启引擎”变成“改配置—热重载—立刻看效果”。
这篇文章我准备从工具链的定位讲起,然后拆解一个最小可用的工具链应该包含哪些模块,再给出一个可落地的搭建路径和一份常见问题排查清单。我自己在搭建引擎工具链时踩过不少坑,有些坑绕过去只需要一个设计决策,有些坑则是把代码删了重写才发现原来的方向有问题,所以这篇会尽量把这些判断说透,让后来者少走点弯路。
适合读这篇文章的人,是那种引擎代码已经有一定基础、但工具链部分还处于“用到才写、写了就乱”状态的人。不管你是独立开发者还是小团队里的引擎程序员,工具链的设计思路其实大同小异:先搞清楚谁在用这些工具、他们什么时候用、做什么,再考虑技术选型。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Tooling 在整个引擎里的位置:它和 Runtime 的关系
很多人聊引擎时习惯把所有代码放在一个仓库里,各种模块互相 #include,运行时和工具链之间没有明确边界。这个做法在非常早期的引擎里问题不大,但一旦工具变多、项目变大,耦合会变成最大的隐形炸弹。
2.1 Runtime 与 Tooling 的分工逻辑
从功能上看,Runtime(运行时) 关心的是“在目标平台上跑起来时尽可能高效”,它要管理内存、控制 GC、减少磁盘 IO,代码里不能有编辑器相关的逻辑,更不能依赖 UI 框架或者本地文件系统细节。Tooling 则完全相反,它运行在开发机甚至云端构建机上,它的任务是把内容资产转化成 runtime 能吃的格式,还要提供可视化编辑、批量修改、校验报错、性能剖析等能力。
一个很形象的类比:Runtime 是舞台上的演员,Tooling 是幕后的导演、灯光师、道具师。前者追求的是表演本身稳定且不穿帮,后者追求的是让整个剧组高效协同。你不能让演员一边表演一边自己搬道具,也不能让道具师在演到一半时上台去挪场景。
所以我在搭建工具链时第一个做的决定就是:整套工具链从第一天起就要和 runtime 保持独立。这个独立不一定是进程级隔离,但至少在代码模块上要分清边界。比如资产导入器只在编辑器进程或命令行工具里运行,它调用引擎的资源系统、序列化系统,但绝不反向依赖渲染循环或者场景 tick 逻辑。
具体到代码组织上,一个常见的做法是分出这样几个项目:
| 模块 | 职责 | 依赖关系 |
|---|---|---|
| RuntimeCore | 渲染、物理、音频、场景管理等 | 不依赖任何工具链代码 |
| AssetPipeline | 导入、转换、校验、打包 | 依赖 RuntimeCore 的资源与序列化定义 |
| EditorCore | 场景编辑、Inspector、调试工具 | 依赖 RuntimeCore 与 AssetPipeline |
| StandaloneTools | 命令行批处理工具、CI 集成 | 只依赖 AssetPipeline |
这样分完之后,一个很实际的好处出现了:runtime 可以在完全脱离编辑器的情况下单独测试。我当时在开发里遇到性能问题要定位,第一件事就是分析有没有“编辑器逻辑混进了 runtime 路径”,如果没有,那性能剖析的结果就非常干净,不用考虑编辑器的叠加影响。
2.2 工具链的四种典型形态
顺着上面说的模块划分,我再把工具链里常见的形态梳理一下,因为它们对开发方式的影响完全不同:
- 独立命令行工具:例如
asset_import、texture_compress、build_scene。它们的优势是可以在 CI 里跑,可以批量执行,也方便写自动化测试。通常在资源导入和构建阶段使用。 - 嵌入式的开发者 HUD 或调试面板:例如运行时显示 FPS、内存占用、绘制调用数量。这种工具虽然“看起来”属于 runtime,但本质上是为开发人员服务的,发布版本应该剥离。
- 可视化编辑器:例如场景编辑器、材质编辑器。它们最直观,但也是工作量最大的地方。早期工具链完全可以不做编辑器,先用文本格式和命令行顶住。
- 资产扫描与元数据管理工具:例如检查哪些资源没有被引用、哪些贴图尺寸过大、哪些音频没有设置压缩格式。这类工具往往是被忽视但性价比极高的。
我在自己的引擎里一开始只做了第一条和第三条里最简形式的命令行工具,后来才补了编辑器。现在回头看,这个顺序是对的:先把“数据能进、能出、能验证”的地基打好,再往上盖可视化那层楼,会轻松很多。如果你上来就直接闷头做编辑器,大概率会发现底层资源格式设计得不好,导致编辑器里改了一个参数但保存时不知道存到哪、加载时不知道按什么规则还原。
2.3 工具链是“为程序员服务”还是“为内容制作者服务”
这是一个方向的灵魂问题,值得单独拿出来说。我自己见过太多引擎团队在这里摇摆不定。
如果工具链只服务程序员自己,那么很多事可以省掉——用 C++ 直接硬编码一个场景然后编译运行,又快又稳,不用做序列化,不用做 JSON 解析。但一旦有美术或策划参与,工具链的设计立场就完全变了:他们的需求不是“能改代码”,而是“我要改一个数字然后立刻看到效果”。所以工具的 UI、交互、出错提示都要围绕非程序员来设计。
从“Building a Simple Engine”这个系列的角度看,我猜你大概率是在一个人开发,或者和极少数几个人协作。即便如此,我还是建议你用一种“服务他人”的心态来做工具链。原因有二:
- 哪怕只有你自己,两个月后你回头看自己的工具,也会像看陌生人写的东西一样,如果工具链交互不清晰、报错不友好,你同样会浪费大量时间;
- 如果有任何一个内容制作者加入,半成品的工具链会直接变成团队的阻力,而不是助力。
所以工具链设计的第一原则被我总结为:让正确的事情变得容易,让错误的事情变得显眼。前者指的是默认配置和常用路径要顺手,后者指的是校验和错误信息能第一时间把人导入正轨。
3. 工具链的核心组成:一个最小可行性方案
站在“还什么都没有”的起点,我给工具链规划了五个核心组件。这些组件不一定要同时开发,但头脑里必须有一个全景图,才能在做数据格式时不给自己挖坑。
3.1 资源导入管线
任何引擎要做内容,第一步都是“把外部资产变成引擎资产”。外部资产可能是 FBX/glTF 模型、PNG/TGA 贴图、WAV 音频、JSON 配置,引擎资产则最好是经过优化、去冗余、按平台需求打包过的数据。
一个典型的导入管线按顺序做这些事:
- 解析原始文件格式:这一步通常依赖第三方库,比如 ASSIMP 读模型、stb_image 读图片、dr_wav 读音频;
- 转换与优化:模型可能要做顶点合并、骨骼重排、材质归并;贴图可能要做压缩格式转换(ASTC、BC7 之类);音频可能要做重采样和 ADPCM 编码;
- 生成引擎资源对象:把转换后的数据打包成引擎定义的资源结构,写入自定义的二进制或者 JSON 格式;
- 写入资产数据库:记录 meta 信息,比如导入时间、源文件哈希、目标平台,方便增量导入和依赖检查。
你不需要一次性把这条管线全部实现。最初版本可以只做一个简单版本:有一个源资产目录,写一个小工具扫描目录里的文件,生成 .engine_asset 文件到另一个目录,然后用一个 manifest.json 记录映射关系。这样当运行时加载资源时,可以通过 manifest 找到对应的引擎资产文件。
注意:源文件的哈希校验从一开始就要做。否则每次导入都是全量扫描,资产多了之后速度会让人无法忍受。这个哈希不仅是文件内容的 MD5/SHA,最好还要包含导入参数——同一个源文件如果导出设置改了,也要能识别为“需要重新导入”。
3.2 场景与实体数据格式
引擎的中心是场景,工具链的中心则是“场景数据的产生与编辑”。哪怕你没有可视化编辑器,也需要一个稳定的场景数据格式。
我比较推荐“双格式”策略:
- 开发格式(Editable Format):JSON 或者 YAML,可读性强,方便 diff、合并且出错时容易排查;
- 运行时格式(Runtime Format):二进制,加载快,不包含多余信息,方便序列化到内存。
场景里的实体可以很朴素:每个 Entity 有一个 ID、一个名字、一个 Transform 以及一组 Component 数据。Component 的具体结构由引擎定义,工具链只负责读写它们。这个设计和现在主流的 ECS 并不冲突——ECS 关注的是运行时性能,场景文件关注的是“怎么描述一个游戏世界”。在实际工程里,场景文件可以是 ECS 的“源数据”,运行时启动时再展开成 ECS 的数组结构。
一个很关键的经验是:场景格式的版本号从一开始就要放在文件里。否则哪天你改了字段结构,老场景没法加载,而你又没有迁移工具,那基本等于所有关卡白做。加上版本号后,你可以写一个 migration 系统,低版本文件自动升级到高版本,这个在后续开发中会救你无数次。
3.3 数据校验与错误报告
工具链里的“校验”可能是最不起眼但最有价值的部分。如果你有一个可视化编辑器,几百个资产摆在那里,每个都可能有不同的错误——模型缺材质、贴图尺寸不是 2 的幂、音频采样率不统一……如果没有集中式校验,内容制作者就会在运行时才发现问题。
所以在工具链里我建议做一个简单的 check 系统:
- 每个资源有自己的校验规则,比如“模型必须有至少一个材质槽”“贴图宽高应为 2 的幂”“音频文件时长不能为 0”;
- 校验结果汇总后输出报告,按 Error / Warning / Info 分级;
- 命令行工具可以做“全量校验”,编辑器里可以做“保存时校验”。
这个系统实现起来不难,但收益极其明显。它最大的价值是把错误发现在源头,而不是让错误一路传导到运行时的奇怪现象里,比如模型半边脸消失、贴图紫色、声音噪音——那是排查成本极高的事。
3.4 构建与打包脚本
如果你的引擎只是自己本机跑 demo,那打包这一步可以往后放。但只要你需要把游戏发给别人玩,或者要发布到某个平台,一个可靠的构建脚本就必不可少。
构建脚本至少要做三件事:
- 收集所有需要打包的场景和资源;
- 运行导入管线,确保所有资源都是最新版本;
- 生成最终包体(可以是可执行文件加资源包,也可以进一步打成压缩包)。
在这个阶段,你可以考虑引入简单的 CI——比如每次提交代码后自动构建一次。这个“自动化”的意义不在于省掉你手动敲命令的时间,而在于坏事情会在你合并代码后的几分钟内暴露,而不是一周后当你准备演示 demo 时才发现引擎链接失败。
3.5 运行时调试工具与日志系统
最后但同样重要的是运行时调试手段。这里我不想只聊 printf 这种最基础的方式,而是建议你从早期就规划一个分级日志系统和一个可选的调试覆盖层。
日志系统至少要有 Debug / Info / Warn / Error 四个级别,能输出到控制台和文件,并且能按模块过滤。很多引擎问题出现时,第一现场就在日志里。比如物理穿透、资源加载失败、网络超时,日志里往往已经把前因后果写清楚了,你缺的只是一个“可搜索”的途径。
调试覆盖层指的是运行时画线和调试菜单。画线工具可以用来显示碰撞体、寻路网格、光源范围;调试菜单可以在运行时开关一些功能、调整参数。这些本质上是“运行时 + 工具”的混合体,但它被实现成一个独立模块,只对 Debug 编译开放。这样发布版本不会带上这些机制,也不会影响性能。
4. 具体地,怎么一步一步搭建这套工具链
接下来这部分我按时间顺序展开一个“最低可行但真正可用”的搭建路径。这不是唯一正确的路,但这是一条我实际走过、确认没有追平的死路。
4.1 第一步:先把资源导入做成一条命令行通路
我在刚开始时犯过一个典型错误:把资源导入函数写在引擎代码里,每次运行引擎时检查资源是否需要导入,需要就先导一次。这个方案看起来方便,实际上非常不可控——你没法并行导入,没法做批量处理,程序崩溃时你甚至不知道导入到哪一步了。
正确姿势是写一个独立的命令行工具,比如 tool_import。它的用法就一句话:
bash复制tool_import --source ./assets_src/ --target ./assets_cache/ --platform win64
这个工具有一个核心清单,记录每个源文件的哈希和对应的目标文件。第一次运行时全量导入,之后每次只处理新增和变化的文件。
我建议先从网格加载开始,因为模型是几乎所有项目最核心的资产类型。加载 glTF 是一个不错的起点,它比 FBX 简单、文档好,而且很多免费工具都支持导出 glTF。处理流程大致是:
- 用 ASSIMP 读取 glTF 文件;
- 提取所有 Mesh 的顶点数据(位置、法线、UV、切线、骨骼索引与权重);
- 按引擎要求的布局打包成二进制 Mesh 文件;
- 生成一个 .meta.json 文件记录顶点数、子网格数、包围盒、骨骼名称表等。
这样做的结果是,你有了一个完全独立的“模型导入”步骤,可以逐文件运行、可以写测试。当你发现某个模型导入结果不对时,你可以单独对那个文件跑一遍导入工具看日志,而不用把引擎启动起来。这笔买卖太划算了。
4.2 第二步:设计场景描述文件与“场景编译器”
有了模型和材质资源还不够,你还得能拼场景。在这个阶段,我强烈建议不要立刻写编辑器 UI,先定义一个可读的场景描述格式。
一个最小的场景文件大概长这样,我用 JSON 表示:
json复制{
"version": 1,
"entities": [
{
"id": "e1001",
"name": "MainCharacter",
"transform": {
"position": [0.0, 0.0, 0.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
},
"components": [
{
"type": "MeshRenderer",
"mesh": "assets_cache/characters/main_hero.mesh",
"material": "assets_cache/materials/hero_standard.mat"
},
{
"type": "PointLight",
"color": [1.0, 0.95, 0.8],
"intensity": 3.5,
"range": 12.0
}
]
}
]
}
然后写一个“场景编译器”,读取 JSON 并输出二进制场景文件。这一步等于把“文本描述”和“运行时数据”解耦。当你以后想加一个类型新的 Component 时,只需要扩展编译器,而不用改动运行时加载路径。
在设计这个格式时,有一个点要特别注意:不要把 Transform 写成后面再补的“非必有字段”。哪怕你现在所有实体都是静态的,也要把 Transform 完整地放进去。因为一旦场景变大,你要做编辑器、做寻路、做相机切换时,99% 的实体都需要位置和朝向。从第一天起就把这个字段当第一公民,能省掉后续大量的数据迁移工作。
4.3 第三步:做资源热重载
工具链的价值感知最强的节点,就是“热重载”。当我完成这一步时,我整个开发节奏被重塑了:修改场景文件,保存,引擎里按一个快捷键或者文件系统 watch 触发重载,立刻看到变化,而不是编译、重启、重新加载全部资源。
热重载并不是一个花哨的功能,它的本质要求是你在运行时维护了一张资源表:
- 每个资源文件对应一个资源 ID;
- 系统监听这些文件的修改时间或哈希变化;
- 当变化被检测到时,重新触发导入管线并替换内存中的资源对象;
- 所有引用这个资源的系统收到一个“资源已更新”的事件。
需要注意的是,热重载不是所有资源都应该支持。例如物理碰撞体在运行时被重建往往很麻烦,音频资源重载还可能出现播放句柄失效。所以设计时要按资源类型分别决定热重载策略:Mesh、Texture、Material、场景配置这几个是高优先级的;Physics Data 可以不做热重载,直接标注 Requires Restart。
我在实现热重载的过程中发现一个比较容易踩的坑:重新导入资源后,旧资源的 GPU 显存没有来得及释放。如果一小时内来回改一个贴图,显存悄然暴涨。解决思路是延迟释放:当资源被替换时,旧资源进入一个“待删除队列”,等渲染帧结束再真正释放 GPU 资源。这个机制刻不容缓。
4.4 第四步:做一个极简的编辑器
等前几步行得通了,再做编辑器就顺理成章。但这里的建议是“极简”:不要一开始就把 Unity 或者 Unreal 的界面当目标。一个只有三个面板的编辑器就够了:
- 场景面板(3D 视图,用来选物体、移动物体);
- 层级面板(列出所有实体,方便选择和排序);
- 属性面板(显示选中实体的组件和属性,允许修改)。
从技术上来说,我的建议是先用 Dear ImGui 搭这个界面。它比 Qt 或者原生 GUI 更贴合游戏引擎开发场景,渲染循环完全由你控制,布控件代码量小,迭代速度快,而且你可以很容易地把运行时用的相机和视图嵌进去。
编辑器里有一个非常容易被低估但价值极高的功能:Undo/Redo。我这里指的不是那种“闭着眼睛把整棵树存一份来恢复”的假 Undo,而是对每个属性修改生成一个反向操作记录。假设你的场景有 1000 个实体,误操作删除掉了其中一个,如果 Undo 只是“重新加载文件”,那你丢失的是那个实体之后所有的修改。真 Undo 系统初期可以简单点,比如为每个实体维护“版本号+快照”,一帧级粒度甚至操作粒度都可以慢慢优化,但框架要预留。
4.5 第五步:把工具链接入你的日常迭代循环
工具链真正成熟的标志是:它不再是“额外的工作”,而是整个开发循环的一部分。我习惯把开发循环描述成下面这样:
- 内容制作者(或者你自己)在源资产目录里放新模型、写新配置;
- 导入工具自动或者一键触发,更新引擎资产;
- 场景编辑器中调整布局和属性;
- 运行引擎,直接看到效果;
- 发现问题,回到工具里修改,热重载,再次观察。
如果每一步之间都要手动复制文件、手敲命令、重新导入,那工具链的价值就打了折扣。所以从第四步开始,我建议把文件系统监控加上。用小工具 watch 源资产目录,只要有文件变化就自动触发导入,然后把结果写进日志。这样你的流程就从“我手动跑了工具”变成“我保存了文件,工具自己知道该干活”。
走到这一步之后,你会开始体会到“引擎开发”和“用引擎做游戏”的边界开始消融。
5. 工具链开发中我踩过的几个典型坑
工具链没有银弹,很多经验必须靠实践积累。这里整理几个我遇到的非常典型的问题,如果你恰好发现自己也碰上了,可以少走弯路。
5.1 过早引入可视化编辑器
我前面已经反复强调了:先命令行,后 UI。原因不只是工作量的问题,而是UI 会掩盖数据流问题。假设你的场景保存功能有 bug,在命令行工具里很容易通过单元测试发现,但在 UI 里你可能只看到“点保存后重启,部分修改没生效”,然后去猜原因,定位成本翻倍。
正确顺序是:数据管线先行,UI 仅仅是数据管线的用户之一。如果 UI 出问题,你能确定问题大概率在 UI 层,而不是数据层。
5.2 直接把源资产拷进包体
这在早期很常见:为了方便,引擎加载时直接读源模型文件或者原始贴图文件,省去了导入步骤。短期看确实快,但后患无穷:原始文件带大量无关数据,加载慢、占空间大;而且一旦你后续在导入过程中加了优化(比如顶点压缩、纹理合批),源资产直接加载的路径会成为“特殊分支”,两种路径的结果可能不一致,导致奇怪的 bug。
所以我很早就规定了一条红线:运行时永远不直接读源资产文件。所有的运行时资源只能来自 assets_cache 目录下的引擎资产。
5.3 校验规则写死在每个工具里
很多工具链问题演进到后面,改的不是功能,而是校验规则。如果你把“颜色值是否合法”“尺寸是否符合条件”写在各个工具的代码里,那么一旦规则变化,你得同时改 N 个地方,漏一个就是线上事故。
更好的方式是把校验规则独立成一份配置,甚至可以用 Lua/Python 写成脚本,工具链只负责加载和执行这些规则。这样当对方说“这个字段新增了一条限制”,你只需要改一份文件。
5.4 忽略了离线构建与增量构建
你可能觉得“我本地跑得好好的,为什么要管增量构建?”直到你的源资产目录里有了几千个文件,每次导入全量跑要几分钟的时候,你才会心疼时间。所以增量导入系统从第一次就要做好,核心就两个东西:源文件哈希 + 导入参数哈希,两者都变了才重新导入,缺一不可。
打包也是这样。一个简单的“只打包运行时需要的资源”的依赖收集器并不难写,但它能把包体从几个 GB 缩到几百 MB,而且能极大减少运行时加载的无效文件数。
5.5 没有考虑资源文件的后向兼容
我见过很多引擎在开发到一半时改了资源格式:字段重命名、结构嵌套层次修改、数据从 float 数组改成 double 数组。如果不处理兼容,老数据全部作废,关卡需要重新摆。这是一个沉默但毁灭性的成本。
做法非常简单:每个资源文件头加一个 version 字段,然后提供一个迁移层。迁移层是链式的,v1 -> v2 -> v3。每次格式变动都新增一个迁移函数,旧版本文件加载时会自动调用对应的迁移函数链。这个机制一劳永逸。
6. 工具链开发中的常见问题速查与避坑技巧
最后按“问题-原因-解法”这种速查方式整理一份清单,方便你以后直接对着排查。
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 导入后的模型在引擎里显示材质全丢 | 导入只处理了几何数据,材质引用没有正确生成 | 检查 .meta.json 中材质槽数量;确认源文件导出时是否内嵌材质;确认材质查找路径是相对路径而非绝对路径 |
| 场景文件保存后再加载,某些组件的属性不一致 | JSON 字段名拼写不一致,或者序列化时忽略默认值导致读不出来 | 写单元测试,对“保存-加载-再保存”做幂等性验证;检查序列化层对默认值的处理策略 |
| 热重载后旧纹理显存缓慢增长 | 旧 GPU 资源没有延迟释放 | 把被替换的资源加入延迟删除队列,在每帧渲染结束统一释放 |
| 大批量修改场景文件时工具崩溃 | 文件写入没有做成原子操作 | 先写临时文件,校验完成后 rename 覆盖原文件;崩溃恢复时用临时文件回滚 |
| 构建出来的包体巨大 | 直接复制了源资产目录 | 写依赖收集器,只拷贝引擎资产和运行时必需的配置;对贴图、音频按平台做压缩 |
| 工具链代码改一处,多个工具行为不一致 | 公共逻辑没有抽取到独立模块 | 把所有工具共享的逻辑(导入、校验、序列化)收敛到同一个静态库或者模块里 |
7. 从“能用”到“好用”:工具链的三个层次
工具链开发这件事,做到最后其实是在做“开发体验”和“内容生产效率”的优化。如果按层次划分,我会分成三层:
第一层:能用。命令行能导入、能打包,报错信息勉强看得懂。满足这个层次,引擎已经可以做 demo 了。
第二层:好用。热重载完善、编辑器顺手、校验规则覆盖大部分问题、CI 能出包。达到这个层次,一个 3-5 人的小队可以比较流畅地做研发。
第三层:可扩展。工具链自身的架构清晰,加新的资源类型、新的 Component、新的平台构建目标都不需要重写工具;校验规则和数据迁移机制能支撑长期迭代。
“Building a Simple Engine”系列做到 Tooling 这一篇,核心目标其实是从第一层次滑向第二层次。我的建议是没必要追求一步到位,工具链和引擎本身一样,是随着项目需求成长的。一版工具能满足当前一个月的开发需要,就是成功的工具。很多团队的问题是工具链做得太超前,结果大部分功能根本没被用到,反而因为没人维护而腐化,最后比“没有工具”还糟糕。
我个人在实际操作中的体会是:工具链的重点不在代码量,而在反馈速度。一个能让你在 10 秒内看到修改效果的简单工具,比一个功能丰富但每次修改都要重新导入、重新构建的重型工具,价值高出一个数量级。后期如果要做复杂的资产处理,比如大世界地形、动画状态机、材质蓝图之类的,再逐步向第三层次演进也不迟。
最后再分享一个小技巧:如果你打算为引擎写工具链,一定尽早把“命令行入口”做得足够好用。不是所有人都会一直开着编辑器,而且你需要跑批量测试、CI 构建、夜间打包时,一个可靠的命令行工具会是你的救命稻草。可视化界面可以慢慢美化,但命令行的核心逻辑从第一天起就该是稳定的、可测试的。
