关掉域重载,编译快10倍的秘密

发布时间:2026/10/7 9:25:33
关掉域重载,编译快10倍的秘密
先说实话:提速 10 倍是营销数字。真实情况是——如果你的项目从没优化过,从 30 秒降到 3~5 秒是现实的;如果已经做过基本功,再挤出 30% 就算不错。更重要的是先搞清楚你慢在哪一步,因为这几种慢的原因和解法完全不同:① 改一行 C# 代码 → 等多久能继续? ← 本文重点(域重载 程序集) ② 打开 Unity 工程 → 等多久? ← 资源导入 / 库缓存 ③ 点 Play → 多久才进游戏? ← 域重载 ④ 点 Build → 多久出包? ← IL2CPP / 资源打包多数人说的编译慢是 ①③。下面按收益从大到小排。第 1 步:关掉 Domain Reload(收益最大)时间花在哪点 Play 之后,Unity 默认做两件事:Domain Reload 卸载并重新加载整个 .NET 域 → 所有 static 变量重置 → 所有脚本重新初始化 → 大项目要 5~20 秒 Scene Reload 重新加载当前场景 → 大场景也要几秒这两步大部分时候没必要。怎么关Edit → Project Settings → Editor → Enter Play Mode Settings ☑ Enter Play Mode Options ← 勾上这个总开关 ☐ Reload Domain ← 取消 ☐ Reload Scene ← 取消进 Play 的时间通常能从十几秒降到一秒内。这是单项收益最大的改动,没有别的选项能比。代价:你得自己管 static关掉域重载后,static变量在退出 Play 时不会自动归零,会带着上一次运行的值进入下一次。// ❌ 关掉域重载后会出错publicclassGameManager:MonoBehaviour{publicstaticGameManagerInstance;privatestaticint_score;// 第二次 Play 时还是上次的分数voidAwake(){Instancethis;}}正确做法:用RuntimeInitializeOnLoadMethod手动重置。publicclassGameManager:MonoBehaviour{publicstaticGameManagerInstance;privatestaticint_score;// 每次进入 Play 都会调用,无论域重载开不开[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]privatestaticvoidResetStatics(){Instancenull;_score0;}voidAwake(){Instancethis;}}还有一类坑是静态事件不会自己清空:publicstaticeventActionOnGameOver;// 关掉域重载后,上次 Play 注册的订阅者还挂着// → 第二次 Play 会触发已销毁对象的回调 → 空引用[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]privatestaticvoidReset()OnGameOvernull;实操建议:先关掉试一天。如果出现莫名其妙的 bug(第二次 Play 才出现、重启 Unity 就好),八成是 static 没重置。把它们找出来加上重置方法,比重新开启域重载划算得多。一个快速自检脚本:#ifUNITY_EDITORusingSystem.Reflection;usingUnityEditor;usingUnityEngine;publicstaticclassStaticFieldAuditor{[MenuItem(Tools/审查可疑的静态字段)]privatestaticvoidAudit(){varasmtypeof(StaticFieldAuditor).Assembly;foreach(vartypeinasm.GetTypes()){varfieldstype.GetFields(BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic);foreach(varfinfields){if(f.IsLiteral||f.IsInitOnly)continue;// const / readonly 安全Debug.Log($可变静态字段:{type.Name}.{f.Name}({f.FieldType.Name}));}}}}#endif跑一遍,人工过一下列表。const和readonly不用管,其他的确认是否需要重置。第 2 步:拆程序集(Assembly Definition)问题默认情况下,Assets下所有 C# 脚本编译进一个大程序集Assembly-CSharp.dll。改一行 UI 代码 ↓ 整个 Assembly-CSharp 重新编译 ↓ 3000 个脚本全部重编 → 等 20 秒解法用Assembly Definition(.asmdef)把代码切成模块。改哪个模块,只重编那个模块。Assets/ ├── Scripts/ │ ├── Core/ │ │ ├── MyGame.Core.asmdef │ │ └── ... │ ├── Gameplay/ │ │ ├── MyGame.Gameplay.asmdef 依赖 Core │ │ └── ... │ ├── UI/ │ │ ├── MyGame.UI.asmdef 依赖 Core │ │ └── ... │ └── Editor/ │ ├── MyGame.Editor.asmdef 依赖全部,平台限 Editor │ └── ...现在改 UI 代码,只重编MyGame.UI,几百毫秒搞定。创建方法右键文件夹 →Create → Assembly Definition,然后在 Inspector 里配依赖。一个 asmdef 文件长这样:{name:MyGame.UI,rootNamespace:MyGame.UI,references:[MyGame.Core,Unity.TextMeshPro],includePlatforms:[],excludePlatforms:[],allowUnsafeCode:false,autoReferenced:true,noEngineReferences:false}Editor 专用的程序集一定要限定平台,否则编辑器代码会被打进包里:{name:MyGame.Editor,references:[MyGame.Core,MyGame.Gameplay,MyGame.UI],includePlatforms:[Editor]}关键:依赖必须是单向的✅ 正确:分层,箭头单向 ┌──────────┐ │ Core │ ← 不依赖任何人 └────┬─────┘ ┌────────┴────────┐ ▼ ▼ ┌──────────┐ ┌──────────┐ │ Gameplay │ │ UI │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ ▼ ┌──────────┐ │ Editor │ └──────────┘ ❌ 错误:互相依赖 Gameplay ⇄ UI ← Unity 直接报错,循环依赖不允许如果 Gameplay 和 UI 必须互相通信,用事件或接口把方向掰直:// Core 里定义接口namespaceMyGame.Core{publicinterfaceIUIController{voidShowDamage(intamount);}}// Gameplay 只依赖 Core 的接口,不依赖 UInamespaceMyGame.Gameplay{publicclassPlayer{privatereadonlyIUIController_ui;publicPlayer(IUIControllerui)_uiui;publicvoidTakeDamage(intamount)_ui.ShowDamage(amount);}}// UI 实现接口,依赖 CorenamespaceMyGame.UI{publicclassHudController:MonoBehaviour,IUIController{publicvoidShowDamage(intamount){/* ... */}}}这样依赖变成Gameplay → Core ← UI,循环解开了,而且架构本身也更干净。拆多细合适太粗(1 个) → 没效果 太细(50 个) → 每个程序集有固定开销,反而变慢 → 依赖管理变麻烦 经验值:5 ~ 15 个,按功能模块切还有一条:把第三方插件也包进 asmdef。很多 Asset Store 插件没有 asmdef,代码直接散在Assets下,被编进主程序集,拖慢所有编译。给它们各自加一个 asmdef(插件不常改,几乎永不重编)。第 3 步:清理触发重编的元凶拆了程序集后,如果还是慢,查这几项。① Editor 代码混在运行时程序集里// ❌ 这个文件在 Assembly-CSharp 里usingUnityEditor;// 导致主程序集引用编辑器库publicclassWeapon:MonoBehaviour{#ifUNITY_EDITOR[MenuItem(Tools/Test)]staticvoidTest(){}#endif}#if UNITY_EDITOR能让它不进包,但编辑器下仍然参与编译。正确做法是把编辑器代码放进单独的Editor文件夹 独立 asmdef。② 全局 define 乱加Project Settings → Player → Scripting Define Symbols每加一个符号,所有程序集都要重编一次。而且某些插件会在这里塞一堆东西。定期清理用不上的。③ 自动生成代码的工具Protobuf、Odin、某些 DI 框架会在编译后生成代码,触发二次编译:你改代码 → 编译 → 工具生成代码 → 又触发编译 → 等两遍检查这类工具有没有仅手动生成的选项,平时关掉自动。④ 分析器(Analyzer)Roslyn Analyzer 和代码风格检查会显著拖慢编译。开发阶段可以关,只在 CI 上跑:// csc.rsp 放在 Assets 根目录-nowarn:0168⑤ 看一眼到底谁在拖后腿Unity 内置了编译耗时记录:Preferences → General → ☑ Compilation: Verbose Logging或者直接看编译报告文件:项目根目录/Library/ScriptAssemblies/ ← 编译产物 Editor.log ← 里面有每个程序集的编译时间Unity 2021 有更好的工具:Window → Analysis → Profiler → 切到 Editor 模式 或装 Compilation Visualizer(Package Manager 里搜)先测量再优化。很多人花时间拆程序集,结果发现慢的根源是某个插件的代码生成器。第 4 步:硬件与工程配置这一步经常被当成氪金解法忽略,但它是实打实的。磁盘是最大瓶颈Unity 的 Library/ 文件夹是海量小文件读写 机械硬盘 → 灾难,别用 SATA SSD → 可以 NVMe SSD → 明显更快 把工程放 NVMe 盘,这一项可能比前面所有软件优化加起来都明显内存16 GB 中型项目勉强,会频繁交换 32 GB 推荐起步 64 GB 大项目 / URPAddressables 舒服内存不够时系统会用磁盘交换,编译时间会变得非常不稳定(有时 5 秒有时 60 秒)。杀毒软件排除目录这一条收益经常被严重低估。实时扫描会拦截 Unity 对Library/的每一次读写。把下面这些目录加入排除列表:项目/Library/ 项目/Temp/ 项目/obj/ Unity 安装目录Windows Defender 的排除设置在:设置 → 隐私和安全性 → 病毒和威胁防护 → 管理设置 → 排除项有人做完这一步编译时间直接减半,尤其是公司统一装了企业级杀软的机器。关掉不用的平台模块Unity Hub → Installs → 齿轮 → Add Modules装了 Android、iOS、WebGL、Windows、Linux 五个平台,Unity 启动和某些操作会检查全部。只留你真用的。Asset 导入优化Preferences → Asset Pipeline ☑ Enable Asset Import Workers ← 并行导入 Desired Import Worker Count ← 设成 CPU 核数的一半到全部团队项目:Accelerator(共享缓存)多人协作时,每个人都在本地重新导入同样的资源,浪费大量时间。Unity Accelerator(原 Cache Server) 一台机器当缓存服务器 ↓ A 导入过的资源 → 上传到服务器 B 拉代码后 → 直接下载导入结果,不用本地重算拉一次大更新后的等待时间能从半小时降到几分钟。局域网里架一台就行,配置在:Preferences → Asset Pipeline → Cache Server → Remote排查顺序(照这个来)1. 测量:到底是 Play 慢、编译慢、还是导入慢? ↓ 2. 关 Domain Reload Scene Reload → 如果是点 Play 慢,这一步就解决大半 ↓ 3. 杀毒软件排除 Library/、Temp/ → 零成本,有时效果惊人 ↓ 4. 确认工程在 SSD 上,内存 ≥ 32GB ↓ 5. 装 Compilation Visualizer,看哪个程序集最慢 ↓ 6. 拆 Assembly Definition(含第三方插件) ↓ 7. 清理:Editor 代码隔离、关掉自动代码生成、精简 define ↓ 8. 团队项目加 Accelerator前三步是零成本高收益,务必先做。第 6 步工作量最大,但对长期开发值得。常见误区说法实际情况“升级 Unity 版本就快了”2021 之后改善明显,但不会自动解决你的架构问题“换 Mono 为 IL2CPP 能加快编译”反了,IL2CPP 打包更慢。它影响的是 Build,不是日常迭代“删掉 Library 文件夹能加速”只会让下次打开工程重新导入全部资源,更慢“脚本越少越快”关键是程序集划分,不是文件数量“CPU 核数越多越快”C# 编译确实能并行,但域重载和资源导入受磁盘限制更大“关掉 VS 的插件有用”影响 IDE 响应,不影响 Unity 编译要点1. 先分清:Play 慢 / 编译慢 / 导入慢 / 打包慢,四件事四种解法 2. 关 Domain Reload 是单项收益最大的改动 代价是要自己管 static,用 RuntimeInitializeOnLoadMethod 重置 3. Assembly Definition 拆 5~15 个,依赖必须单向 别忘了把第三方插件也包起来 4. 杀毒软件排除 Library/ —— 零成本,常被忽略 5. 工程放 NVMe,内存 32GB 起 6. 先测量(Compilation Visualizer),别凭感觉优化