1、换了个行业,换了对「构建」的理解
去年我换工作了。
从互联网行业跳到了游戏行业。开发语言从 Java 换成了 C# 和 C++。IDE 从 IntelliJ IDEA 换成了 Rider 和 Visual Studio。
坦白说,技术栈的切换比想象中顺利——毕竟编程语言说到底都是相通的。但有一个东西,让我结结实实地震惊了一下。
构建系统。
在互联网行业做 Java 开发的时候,构建是世界上最简单的事:
./gradlew build
或者 Maven 项目:
mvn clean package
一行命令,三分钟之内编译、测试、打包一气呵成。搞不好还能配个 CI,每次 push 自动跑。
到了游戏行业之后,我才发现之前的「构建」只是幼儿园难度。
这里没有一行命令搞定一切的好事。没有「万物皆是 jar 包」的简单模型。没有 Maven Central 这种你一伸手就拿到所有依赖的地方。
这里有的是几十个 G 的资源文件、模型、贴图、音频、Shader、动画状态机……有的是「为什么这个构建要跑 40 分钟」的日常困惑。
更要命的是——公司同时使用 Unity 和 Unreal 两个引擎。
2、互联网的构建,本质是在和 CPU 编程
回想在互联网行业这十年,我做的事情本质上是在和 CPU 编程。
你的程序跑起来,输入是数据,输出是数据。HTTP 请求进来,数据库查一查,算一算,HTTP 响应出去。你要操心的是线程池、GC 停顿、SQL 优化、缓存一致性。
所有的问题都可以归结为:我怎么让 CPU 算得更快、更稳?
构建系统在这个世界里扮演的角色非常单纯——把 .java 编译成 .class,打成 jar 包,塞进容器镜像。Maven 和 Gradle 的复杂性,主要体现在依赖解析和版本冲突上,和 CPU 的运行模型一脉相承。
3、游戏的构建,本质上是在和 GPU 编程
到了游戏行业,画风突变。
游戏不是「计算数字」,游戏是「渲染画面」。你的程序跑起来,引擎在加载几百个模型,解码几千张贴图,编译几万个 Shader Variant,然后每 16 毫秒往 GPU 扔一帧。
你要操心的变成了——Draw Call、LOD、Mipmap、纹理压缩、Shader 编译缓存、光照烘焙……
你的对手从 CPU 变成了 GPU。
构建系统在这个世界里的角色也完全不一样了。它不只是「编译代码」,它要处理海量的资源资产:模型要导入,贴图要转格式,材质要烘焙,Shader 要预编译并缓存。
回过头看,从「和 CPU 编程」到「和 GPU 编程」,从「计算数字」到「渲染画面」,职业生涯也算完整了。哈哈。
4、Unity 的构建:一键构建只是假象
刚接触 Unity 的时候,看到 File → Build Settings → Build,我以为这就是游戏版的 gradle build。
天真了。
Unity 确实有个一键构建的界面,但点下去之前你要做的事情海了去了。你得用 C# 编写脚本,告诉 Unity 每个资源属于哪个 AssetBundle 包。系统提供了 BuildPipeline.BuildAssetBundles() 接口,但「哪些资源打哪个包、怎么分组、怎么命名、依赖怎么处理」——这些逻辑全部要你自己写。
更让人头疼的是,资源分包标记在早几个版本里是写死在 .meta 文件里的。一个资源属于哪个包,写在 meta 里。你换台机器、换个分支,meta 就跟着跑了。听起来没啥大问题,但在多人协作、频繁切换分支的游戏项目里,这是实实在在的噩梦。
说的直白一点——Unity 给的是最底层的接口,上层的全套分包逻辑都得开发者自己扛。而且这一切还依赖 Unity 编辑器环境。你想在纯命令行/CI 服务器上跑?可以,但不优雅。你得装一个完整的 Unity Editor,用 -batchmode 和 -nographics 跑。
这不是「一键构建」,这是「把复杂度的皮球踢给了你」。
5、Unreal 的构建:工业化的味道
相比之下,Unreal 的构建体系就工业多了。
首先,Unreal 的构建工具(UBT 和 UAT)是独立的命令行工具。它们是两个纯正的 C# 项目,有明确的步骤拆分和传参控制——你可以用 -cook、-pak、-stage 等参数精确控制每一步做什么。不需要打开编辑器窗口,不需要 GUI,CI 脚本一把梭。
当然,这不意味着「不需要安装 Unreal 引擎」——打包流程仍需要引擎来执行 Cook、编译 Shader 等实际操作。但它的入口是独立的 CLI,不像 Unity 那样只能用编辑器的批处理模式作为入口。Unity 的 -batchmode 任何时候都会启动完整编辑器进程、重新导入所有资源,限制很大。Unreal 的 UAT 是一个真正的命令行调度器,它知道先调什么、再调什么、怎么传参,把流程拆解得清清楚楚。这对 CI/CD 来说是质的区别。
其次,在资源处理上,Unreal 有 Cook 流程——引擎自动分析引用关系,把需要的东西「烤」进包里。你不用给每个资源手动标记「属于哪个包」。当然,Chunk 分区规则还是要在配置里指定,但这和 Unity 那种「每个资源都要写脚本决定归属」的工作量不在一个量级。
再者,Unreal 的缓存体系——DDC(Derived Data Cache)和 Shader Pipeline Cache——做得更系统化。同样的资源,只要 Cook 过一次、Shader 编译过一次,第二次就飞快。多人协作时甚至可以共享 DDC,一个人 Cook 完,全团队直接用。
总结一下我的感受:Unity 把构建的复杂度暴露给了你,Unreal 把构建的复杂度消化在了工具链里。
6、为什么要写这个系列
前面我说了这么多,其实核心就一句话——
游戏行业的「构建」,和互联网行业的「构建」,完全不是一回事。
但遗憾的是,这方面的中文资料并不多。你能搜到的多半是官方文档的翻译,或者「三步完成 Unity 打包」这种快餐教程。缺少从底层原理出发、循序渐进讲清楚「游戏构建到底在做什么」的内容。
所以我打算写两个系列,把我在 Unity 和 Unreal 构建上的学习与踩坑记录下来。
Unity 构建系列——从 Build Settings 的基础流程讲起,到 AssetBundle 的资源打包演进史,再到 Addressables 的设计理念和 IL2CPP 的跨平台机制。不跳步,不堆术语,一步步带你理解 Unity 这套构建体系为什么长成今天这个样子。
Unreal 构建系列——从 UBT/UAT 的角色定位开始,到 Cook 流程是干什么的,到 Pak 文件的打包机制,最后聊聊 Shader 编译这个让所有人头疼的老大难问题。
下一篇,我们从 Unity 构建(一) 开始——当你点下 Build Settings 的「Build」按钮,Unity 到底帮你做了多少步?
每天前进一小步,就是一个新的高度!