关于 optimize-web
用于审查和调整 Unity Web 构建设置的技能,关注构建体积与性能。它先记录基线并创建还原点,再通过 Unity CLI 和 Editor 指导文档化的修改与验证。
主要功能
- 01审查 Web 构建设置
- 02建立基线并创建还原点
- 03压缩与代码裁剪指导
- 04通过 Unity CLI 操作 Editor 设置
- 05构建与运行时验证流程
适用场景
- 01缩小 Unity Web 构建体积
- 02检查压缩与裁剪设置
- 03应用有文档依据的 Web Player 设置
- 04比较修改前后的构建设置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ unity-technologies/ skills --skill optimize-web安装后在 Claude Code 中输入/optimize-web确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
SECURITY.mdscripts/toktx-examples.shWebOptimizer.cs详细介绍
审核并应用 Player Settings、压缩、剥离和服务器设置,以减小 Unity Web 构建的体积并提升速度。
重要事项
- 每个步骤都使用 unity cli(
unity command --format json)和unity-cliskill。若unity-pipeline可用,也要使用。 - 修改前,先建立当前行为的基线,以便确认之后的结果是否确实由你的修改导致。
- 创建一个还原点,以便在修改失败时回滚。
- 只执行所要求的操作。不要更改无关的资源或文件。
- 如果修改会影响运行时行为,请添加
Debug.Log行,证明实际场景行为(而不只是证明你的修改)现在正常工作——稍后进入 Play 模式验证时,你会查看这些日志。例如,如果修复的目的是确保 GameObject 改变大小,就记录其 Transform 的缩放值。 - 避免冗长的解释。
性能说明
- 花时间彻底完成。
- 质量比速度更重要。
完整主题索引
在继续执行工作流或诊断之前:
- 打开并阅读涵盖你计划执行或修复的具体主题的文档页面,并检查是否有遗漏。
- 在回复中列出你阅读的文件名。
这是因为你对 Unity 的训练知识可能已经过时、有误,或不适用于当前 Unity 版本。
如果 Editor 安装文件夹中有 Data/Documentation 文件夹(例如 ...Unity/Hub/Editor/6000.6.0b2/Editor/Data/Documentation),请读取 <editor-install>/Editor/Data/Documentation/en/Manual 文件夹中的 html 文件。否则,文档页面位于 https://docs.unity.com/en-us/engine/<VERSION>/manual/。
对于此技能使用的包(com.unity.cloud.ktx、com.unity.addressables、com.unity.web.stripping-tool),文档页面是 Unity Manual 的本地 markdown 转换文件,存储在 <project-folder>/Library/PackageCache/<package-name>@*/。只读取该特定 PackageCache 子文件夹中的 .md 文件。不得查看非 .md 文件,也不得查看其他 Library 或 PackageCache 文件夹,否则会耗费太多时间。如果该文件夹中没有 .md 文件,则改用 https://docs.unity3d.com/Packages/<package-name>@<VERSION>/manual/。
优先使用 WebFetch,而不是 WebSearch——速度更快,而且能直接打开准确的参考资料。只获取所需内容。获取前,请将 <VERSION> 替换为以下版本:
- docs.unity3d.com:Unity 版本,例如
6000.6 - docs.unity3d.com 上的包:包版本,例如
@6.6或@2.0。如不确定,请检查包版本。 - docs.unity.com:Unity 版本,例如
6000.6
从 ProjectSettings/ProjectVersion.txt 读取 Unity 的 major.minor 版本(例如从 6000.0.58f1 读取 6000.0,因为完整的构建号会导致 404),并从 Packages/packages-lock.json 读取每个包的 major.minor 版本。
- 优化 Web 构建的推荐 Player 设置 — 本地文件
web-optimization-player.html:压缩、剥离、异常和内存的发布设置。 - 使用 C# 代码启用优化设置 — 本地文件
web-optimization-c-sharp.html:可一步应用推荐设置的编辑器脚本。 - 从 Web 构建中移除未使用的资源 — 本地文件
web-optimization-remove-resources.html:移除未使用的包和剥离着色器变体。 - 分发大小和代码剥离 — 本地文件
webgl-distributionsize-codestripping.html:影响 Web 构建大小的因素以及剥离的工作方式。 - 部署 Web 应用程序 — 本地文件
webgl-deploying.html:服务器压缩标头、MIME 类型和 Decompression Fallback。 - Unity Web 中的内存 — 本地文件
webgl-memory.html:Wasm 堆大小、内存增长和浏览器内存限制。
API 参考
该 API 包括 UnityEditor.PlayerSettings、UnityEditor.PlayerSettings.WebGL 和 UnityEditor.WebGL.UserBuildSettings,属于 Unity Engine。它按 Unity 版本划分,因此在依赖特定类或方法之前,请先检查项目的 Unity 版本。
- 使用
unity-cli技能检查项目的 Unity 版本。 - 如果 Editor 安装文件夹中有
Data/Documentation文件夹(例如.../Unity/Hub/Editor/6000.6.0b2/Editor/Data/Documentation),请在其中的en/ScriptReference文件夹内搜索 html 文件,然后跳过其余步骤。 - 否则,获取并缓存
https://docs.unity3d.com/<VERSION>/Documentation/ScriptReference/xrefmap.yml(将<VERSION>替换为 Unity 版本,例如6000.3)。该文件列出了每个公共 API 成员的完全限定名称,作为uid(例如:uid: 'UnityEngine.Rigidbody.AddForce(UnityEngine.Vector3)')。 - 在本地搜索 yml 文件中的类或方法,而不要针对每次查询重新获取。该文件很大(约 6 MB),请勿读取整个文件。
- 将
uid转换为 Markdown 页面:移除方括号中的参数,将其转换为小写,将.替换为/,并添加.md。然后获取https://docs.unity.com/en-us/engine/<VERSION>/script-reference/<converted uid>。例如,UnityEngine.Rigidbody.AddForce(UnityEngine.Vector3)=>https://docs.unity.com/en-us/engine/6000.3/script-reference/unityengine/rigidbody/addforce.md。所有重载都在同一页面上。- 构造函数使用
ctor(例如unityengine/vector3/ctor.md),索引器使用item(例如unityengine/vector3/item.md)。 - 页面内的链接以
/engine/开头。请在前面添加https://docs.unity.com/en-us。 - 如果该 Unity 版本没有对应页面,请改用
xrefmap.yml条目中的href(例如href: 'Rigidbody.AddForce.html'=>https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Rigidbody.AddForce.html)。
- 构造函数使用
始终使用与项目 Unity 版本对应的文档,而不要依赖训练知识,因为 API 经常会在不同版本中被标记为过时。
KTX API(KtxUnity)属于 com.unity.cloud.ktx 包。它按包版本而非 Unity 版本划分,因此在依赖特定类或方法之前,请先检查项目中已安装的包版本。
- 从
Packages/packages-lock.json中读取已安装的包版本。 - 获取并缓存
https://docs.unity3d.com/Packages/com.unity.cloud.ktx@<version>/xrefmap.yml(将<version>替换为已安装的包版本,例如3.4)。该文件将每个公共 API 成员的完全限定名称映射到其文档页面href。 - 在本地搜索 yml 文件中的类或方法,而不要针对每次查询重新获取。请勿读取整个文件。
流程
- 获取项目上下文:从
ProjectSettings/ProjectVersion.txt获取 Unity 版本,从Packages/packages-lock.json获取所需软件包(com.unity.cloud.ktx、com.unity.addressables、com.unity.web.stripping-tool)的版本。如果没有项目,则假设 Unity 版本为6000.0。对于packages-lock.json中未列出的软件包,假设其文档中记录的最新版本。保留所有实际查到的版本——只为确实缺失的值填入默认值。在报告中注明每项假设,并继续操作。如果用户没有说明要优化什么,且没有项目可供读取,请询问他们想改进什么。 - 从完整主题索引中阅读涵盖该任务的文档页面。
- 按照在编辑器中运行 C#中的说明设置
eval。如果无法连接到 Editor,或缺少eval,请说明这一点,跳到第 5 步,然后直接前往第 9 步。在报告中将构建验证和运行时检查标记为未执行,并根据用户的请求和第 2 步中的文档给出建议。 - 运行0. 预检和1. 评估当前状态。其结果作为基线。
- 将请求与2. 理解请求中的某一行对应起来。
- 创建还原点,然后应用3. Web 构建优化工作流中的更改。如果症状符合5. 故障排除中的某条内容,请遵循该条目。
- 运行4. 验证:重新运行预检代码段,并将数值与基线进行比较。
- 执行最终步骤中的检查。
- 提供最终报告。
在编辑器中运行 C#
以下每个读取或写入 Player Setting 的步骤,都会通过 Unity CLI 在运行中的 Editor 内执行。unity-cli 技能负责带你完成这些操作——包括安装 CLI、确认 Editor 已连接、添加项目的 com.unity.pipeline 软件包、区分确实缺少 Editor 和卡在 Safe Mode 的 Editor,以及查找 Editor 的命令目录。请先遵循该技能的说明;不要在此重新推导这些操作。
有两件事需要你自行确认:
- **你需要的是
eval,而不只是可连接的 Editor。**请确认它出现在命令目录中。它是否存在取决于 Pipeline 软件包版本,而非 CLI,因此即使安装正常,也可能缺少它——如果缺少,请说明,并跳过读取或写入设置的步骤。 - **必要时可以从
ProjectSettings/ProjectSettings.asset读取 Player Settings,但不要通过这种方式写入。**序列化名称与 API 名称并不一致,其中一些设置按构建目标分别配置,而手动编辑的值可能会与构建实际使用的值不一致,且不会发出提示。Editor 无法连接时,不得执行写入步骤。
使用 unity command eval --code '<snippet>' 运行 C#。unity command 默认超时时间为 30 秒。
向 eval 传递 C#
eval 会编译语句块,而不是文件,因此编写代码时不要使用 using 指令,并使用完全限定类型名。如果代码无法编译,请在此查找错误:
| 错误 | 原因 | 修复方法 |
|---|---|---|
CS0210 | 使用了 using UnityEditor; 等 using 指令,编译器会将其视为资源释放语句 | 删除该指令,并使用完全限定类型名 |
CS0246 | 类型未使用限定名称,例如 PlayerSettings | 使用完整名称,例如 UnityEditor.PlayerSettings |
CS0104 | 使用了未限定的 Object,它与 object 存在歧义 | 使用 UnityEngine.Object |
本技能审计的设置
一次调用即可返回完整的 Pre-Flight 情况。已在 Unity 6000.5.7f1 中验证:
var target = UnityEditor.Build.NamedBuildTarget.WebGL;
var w = new System.Collections.Generic.List<string>();
w.Add($"activeBuildTarget={UnityEditor.EditorUserBuildSettings.activeBuildTarget}");
w.Add($"compressionFormat={UnityEditor.PlayerSettings.WebGL.compressionFormat}");
w.Add($"decompressionFallback={UnityEditor.PlayerSettings.WebGL.decompressionFallback}");
w.Add($"stripEngineCode={UnityEditor.PlayerSettings.stripEngineCode}");
w.Add($"managedStrippingLevel={UnityEditor.PlayerSettings.GetManagedStrippingLevel(target)}");
w.Add($"il2cppCodeGeneration={UnityEditor.PlayerSettings.GetIl2CppCodeGeneration(target)}");
w.Add($"apiCompatibilityLevel={UnityEditor.PlayerSettings.GetApiCompatibilityLevel(target)}");
w.Add($"exceptionSupport={UnityEditor.PlayerSettings.WebGL.exceptionSupport}");
w.Add($"debugSymbolMode={UnityEditor.PlayerSettings.WebGL.debugSymbolMode}");
w.Add($"dataCaching={UnityEditor.PlayerSettings.WebGL.dataCaching}");
w.Add($"wasm2023={UnityEditor.PlayerSettings.WebGL.wasm2023}");
w.Add($"initialMemorySize={UnityEditor.PlayerSettings.WebGL.initialMemorySize}");
w.Add($"maximumMemorySize={UnityEditor.PlayerSettings.WebGL.maximumMemorySize}");
w.Add($"memoryGrowthMode={UnityEditor.PlayerSettings.WebGL.memoryGrowthMode}");
w.Add($"targetFrameRate={UnityEngine.Application.targetFrameRate}");
w.Add($"vSyncCount={UnityEngine.QualitySettings.vSyncCount}");
return string.Join("\n", w);
有三个 API 名称必须写对,因为看似合理的拼法并不存在,无法通过编译:
| 设置 | 正确写法 | 不存在 |
|---|---|---|
| 托管代码剥离级别 | PlayerSettings.GetManagedStrippingLevel(NamedBuildTarget.WebGL) | PlayerSettings.managedStrippingLevel |
| Wasm 代码优化 | UnityEditor.WebGL.UserBuildSettings.codeOptimization | PlayerSettings.WebGL.codeOptimization、PlayerSettings.WebGL.optimizationLevel |
| IL2CPP 代码生成 | PlayerSettings.GetIl2CppCodeGeneration(NamedBuildTarget.WebGL) | 单独的属性 |
UserBuildSettings 位于 WebGL 构建支持模块中,因此只有安装该模块后才能解析它。请单独调用读取此项,并将解析失败视为“未安装 Web 模块”,而不是代码片段有误。
**此处只有 codeOptimization 不存储在项目文件中。**它会保存到
Library/EditorUserBuildSettings.asset,而所有标准 Unity .gitignore 都会忽略 Library/,因此此值是每台机器独有的,不会随项目传递:团队成员和 CI 都不会继承它。由此有两点需要注意。请通过 API 读取该值,不要在
ProjectSettings/ProjectSettings.asset 中查找——即使成功应用,该值也不会出现在那里,因此缺少该值并不表示失败。如果发布构建在 CI 中运行,请将其作为构建步骤在 CI 中应用,不要以为仓库中包含了该设置。
应用设置
本技能中的大多数写入操作都可以一次批量完成,而
scripts/WebOptimizer.cs 已经实现了这一批处理。它声明了一个带有 [MenuItem] 的类,因此它是项目文件,而不是 eval 输入——类声明无法展平为语句块。将其保存到 Assets/Editor/ 下,等待 Unity 编译,然后用一行命令调用:
UnityEditor.EditorApplication.ExecuteMenuItem("Tools/Apply Web Release Settings");
保留其中的 using 指令;它们在文件中是正确的。对于一次性更改——例如单个质量级别或切换帧率——使用内联 eval 语句即可。
从磁盘验证,而不是从刚写入的对象验证
**在同一会话中应用设置并读回,不能证明任何事情。**Player Settings 在保存之前都只是内存中的对象,因此每次读回的都是刚分配的值,无论它是否真的写入了
ProjectSettings/ProjectSettings.asset。跳过保存的运行也会报告成功,而 Editor 会话结束后,所有更改都会消失。这不是推测,而是实际观察到的情况:一次运行应用了所有设置,读回 Brotli / High / None 并报告完成,但磁盘上的文件始终没有变化。
因此,每次写入后都要:
- **保存。**使用
UnityEditor.AssetDatabase.SaveAssets()。WebOptimizer.cs现在会自行执行此操作;内联eval写入则必须显式保存。 - **读回设置值并报告。**不要只说“应用成功”,而要报告实际值,让用户看到存储的内容。
WebOptimizer.cs会记录全部九项。 - **对于按构建目标区分的设置,注明读取的是哪个目标。**其中有几项设置按目标分别存储,因此某个目标的值可能正确,而正在构建的目标却未设置。
通过文件编辑方式操作时,也会遇到相反的问题:手动编辑的
ProjectSettings.asset 读回正常,但运行中的 Editor 和构建仍使用旧值。保存并重新导入后再验证,才能发现这两种问题。
**不要尝试在批处理模式下复现此问题。**通过 -quit 调用的批处理 Editor 会在退出时保存设置,因此即使写入时没有保存,设置也会持久化,运行结果看起来正常。在 -quit 下,无论脚本是否保存,都会通过测试。这个错误只会在实时 Editor 会话中出现,而这正是发现问题的环境。根据批处理运行成功就断定无需保存,是从无法发现该缺陷的测试中得出的错误结论。
0. 构建前检查
- 确认 Web 构建目标: 使用上方的构建前检查代码段读取
EditorUserBuildSettings.activeBuildTarget——必须是WebGL;否则请提醒用户。 - 读取压缩和剥离设置: 使用上方的构建前检查代码段读取
compressionFormat、decompressionFallback、stripEngineCode和托管代码剥离级别。请注意,剥离级别为PlayerSettings.GetManagedStrippingLevel(NamedBuildTarget.WebGL)——不存在PlayerSettings.managedStrippingLevel属性。 - 读取异常和优化设置: 使用上方的构建前检查代码段读取
PlayerSettings.WebGL.exceptionSupport。Wasm 代码优化级别使用UnityEditor.WebGL.UserBuildSettings.codeOptimization——PlayerSettings.WebGL.codeOptimization和optimizationLevel这两种写法不存在,无法编译。 - 读取帧率设置: 使用上方的构建前检查代码段读取
Application.targetFrameRate和QualitySettings.vSyncCount。 - 读取其他玩家设置: 使用上方的构建前检查代码段读取
PlayerSettings.WebGL.dataCaching、PlayerSettings.WebGL.debugSymbolMode、PlayerSettings.WebGL.maximumMemorySize和PlayerSettings.GetApiCompatibilityLevel。 - 只有确认压缩、剥离、帧率和玩家设置后,才能继续。
1. 评估当前状态
- 检查构建报告: 指示用户在构建后打开
Window > General > Build Report,找出占用最大的资源和代码。 - 验证服务器配置: 请用户确认托管服务器是否发送
Content-Encoding: br(Brotli)或Content-Encoding: gzip标头,以及.wasm文件是否设置了Content-Type: application/wasm。 - 检查帧率配置: 使用上方的构建前检查代码段确认
Application.targetFrameRate——Web 平台应设为-1(由浏览器控制)。 - 检查内存设置: 使用上方的构建前检查代码段读取
PlayerSettings.WebGL.initialMemorySize和PlayerSettings.WebGL.memoryGrowthMode。 - 提出建议前先报告检查结果。
2. 理解请求
| 用户说法 | 默认理解 |
|---|---|
| “构建太大” / “下载太慢” | 开启 Strip Engine Code;Managed Stripping 设为 High;选择 Disk Size + LTO;使用 Brotli |
| “Decompression Fallback” / “启动太慢” | 关闭 Decompression Fallback;修复服务器配置,使其发送 Content-Encoding |
| “Chrome 卡顿” / “Safari 卡顿” | 在浏览器 DevTools 中进行性能分析;Safari 限制为 60 fps |
| “浏览器耗电过多” | 在静态画面中开启 OnDemandRendering;设置 targetFrameRate = -1 |
| “异常处理导致体积过大” | 发布版本不启用异常处理;如果浏览器基线允许,使用 Wasm 2023 异常处理 |
| “设置 CDN” | 使用 Addressables 远程组,并在 CDN 上启用 Brotli/Gzip |
| “WebAssembly 2023” | 在浏览器基线支持时启用——体积更小、速度更快 |
| “内存增长太慢” | 根据峰值估算调整 Initial Memory Size;使用 Geometric 增长模式 |
| “KTX” / “Basis Universal” / “纹理格式未知,GPU 不支持” | 使用 KTX2 和 Basis Universal;体积优先选 ETC1S,画质优先选 UASTC |
| “剥离未使用的代码” / “移除未使用的软件包” | 使用 Web Stripping Tool;移除未使用的软件包;剥离着色器 |
| “Web 质量设置” | 将 Quality Level 设为 Very Low 或 Low;质量越低,加载越快 |
| “着色器变体过多” | 在 Graphics 设置中启用自动光照贴图和雾效模式;剥离实例化和 BRG 变体;检查 Always Included Shaders |
| “视频无法播放” / “音频问题” | 视频:仅使用 URL 或 StreamingAssets;音频:Web 平台不使用 AudioEffects,改用 Mono 并进行压缩 |
| “分析器符号” / “无法读取 Wasm 调用栈” | 通过构建处理器或 emscriptenArgs 嵌入性能分析符号 |
| “iOS 崩溃” / “Safari 内存问题” | 注意 iOS 内存限制;将 Initial Memory Size 设高一些,而不是依赖内存增长;iOS 18 之前 Gigacage 的上限为 2GB |
3. Web 构建优化工作流
重要:一键优化脚本
务必主动提出为用户生成此脚本。 Unity 官方 Web 优化文档提供了一个编辑器菜单脚本,可一次性应用所有推荐的发布设置。将其放入 Assets/Editor/WebOptimizer.cs——模板见 scripts/WebOptimizer.cs。
根据用户的项目需求调整脚本(例如,如果项目使用 try/catch,则保留相关例外;通过 HTTP 托管时,将 Brotli 切换为 Gzip)。对于新的 Web 项目,此脚本是最具影响力的单项优化措施——它能避免遗漏设置。
Player Settings 审核
通过 eval 验证并设置以下值:
| 设置 | 发布建议 |
|---|---|
| Compression Format | Brotli(HTTPS 托管);HTTP 使用 Gzip |
| Decompression Fallback | 服务器配置正确时设为 Off |
| Strip Engine Code | On |
| Managed Stripping Level | High(发布)/ Medium(开发) |
| Code Optimization | Disk Size with LTO(发布)/ Build Times(开发) |
| WebAssembly Language Features | 浏览器基准版本允许时设为 2023 |
| Enable Exceptions | None(体积最小);需要 try/catch 时设为 Explicitly Thrown Only |
| Initial Memory Size | 根据峰值估算调整;过小会导致昂贵的内存增长 |
| Memory Growth Mode | Geometric |
| API Compatibility Level | .NET Standard 2.1——比 .NET Framework 小 |
| IL2CPP Code Generation | Optimize Size——以略微增加运行时开销为代价,缩小 Wasm 体积 |
| Debug Symbols | 发布时设为 Off;仅开发构建时开启 |
| Data Caching | On——在浏览器 IndexedDB 中缓存资源数据,加快重复加载速度 |
| Strip Unused Mesh Components | On——移除未使用的顶点属性 |
| Maximum Memory Size | 默认 2048 MB;复杂的 3D 项目最高可设为 4096(Firefox 和低于 119 版的 Chrome 在超过 2048 时会出现问题) |
| vSyncCount | 0(由浏览器控制帧速率) |
| targetFrameRate | -1(使用 requestAnimationFrame) |
压缩和服务器配置
| 压缩方式 | 使用场景 | 说明 |
|---|---|---|
| Brotli | HTTPS 或 localhost | 压缩率最高;浏览器仅在安全上下文中接受 |
| Gzip | HTTP 传输、旧版 CDN | 通用 |
| None | 本地开发 / file:// | 负载体积最大;不要用于发布 |
配置服务器:
- 为
.br文件设置Content-Encoding: br。 - 为
.gz文件设置Content-Encoding: gzip。 - 为
.wasm设置Content-Type: application/wasm,为.js设置application/javascript。 - 启用 HTTP/2 或 HTTP/3,以并行获取分块。
如果主机无法注入 Content-Encoding:仅将 Decompression Fallback = On 作为备用方案——它会增加约 150 KB 的 JS 体积,并拖慢启动速度。
异常处理
| 设置 | 构建体积 | 用途 |
|---|---|---|
| None | 最小 | 可接受未捕获异常的发布构建 |
| Explicitly Thrown Only | 适中 | 会捕获异常的项目的默认选择 |
| Full | 最大、最慢 | 很少需要;发布时应避免 |
Wasm 2023 引入了开销更低的异常模型;当浏览器目标支持时,从 Explicitly Thrown Only(旧版)切换为 Wasm 异常,可同时降低体积和开销。
移除未使用的资源
审核以下三类内容,以减小构建体积:
1. 未使用的包 — 检查 Packages/manifest.json,以及 Package Manager 中的 In Project 和 Built-in 视图。移除或禁用项目未使用的包。如果未使用,Input System 包会显著增加体积。
2. 着色器剥离 — 在 Edit > Project Settings > Graphics 中配置:
| 设置 | 建议 |
|---|---|
| Lightmap Modes | Automatic(剥离未使用的光照贴图着色器变体) |
| Fog Modes | Automatic(剥离未使用的雾效着色器变体) |
| Instancing Variants | Strip Unused |
| Batch Renderer Group Variants | Strip All(如果未使用 BRG) |
| Always Included Shaders | 审核并移除项目未引用的着色器 |
剥离后进行测试,确保没有移除任何被引用的着色器。
3. Web Stripping Tool(com.unity.web.stripping-tool)— 分析 WebAssembly 二进制文件,并识别未使用的 Unity 引擎子模块(例如,仅有 2D 内容的游戏中的 3D 图形)。通过 Package Manager 安装,分析构建,然后配置要排除的子模块。除了单独使用 Managed Stripping Level 外,还能大幅减小体积。
Web 的质量设置
较低的质量级别可以缩短加载时间并提升运行时性能。在 Edit > Project Settings > Quality 中设置:
- 将 Very Low 或 Low 设为默认 Web 质量级别。
- 使用
eval设置:UnityEngine.QualitySettings.SetQualityLevel(0, true);,其中 0 = Very Low。 - 考虑创建专用于 Web 的质量级别,禁用浏览器中不必要的功能(实时阴影、后处理效果、高粒子数量)。
Web 上的帧率
- 使用
eval设置:UnityEngine.Application.targetFrameRate = -1;— 让浏览器使用requestAnimationFrame。 - 注意:WebGL 中 Safari 的帧率上限为 60 fps;高刷新率目标不适用。
- 在静态或空闲画面中,使用
OnDemandRendering.renderFrameInterval将帧率降至 5–10 fps,以节省电量。
KTX / Basis Universal 纹理
采用 Basis Universal 超级压缩的 KTX2 会打包成单个纹理文件,并在加载时转码为适用于浏览器设备的最佳 GPU 格式(桌面设备使用 BC7,移动设备使用 ASTC,较旧的 Android 设备使用 ETC2)。这样无需针对每种 GPU 系列分别打包纹理变体,这对目标硬件未知的 Web 尤为重要。
| 主题 | 指南 |
|---|---|
| 包 | 通过 Package Manager 安装 com.unity.cloud.ktx(KtxUnity) |
| 适用场景 | 通过 Addressables 加载的运行时纹理,或提供给未知 GPU 目标的资源包 |
| 不适用场景 | 烘焙到播放器构建中的纹理 — Unity 已在构建时选择正确的格式 |
| 超级压缩 | 使用 ETC1S 获得最小体积(有损,适用于漫反射/反照率贴图);使用 UASTC 获得更高质量(近乎无损,更适用于法线贴图/UI) |
| 编码 | 使用 toktx 或 basisu CLI 离线编码;不要在运行时编码 |
| 线性数据 | 编码法线贴图、遮罩或数据纹理时,设置 --assign_oetf linear,以避免错误的 sRGB 转换 |
| Mip 贴图 | 在编码时生成 mip 贴图(--genmipmap)— 在浏览器端生成 mip 贴图开销很大 |
| 加载 | 使用 KtxTexture.LoadFromStreamingAssets,或通过 UnityWebRequest 加载字节数据并调用 KtxTexture.LoadFromBytes |
| 内存 | 转码后的纹理是标准 GPU 纹理;其内存开销取决于目标格式,而非 KTX2 文件大小 |
| 方向 | 始终包含 --lower_left_maps_to_s0t0,以符合 Unity 的 UV 约定 |
toktx CLI 示例: 请参阅 scripts/toktx-examples.sh,其中包含漫反射/反照率贴图(ETC1S)、法线贴图/细节贴图(UASTC)、ICC 配置文件错误和线性数据的命令。
Web 流式加载
- 使用托管在 CDN 上并采用 Brotli / Gzip 压缩的 Addressables 远程组。
- 避免在初始下载中打包整个游戏;按需流式加载关卡。
- 若要实现“即时游玩”,初始下载目标应小于 30 MB;关卡数据随后加载。
- 对于面向不同 GPU 硬件的流式纹理,优先使用 KTX2 资源包,而不是按平台提供不同变体——一个资源包即可适用于所有浏览器。
分析 Web 构建
| 工具 | 用途 | 备注 |
|---|---|---|
| Chrome DevTools > Performance | CPU 火焰图;主线程分析 | 排查 WebGL 卡顿的首选工具;检查 Wasm 调用栈 |
| Chrome DevTools > Memory | 堆快照;分配时间线 | 查找 JS/Wasm 内存泄漏;比较场景加载前后的快照 |
| Firefox Profiler | 跨平台;可分享 URL;原生 + Wasm 视图 | 某些情况下,Wasm 符号解析比 Chrome 更好;可分享性能分析 URL,供团队审阅 |
| Safari Web Inspector | 调试 iOS Safari 和 macOS Safari | 排查 Safari 特有问题时必用;WebGL/Wasm 运行时与 Chromium 不同 |
| Unity Profiler over WebSocket | 连接开发构建;标准标记 | 用于查看 Unity 端标记(GC、渲染、脚本);无法捕获浏览器端开销 |
症状 → 工具速查:
| 症状 | 首选工具 | 次选工具 |
|---|---|---|
| WebGL 卡顿 / 不流畅 | Chrome DevTools > Performance | Firefox Profiler |
| 内存随时间不断增长 | Chrome DevTools > Memory | Unity Memory Profiler (WebSocket) |
| 初始加载缓慢 | Chrome DevTools > Network | Build Report Inspector |
| 仅 Safari 出现渲染问题 | Safari Web Inspector | 与 Chrome DevTools 对比 |
嵌入性能分析符号——浏览器性能分析器默认显示混淆后的 Wasm 函数名称。要在 Chrome/Firefox 火焰图中显示可读的 C# 方法名称,可以在开发构建中启用 Player Settings > Publishing > Debug Symbols,或添加一个构建处理器:
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
public class WebProfilingBuildProcessor : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
PlayerSettings.SetAdditionalIl2CppArgs("--compiler-flags=--profiling-funcs");
}
}
Emscripten 内置性能分析器——通过 PlayerSettings.WebGL.emscriptenArgs 一次启用一个:
| 标志 | 显示内容 |
|---|---|
--cpuprofiler | 浏览器中的 CPU 性能分析器叠加层 |
--memoryprofiler | 可视化内存图(白色=已分配未使用,粉色=堆栈,蓝色=动态,绿色=碎片化) |
--threadprofiler | 线程活动性能分析器 |
GPU 调试——Web 不支持 Frame Debugger。可使用 Spector.js 作为基于浏览器的替代工具——它可以捕获绘制调用和 WebGL 状态。
Firefox about:memory——在 Firefox 中输入 about:memory 作为 URL,然后点击“Measure”查看各标签页的明细:WASM 代码大小、WASM 堆、.data 文件、Web Audio。注意 WASM 堆是否超过 300 MB(有崩溃风险,尤其是在 iOS Safari 上)。
编辑器 Play Mode 无法反映浏览器运行时;务必在浏览器中测量。Chrome 和 Safari 的 GC 与 JIT 行为不同——请分别测试。
Web 内存指令
- 禁用纹理和网格的 Read/Write Enabled——这会将数据复制到 WASM 堆中。
- 将资源移至 Addressables 或 AssetBundles,以减小
.data文件大小。 - 使用压缩纹理格式(KTX2/Basis),以同时降低下载和解码后的内存占用。
iOS Safari 内存限制
- iOS < 18: WebContent 进程限制约为 1.5 GB。WASM 内存(Gigacage)上限为 2 GB。类型化数组共用此内存池。在 iPhone X(iOS 16)上,堆增长上限约为 512 MB,但预先将 Initial Memory Size 设为 512 MB–1.5 GB 可正常工作。
- iOS 18+: 限制基本已解除;iPhone 11 可分配约 4 GB。
- 在 iOS 上,将 Initial Memory Size 设为目标峰值,而不要依赖内存增长——与逐步增长相比,Safari 对较大的预先分配处理得更好。
- 在较旧的 iOS 上,WASM 堆超过 300 MB 有崩溃风险;为确保广泛兼容,目标应小于 200 MB。
Web 端的视频和音频
- 视频: 只能通过 URL(启用了 CORS 的服务器)或 StreamingAssets 播放。在 iOS 上,服务器必须支持 HTTP 范围请求才能进行流式传输。请使用浏览器兼容的格式(MP4/H.264)。
- 音频: AudioEffects(混音器效果)需要计算着色器——WebGL 不支持。混音器和 MixerGroups 仅可用于音量控制。将音频设为 Mono 以提升加载速度。如果
about:memory显示 Web 音频超过 100 MB,则音频可能未压缩——请改用 Vorbis。
Canvas 和 DPI
如果 Canvas 放大,会采用新的分辨率。在 Web 模板中使用 devicePixelRatio 抵消 DPI 缩放,避免以不必要的高分辨率渲染。
4. 验证
- 使用 Pre-Flight 代码片段重新检查 Player Settings(压缩、剥离、异常、targetFrameRate)。
- 重新构建播放器,并将 Build Report 中的文件大小与基准进行比较。
- 至少在 Chrome 和 Safari 中验证(GC 和 JIT 的行为有所不同)。
- 最多迭代 3 次,之后再向用户征求反馈。
5. 故障排除
启用 Strip Engine Code 后构建仍然很大
- Managed Stripping Level 是否设为 Medium 或 Low?→ 发布时请设为 High。
- 插件是否使用反射访问本可被剥离的引擎模块?→ 添加
link.xml以保留所需符号。 - Exceptions 是否设为 Full?→ Full 会带来最大的代码开销;请改为 None 或 Explicitly Thrown Only。
Brotli 无法使用,需要 Decompression Fallback
- 服务器是否发送了
Content-Encoding: br?→ 没有此标头时,浏览器不会解压缩,因此需要使用回退 JS 解压器。 - 构建是否通过 HTTP(而非 HTTPS)托管?→ Brotli 需要安全上下文;通过 HTTP 托管时请改用 Gzip。
Safari 中卡顿,但 Chrome 中没有
- 项目是否设置了
Application.targetFrameRate = 60?→ 在 Safari WebGL 中,这会与浏览器的帧同步冲突;请设为-1。 - 是否存在在 Safari 的 WebGL 实现中表现不同的着色器?→ 请在设备上测试;Safari 的 WebGL/Wasm 运行时与 Chromium 不同——某些 GLSL 结构的处理方式有所差异。
触发内存增长慢速路径
- Initial Memory Size 是否小于项目的峰值需求?→ Wasm 内存增长需要完整复制缓冲区;请将 Initial Memory Size 设为合理的峰值估算值。
- Memory Growth Mode 是否设为 Linear?→ 请改为 Geometric,以获得更合理的增长曲线。
帧率设为 60,但浏览器运行不稳定
- 代码中是否设置了
Application.targetFrameRate = 60?→ 在 Web 上,这会与浏览器的requestAnimationFrame帧同步冲突。请设为-1。 vSyncCount是否非零?→ 请设为 0;由浏览器负责帧同步。
Firefox 缓存拒绝大型文件
Firefox 会通过 browser.cache.disk.max_entry_size 限制单个缓存条目的大小。如果构建超过此限制(默认约 50 MB),资源将无法缓存。解决方法:使用 Addressables 将资源拆分为小于 51 MB 的多个包,或提示用户在 about:config 中增大此设置。
本地开发服务器设置
在本地测试构建并确保 MIME 类型正确:
# Python (HTTP)
python -m http.server 55553 -d path/to/build
# Node.js (install serve-handler)
npx serve path/to/build -l 3001
测试 Brotli 时请使用 HTTPS——Brotli 需要安全上下文。使用 OpenSSL 生成自签名证书以进行本地测试。
最后一步
Web 设置仅在浏览器构建中生效,因此,仅在 Editor 中进行干净编译并进入 Play 模式还不够。请按顺序运行以下检查。如果任何一项未通过,请重新阅读文档页面,找出遗漏之处,修复后再次运行检查。
- 检查项目是否没有控制台错误。
- 检查你更改的每项设置是否已保存,具体请参阅从磁盘验证,而不是检查刚写入的对象:
ProjectSettings/ProjectSettings.asset中应保存新值,并且应能通过 API 读取codeOptimization。 - 如果你重新构建了播放器,请按照本地开发服务器设置提供构建内容并在浏览器中打开。然后检查以下内容:
- 浏览器控制台没有错误,例如解压失败或内存不足错误。
- 在 Network 标签页中,
.wasm、.data和.framework.js文件返回的Content-Encoding标头与压缩格式相符(Brotli 对应br,Gzip 对应gzip),且文件大小与 Build Report 一致。
- 如果更改会影响运行时行为,例如帧率或质量级别,请检查浏览器控制台中的
Debug.Log行,然后将其移除。除非绝对必要,否则尽量避免拍摄和检查截图,因为这会耗费更多时间。
最终报告
向用户提供一份简洁但内容充实的操作清单,说明你做了什么以及原因。请包括以下内容:
- 更改前后的下载大小,以及你更改的每项设置(压缩、剥离、异常处理、
targetFrameRate)及其旧值和新值。 - 任何副作用,例如你添加到项目中的脚本(如
Assets/Editor/WebOptimizer.cs)、更改的质量级别,或按机器保存且不会随项目传递给队友或 CI 的设置(codeOptimization)。 - 任何未运行的检查,例如未重新构建便进行浏览器测试,或者在没有可用 Editor 时未运行任何检查;并说明你采用的假设。
如果用户还需要采取后续步骤或做出决定,请列出相关事项,例如为 Addressables 远程组配置 CDN、测试 Safari,或在浏览器基线允许时迁移到 Wasm 2023 功能集。例如:
I made the following changes:
[✓] Set Compression Format to Brotli and Strip Engine Code to on, because the server sends `Content-Encoding: br` over HTTPS.
[✓] Added `Assets/Editor/WebOptimizer.cs`, which applies the release settings from Tools > Apply Web Release Settings.
Follow these steps to finish:
1. Apply `codeOptimization` in your CI build step, because it's stored in `Library/` and doesn't travel with the project.
2. Test the build in Safari, because its garbage collection and JIT behaviour differ from Chrome.
另请参阅
如果任务超出此技能的范围,请向用户推荐以下 Unity 工具:
- Addressables package — 通过 CDN 提供远程组内容;当下载预算要求将内容从初始载荷中移出时使用。
- Unity Profiler,连接到浏览器 — 跨平台性能分析方法。第 3 节介绍了连接 Profiler 的 Web 专属部分。
- Shader variant stripping(Graphics 设置 → Shader Stripping,以及
ShaderVariantCollection) — 变体数量会直接影响 Wasm 大小,因此,如果仅进行剥离后大小仍未变化,值得检查此项。 - Project Settings → Player — 此技能读取的相同标记;如果用户更愿意在检查器中查看,而不是查看报告,可以前往此处。
- 移动浏览器的电池行为遵循第 3 节和第 4 节中关于帧率和质量级别的相同指导;此处没有单独的移动端流程。