点击Build后,Unity 会把 C# 代码、场景、资源和 Shader 处理成目标平台能运行的安装包。了解每个阶段做了什么,有助于控制包体、加快构建、排查问题。
💡 本文以 Unity 2021 LTS ~ Unity 6 为参考。Unity 6 中 Build Settings 改名为Build Profiles,但核心流程相同。
一、构建流程全景图
点击 Build │ ├─① 构建前回调(IPreprocessBuild) │ ├─② 脚本编译 ──→ C# → IL(.dll) │ │ │ ├─ Mono:直接打包 DLL │ └─ IL2CPP:裁剪 → 转 C++ → 原生编译 │ ├─③ 场景处理(IProcessSceneWithReport) │ ├─④ 资源收集:Scenes In Build + 依赖 + Resources │ ├─⑤ 资源转换:纹理压缩、模型、音频、Shader 变体编译 │ ├─⑥ 序列化写入:level0、sharedassets、resources.assets │ ├─⑦ 平台打包:Gradle(Android)/ Xcode 工程(iOS) │ └─⑧ 构建后回调(IPostprocessBuild)二、脚本编译:Mono 与 IL2CPP
1. 两种脚本后端
| 对比项 | Mono | IL2CPP |
|---|---|---|
| 原理 | 打包 IL,运行时 JIT 编译 | IL 转 C++,提前编译为机器码(AOT) |
| 构建速度 | 快 | 慢(需编译大量 C++) |
| 运行性能 | 一般 | 更好 |
| 反编译难度 | 低(DLL 可直接反编译) | 较高 |
| 平台要求 | — | iOS 必须;Google Play 上架 64 位必须 |
| 热更新 | 可直接替换 DLL | 需借助 HybridCLR、xLua 等方案 |
2. IL2CPP 的完整流程
C# 源码 ↓ Roslyn 编译 Assembly-CSharp.dll 等 IL 程序集 ↓ UnityLinker(托管代码裁剪) 裁剪后的 DLL ↓ il2cpp.exe C++ 源码(Library/Il2cppBuildCache) ↓ Clang / NDK / Xcode libil2cpp.so(Android)/ UnityFramework(iOS)/ GameAssembly.dll(PC)3. 代码裁剪(Managed Stripping)
Player Settings → Other Settings → Managed Stripping Level
| 级别 | 说明 |
|---|---|
| Disabled | 不裁剪(仅 Mono 可选) |
| Minimal / Low | 保守裁剪,较安全 |
| Medium | 裁剪更多,反射代码可能出问题 |
| High | 包体最小,风险最高 |
被反射、序列化库(如 Newtonsoft.Json)调用的类型可能被误删,导致运行时报MissingMethodException。可以用link.xml保留:
<!-- Assets/link.xml --><linker><assemblyfullname="Assembly-CSharp"><typefullname="MyGame.Data.PlayerConfig"preserve="all"/></assembly><assemblyfullname="Newtonsoft.Json"preserve="all"/></linker>或者在代码中标记:
usingUnityEngine.Scripting;[Preserve]publicclassPlayerConfig{}4. 加快脚本编译
- 使用Assembly Definition(.asmdef)拆分程序集,修改代码时只重新编译相关部分;
- 第三方插件放入独立的程序集;
- 编辑器代码放在
Editor文件夹或 Editor-only 程序集中,避免被打进包体。
三、资源收集:哪些东西会被打进包?
Unity 采用依赖追踪,并不是把 Assets 下所有文件都打进去。
| 来源 | 是否打包 | 说明 |
|---|---|---|
| Scenes In Build中的场景 | ✅ | 以及它们引用的所有资源 |
| 场景引用的资源 | ✅ | 自动追踪依赖 |
Resources文件夹 | ✅全部打包 | 无论是否使用,都会打进resources.assets |
StreamingAssets文件夹 | ✅ 原样复制 | 不做任何处理 |
| Always Included Shaders | ✅ | Graphics Settings 中配置 |
| 未被引用的资源 | ❌ | 不会打包 |
| Editor 文件夹 | ❌ | 仅编辑器使用 |
构建产物结构(以 PC 为例)
MyGame_Data/ ├── level0, level1 ... ← 场景数据 ├── sharedassets0.assets ... ← 场景共享的资源 ├── resources.assets ← Resources 文件夹内容 ├── globalgamemanagers ← 项目设置 ├── StreamingAssets/ ← 原样复制 └── Managed/ 或 il2cpp_data/ ← 代码相关⚠️
Resources是包体膨胀的常见原因。官方建议仅用于少量全局配置,大量资源应改用 AssetBundle 或 Addressables。
四、AssetBundle 与 Addressables
1. 为什么需要它们
- 减小首包体积,资源可按需下载;
- 支持资源热更新;
- 精细控制内存加载与卸载。
2. 构建顺序很重要
使用 Addressables 时,需要先构建资源,再构建 Player:
① Addressables Groups → Build → New Build → Default Build Script ② File → Build Settings → Build也可以在Addressable Asset Settings中勾选Build Addressables on Player Build,让它自动执行。
3. 常见注意点
| 问题 | 建议 |
|---|---|
| 资源被重复打包 | 使用Analyze工具检查重复依赖,将公共资源单独分组 |
| 压缩格式 | 本地包用LZ4(加载快);远程包可选 LZMA(体积小,但需解压) |
| 热更新 | 使用Update a Previous Build,并保留addressables_content_state.bin |
| Shader 重复 | 公共 Shader 单独打包,避免每个包各带一份 |
五、Shader 编译与变体
Shader 往往是构建慢、包体大、内存高的头号原因。
1. 变体是怎么产生的
#pragma multi_compile _ _FOG_ON // 2 个 #pragma multi_compile _ _SHADOWS_ON // 2 个 #pragma multi_compile _LOW _MID _HIGH // 3 个 // 2 × 2 × 3 = 12 个变体,再乘以 Pass 数和图形 API 数2. multi_compile 与 shader_feature
| 关键字 | 打包规则 | 适用场景 |
|---|---|---|
multi_compile | 所有组合都打包 | 运行时代码会切换的功能 |
shader_feature | 只打包材质实际用到的组合 | 材质上固定的功能开关 |
_local后缀 | 局部关键字,不占全局关键字数量 | 推荐优先使用 |
3. 剔除不需要的变体
usingSystem.Collections.Generic;usingUnityEditor.Build;usingUnityEditor.Rendering;usingUnityEngine;usingUnityEngine.Rendering;publicclassStripDebugShaderVariants:IPreprocessShaders{publicintcallbackOrder=>0;privatestaticreadonlyShaderKeywordDebugKeyword=newShaderKeyword("_DEBUG_VIEW");publicvoidOnProcessShader(Shadershader,ShaderSnippetDatasnippet,IList<ShaderCompilerData>data){for(inti=data.Count-1;i>=0;i--){if(data[i].shaderKeywordSet.IsEnabled(DebugKeyword))data.RemoveAt(i);}}}4. 其他建议
- URP Asset中关闭不使用的功能(如额外光源阴影、SSAO),URP 会自动剔除对应变体;
- Graphics Settings → Shader Stripping中剔除不用的雾效、光照贴图模式;
- 使用ShaderVariantCollection记录并预热变体,避免运行时首次使用卡顿;
- Editor.log 中搜索
Compiling shader可查看各 Shader 的变体数量。
六、Android 构建细节
1. 关键设置
| 设置 | 推荐 |
|---|---|
| Scripting Backend | IL2CPP |
| Target Architectures | ARM64(Google Play 要求 64 位) |
| 纹理压缩 | ASTC |
| 输出格式 | 上架 Google Play 用AAB;测试用 APK |
| Minify | Release 开启 R8,注意保留插件类 |
| Target API Level | 满足 Google Play 的最新要求 |
2. 构建流程
Unity 导出 Gradle 工程(Library/Bee/Android/Prj) ↓ Gradle 编译 Java/Kotlin、合并 AndroidManifest、打包资源 ↓ 签名(Keystore) ↓ APK / AAB3. 常见问题
| 问题 | 解决方法 |
|---|---|
| Gradle 构建失败 | 查看 Console 中完整的 Gradle 报错;检查 SDK / NDK / JDK 版本 |
| 依赖冲突 | 使用External Dependency Manager统一处理 |
| 需要自定义 Gradle | Publishing Settings 中启用 Custom Main Gradle Template 等模板 |
| AAB 超过 150MB | 使用Play Asset Delivery拆分资源 |
| 需要导出工程调试 | 勾选Export Project,用 Android Studio 打开 |
七、iOS 构建细节
Unity不直接生成 ipa,而是导出一个Xcode 工程,再由 Xcode 编译、签名、打包。
Unity Build → Xcode 工程 → Xcode Archive → 签名 → ipa1. Append 与 Replace
| 选项 | 说明 |
|---|---|
| Append | 保留你在 Xcode 中的修改,增量更新,速度快 |
| Replace | 完全重新生成工程,手动修改会丢失 |
2. 用代码自动修改 Xcode 工程
手动改 Xcode 设置容易遗漏,推荐在构建后回调中自动处理:
#ifUNITY_IOSusingSystem.IO;usingUnityEditor;usingUnityEditor.Callbacks;usingUnityEditor.iOS.Xcode;publicstaticclassIOSPostBuild{[PostProcessBuild(100)]publicstaticvoidOnPostBuild(BuildTargettarget,stringpath){if(target!=BuildTarget.iOS)return;// 修改工程设置stringprojPath=PBXProject.GetPBXProjectPath(path);varproj=newPBXProject();proj.ReadFromFile(projPath);stringframeworkTarget=proj.GetUnityFrameworkTargetGuid();proj.SetBuildProperty(frameworkTarget,"ENABLE_BITCODE","NO");proj.AddFrameworkToProject(frameworkTarget,"StoreKit.framework",false);proj.WriteToFile(projPath);// 修改 Info.pliststringplistPath=Path.Combine(path,"Info.plist");varplist=newPlistDocument();plist.ReadFromFile(plistPath);plist.root.SetString("NSCameraUsageDescription","需要使用相机进行扫码");plist.WriteToFile(plistPath);}}#endif八、构建回调:在流程中插入自己的逻辑
| 接口 | 时机 | 典型用途 |
|---|---|---|
IPreprocessBuildWithReport | 构建开始前 | 写入版本号、切换配置、检查资源 |
IProcessSceneWithReport | 处理每个场景时 | 删除调试物体、烘焙数据 |
IPreprocessShaders | 编译 Shader 变体前 | 剔除变体 |
IPostprocessBuildWithReport | 构建完成后 | 修改工程、上传、通知 |
usingUnityEditor;usingUnityEditor.Build;usingUnityEditor.Build.Reporting;usingUnityEngine;publicclassBuildVersionWriter:IPreprocessBuildWithReport{publicintcallbackOrder=>0;publicvoidOnPreprocessBuild(BuildReportreport){stringbuildTime=System.DateTime.Now.ToString("yyyyMMdd_HHmm");PlayerSettings.bundleVersion=$"1.0.{buildTime}";Debug.Log($"[Build] 平台:{report.summary.platform},版本:{PlayerSettings.bundleVersion}");}}九、自动化构建与 CI
1. 构建脚本
// Assets/Editor/Builder.csusingSystem;usingSystem.Linq;usingUnityEditor;usingUnityEditor.Build.Reporting;usingUnityEngine;publicstaticclassBuilder{publicstaticvoidBuildAndroid(){// 取 Build Settings 中已启用的场景string[]scenes=EditorBuildSettings.scenes.Where(s=>s.enabled).Select(s=>s.path).ToArray();EditorUserBuildSettings.buildAppBundle=false;// true 则输出 AABPlayerSettings.SetScriptingBackend(BuildTargetGroup.Android,ScriptingImplementation.IL2CPP);PlayerSettings.Android.targetArchitectures=AndroidArchitecture.ARM64;varoptions=newBuildPlayerOptions{scenes=scenes,locationPathName="Builds/Android/MyGame.apk",target=BuildTarget.Android,options=BuildOptions.None// 调试可用 Development | ConnectWithProfiler};BuildReportreport=BuildPipeline.BuildPlayer(options);BuildSummarysummary=report.summary;if(summary.result==BuildResult.Succeeded){Debug.Log($"构建成功:{summary.totalSize/1024/1024}MB,耗时{summary.totalTime}");}else{Debug.LogError($"构建失败:{summary.result}");if(Application.isBatchMode)EditorApplication.Exit(1);// 让 CI 识别失败}}}2. 命令行构建
# Windows"C:\Program Files\Unity\Hub\Editor\2022.3.xxf1\Editor\Unity.exe"^-batchmode-quit-nographics^-projectPath"D:\Projects\MyGame"^-buildTargetAndroid ^-executeMethodBuilder.BuildAndroid ^-logFile"D:\Projects\MyGame\build.log"| 参数 | 作用 |
|---|---|
-batchmode | 无界面运行 |
-quit | 执行完自动退出 |
-buildTarget | 启动时直接切换平台,避免二次导入资源 |
-executeMethod | 调用静态构建方法 |
-logFile | 输出日志,CI 中排查问题必备 |
可接入Jenkins、GitHub Actions(GameCI)、GitLab CI、TeamCity等工具实现自动出包。
⚠️ 同一个工程目录不能同时打开两个 Unity 实例,CI 构建前需确保编辑器已关闭。
十、包体分析与优化
1. 查看构建报告
构建完成后,打开Editor.log(Console 右上角菜单 → Open Editor Log),搜索Build Report:
Textures 120.5 mb 58.3% Meshes 20.1 mb 9.7% Animations 15.2 mb 7.4% Sounds 18.0 mb 8.7% Shaders 8.3 mb 4.0% ... Used Assets and files from the Resources folder, sorted by uncompressed size: 12.0 mb Assets/Textures/Background.png ...也可以安装Build Report Inspector包,或使用 Asset Store 上的 Build Report Tool,可视化分析。
2. 常见优化手段
| 类别 | 方法 |
|---|---|
| 纹理 | 移动端用 ASTC;按需设置 Max Size;关闭不需要的 Mipmap 和 Read/Write |
| 模型 | 关闭 Read/Write;压缩网格;去掉不需要的法线、切线、UV2 |
| 动画 | 设置 Anim. Compression 为 Optimal;去掉缩放曲线 |
| 音频 | 长音乐用 Streaming + Vorbis;短音效用 ADPCM;单声道化 |
| 代码 | 提高 Managed Stripping Level;移除无用插件 |
| Shader | 减少 multi_compile;剔除变体 |
| Resources | 清理不用的资源,迁移到 Addressables |
| 字体 | 中文字体只保留使用到的字符(TMP 静态字体集) |
十一、提升构建速度
| 方法 | 说明 |
|---|---|
| 保留 Library 文件夹 | 资源导入缓存都在这里,删除后需要重新导入,非常耗时 |
| 使用 Unity Accelerator | 团队共享资源导入缓存 |
| IL2CPP Code Generation | 选Faster (smaller) builds可加快构建 |
| 开发阶段用 Mono | 在 PC / Android 上快速迭代,发版时再切 IL2CPP |
| Patch / Patch And Run | Android 开发包仅推送改动的部分 |
| 减少 Shader 变体 | 往往是构建最慢的环节 |
| SSD + 足够内存 | IL2CPP 和 Shader 编译对 IO、CPU 要求很高 |
| 固定构建机平台 | 避免反复切换平台导致资源重新导入 |
十二、常见构建问题速查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
编辑器正常,真机报MissingMethodException | 代码被裁剪 | 添加link.xml或[Preserve] |
构建时报UnityEditor命名空间错误 | 编辑器代码被打进包 | 放进Editor文件夹或用#if UNITY_EDITOR包裹 |
| 真机材质粉红 | Shader 变体被剔除或未打包 | 检查 shader_feature、Always Included Shaders、ShaderVariantCollection |
| 首次出现特效时卡顿 | Shader 运行时编译 | 使用 ShaderVariantCollection 预热 |
| 包体突然变大 | Resources 新增资源、AB 重复依赖 | 查看 Build Report,使用 Addressables Analyze |
| IL2CPP 构建失败 | NDK 版本或路径问题、路径含中文 | 使用 Unity Hub 安装的 NDK;工程路径避免中文和空格 |
| 构建越来越慢 | Shader 变体增长、缓存失效 | 分析 Editor.log 中各阶段耗时 |
| 平台切换很久 | 资源需重新导入 | 使用-buildTarget参数或 Accelerator |
总结
代码 → 编译与裁剪(Mono / IL2CPP) 资源 → 依赖追踪 + 格式转换(纹理压缩、Shader 变体) 打包 → 平台工程(Gradle / Xcode) 控制 → 构建回调 + 构建脚本 + CI 自动化 优化 → Build Report 分析包体,Library 缓存与变体剔除提升速度