关于 optimize-web
面向 Unity 开发的技能,用于审查和调整影响 Web 构建体积与性能的设置。它依据项目版本查阅文档,并在应用限定范围的更改前建立基线和还原点。
主要功能
- 01审查 Web Player 设置
- 02调整压缩与代码剥离
- 03检查未使用资源和着色器变体
- 04检查部署与内存设置
- 05根据基线验证更改
适用场景
- 01缩减 Unity Web 构建体积
- 02审查压缩和代码剥离设置
- 03检查 Web 构建的服务器要求
- 04诊断 Web 构建内存限制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ unity-technologies/ unity-agent-plugin --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#中的说明设置
script eval。如果无法连接任何 Editor,或缺少script 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 和 Editor 卡在 Safe Mode 的情况,以及查找 Editor 的命令目录。请先遵循该技能;不要在此重新推导这些操作。
有两点它无法替你判断:
- **你需要的特定功能是
script eval,不能只满足于 Editor 可连接。**请确认它出现在命令目录中。它是否存在取决于 Pipeline 包的版本,而非 CLI,因此即使安装正常,也可能缺少该功能——如果缺少,请说明这一点,并跳过读取或写入设置的步骤。 - **紧急情况下,你可以从
ProjectSettings/ProjectSettings.asset读取 Player Settings,但不要以这种方式写入。**序列化名称与 API 名称不匹配,其中一些设置按构建目标分别配置,而且手动编辑的值可能会悄无声息地与构建实际使用的值不一致。Editor 无法连接时,必须停止写入步骤。
使用 unity command script eval --caller plugin --skill optimize-web --code '<snippet>' 运行 C#。unity command 默认超时时间为 30 秒。
向 script eval 传递 C#
script eval 编译的是语句块,而非文件,因此编写代码时不要使用 using 指令,并使用完全限定类型名。如果代码无法编译,请在此查找错误:
| 错误 | 原因 | 修复方法 |
|---|---|---|
CS0210 | using UnityEditor; 之类的 using 指令被编译器当作资源释放语句读取 | 删除该指令,并使用完全限定类型名 |
CS0246 | 未限定的类型,例如 PlayerSettings | 使用完整名称,例如 UnityEditor.PlayerSettings |
CS0104 | 单独使用 Object,与 object 存在歧义 | 使用 UnityEngine.Object |
此技能审计的设置
一次调用即可返回完整的预检信息。已在 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,而 Library/ 会被所有标准 Unity .gitignore 忽略,因此该值是每台机器独有的,不会随项目传递:队友和 CI 都不会继承它。由此有两个注意事项。请通过 API 读回该值,不要在 ProjectSettings/ProjectSettings.asset 中查找——即使应用成功,该文件中也不会出现它,因此缺少该值并不代表失败。如果发布构建在 CI 中运行,请在那里将其作为构建步骤应用,而不要假设仓库中保存了它。
应用设置
此技能中的大多数写入操作都通过一次批处理完成,而
scripts/WebOptimizer.cs 已经实现了这个批处理。它声明了一个带有 [MenuItem] 的类,因此它是一个项目文件,而不是 script eval 输入——类声明无法展开成语句块。请将它保存在 Assets/Editor/ 下,等待 Unity 编译,然后用一行命令调用:
UnityEditor.EditorApplication.ExecuteMenuItem("Tools/Apply Web Release Settings");
保留其中的 using 指令;它们在文件中是正确的。对于一次性的更改——例如单个质量级别或帧率切换——内联的 script eval 语句就可以。
从磁盘验证,而不是从刚写入的对象验证
**在同一会话中应用设置并读回,不能证明任何事情。**Player Settings 在保存前只是内存中的对象,因此每次读回都会返回刚赋的值,无论它是否真的写入了
ProjectSettings/ProjectSettings.asset。跳过保存的运行仍会报告成功,而 Editor 会话结束后,整个更改都会消失。这不是推测,而是实际观察到的:一次运行应用了所有设置,读回 Brotli / High / None,显示已完成,但磁盘上的文件毫无变化。
因此,每次写入后都要:
- 保存。
UnityEditor.AssetDatabase.SaveAssets()。WebOptimizer.cs现在会自行执行此操作;内联的script 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 设置审查
通过 script eval 验证并设置以下值:
| 设置 | 发布建议 |
|---|---|
| 压缩格式 | Brotli(HTTPS 托管);HTTP 使用 Gzip |
| 解压缩回退 | 服务器配置正确时设为 关闭 |
| 剥离引擎代码 | 开启 |
| 托管代码剥离级别 | 发布时选 High/开发时选 Medium |
| 代码优化 | 发布时选 Disk Size with LTO/开发时选 Build Times |
| WebAssembly 语言功能 | 浏览器基线允许时选 2023 |
| 启用异常 | None(体积最小);需要 try/catch 时选 Explicitly Thrown Only |
| 初始内存大小 | 根据峰值估算进行调整;过小会导致昂贵的内存增长 |
| 内存增长模式 | Geometric |
| API 兼容性级别 | .NET Standard 2.1——比 .NET Framework 小 |
| IL2CPP 代码生成 | Optimize Size——以略微增加运行时开销为代价缩小 Wasm 体积 |
| 调试符号 | 发布时 关闭;仅开发构建时开启 |
| 数据缓存 | 开启——在浏览器 IndexedDB 中缓存资源数据,加快重复加载 |
| 剥离未使用的网格组件 | 开启——移除未使用的顶点属性 |
| 最大内存大小 | 默认 2048 MB;复杂的 3D 项目最高可设为 4096(Firefox 和 Chrome < 119 在超过 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:仅将 解压缩回退 = 开启 作为备用方案——这会增加约 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 质量级别。
- 使用
script eval设置:UnityEngine.QualitySettings.SetQualityLevel(0, true);,其中 0 = Very Low。 - 可以创建专用于 Web 的质量级别,禁用浏览器中不需要的功能(实时阴影、后期处理效果、高粒子数量)。
Web 帧率
- 使用
script 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 节中关于帧率和质量级别的相同指导;此处没有单独的移动端流程。