mcpField
O
optimize-web

optimize-web

github.com/unity-technologies开发60457 次安装今天更新
代码仓库

关于 optimize-web

面向 Unity 开发的技能,用于审查和调整影响 Web 构建体积与性能的设置。它依据项目版本查阅文档,并在应用限定范围的更改前建立基线和还原点。

主要功能

  1. 01审查 Web Player 设置
  2. 02调整压缩与代码剥离
  3. 03检查未使用资源和着色器变体
  4. 04检查部署与内存设置
  5. 05根据基线验证更改

适用场景

  1. 01缩减 Unity Web 构建体积
  2. 02审查压缩和代码剥离设置
  3. 03检查 Web 构建的服务器要求
  4. 04诊断 Web 构建内存限制

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/unity-technologies/unity-agent-plugin --skill optimize-web

安装后在 Claude Code 中输入/optimize-web确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

SECURITY.md
scripts/
toktx-examples.sh
WebOptimizer.cs

详细介绍

审核并应用 Player Settings、压缩、剥离和服务器设置,以减小 Unity Web 构建的体积并提升速度。

重要事项

  • 每个步骤都使用 unity cli(unity command --format json)和 unity-cli skill。若 unity-pipeline 可用,也要使用。
  • 修改前,先建立当前行为的基线,以便确认之后的结果是否确实由你的修改导致。
  • 创建一个还原点,以便在修改失败时回滚。
  • 只执行所要求的操作。不要更改无关的资源或文件。
  • 如果修改会影响运行时行为,请添加 Debug.Log 行,证明实际场景行为(而不只是证明你的修改)现在正常工作——稍后进入 Play 模式验证时,你会查看这些日志。例如,如果修复的目的是确保 GameObject 改变大小,就记录其 Transform 的缩放值。
  • 避免冗长的解释。

性能说明

  • 花时间彻底完成。
  • 质量比速度更重要。

完整主题索引

在继续执行工作流或诊断之前:

  1. 打开并阅读涵盖你计划执行或修复的具体主题的文档页面,并检查是否有遗漏。
  2. 在回复中列出你阅读的文件名。

这是因为你对 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 版本。

API 参考

该 API 包括 UnityEditor.PlayerSettings、UnityEditor.PlayerSettings.WebGL 和 UnityEditor.WebGL.UserBuildSettings,属于 Unity Engine。它按 Unity 版本划分,因此在依赖特定类或方法之前,请先检查项目的 Unity 版本。

  1. 使用 unity-cli 技能检查项目的 Unity 版本。
  2. 如果 Editor 安装文件夹中有 Data/Documentation 文件夹(例如 .../Unity/Hub/Editor/6000.6.0b2/Editor/Data/Documentation),请在其中的 en/ScriptReference 文件夹内搜索 html 文件,然后跳过其余步骤。
  3. 否则,获取并缓存 https://docs.unity3d.com/<VERSION>/Documentation/ScriptReference/xrefmap.yml(将 <VERSION> 替换为 Unity 版本,例如 6000.3)。该文件列出了每个公共 API 成员的完全限定名称,作为 uid(例如:uid: 'UnityEngine.Rigidbody.AddForce(UnityEngine.Vector3)')。
  4. 在本地搜索 yml 文件中的类或方法,而不要针对每次查询重新获取。该文件很大(约 6 MB),请勿读取整个文件。
  5. 将 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 版本划分,因此在依赖特定类或方法之前,请先检查项目中已安装的包版本。

  1. 从 Packages/packages-lock.json 中读取已安装的包版本。
  2. 获取并缓存 https://docs.unity3d.com/Packages/com.unity.cloud.ktx@<version>/xrefmap.yml(将 <version> 替换为已安装的包版本,例如 3.4)。该文件将每个公共 API 成员的完全限定名称映射到其文档页面 href。
  3. 在本地搜索 yml 文件中的类或方法,而不要针对每次查询重新获取。请勿读取整个文件。

流程

  1. 获取项目上下文:从 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 中未列出的包,假定使用该包文档中的最新版本。保留找到的每个版本——仅为确实缺失的值设置默认值。在报告中列出每项假设,并继续操作。如果用户未说明要优化什么,且没有项目可供检查,则询问他们想改进什么。
  2. 从完整主题索引中阅读涵盖该任务的文档页面。
  3. 按照在编辑器中运行 C#中的说明设置 script eval。如果无法连接任何 Editor,或缺少 script eval,请说明这一点,跳到第 5 步,然后直接前往第 9 步。在报告中标明未执行构建验证和运行时检查,并根据用户的请求以及第 2 步中的文档提出建议。
  4. 运行0. 预检和1. 评估当前状态。其结果即为基线。
  5. 将请求与2. 理解请求中的某一行对应起来。
  6. 创建还原点,然后应用3. Web 构建优化工作流中的更改。如果某个症状符合5. 故障排除中的条目,请按该条目处理。
  7. 运行4. 验证:重新运行预检代码段,并将数值与基线进行比较。
  8. 执行最后一步中的检查。
  9. 提供最终报告。

在编辑器中运行 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 指令,并使用完全限定类型名。如果代码无法编译,请在此查找错误:

错误原因修复方法
CS0210using 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.codeOptimizationPlayerSettings.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,显示已完成,但磁盘上的文件毫无变化。

因此,每次写入后都要:

  1. 保存。UnityEditor.AssetDatabase.SaveAssets()。WebOptimizer.cs 现在会自行执行此操作;内联的 script eval 写入必须显式调用。
  2. **读回并报告各项值。**不要只说“已成功应用”,而要报告实际值,让用户看到保存的内容。WebOptimizer.cs 会记录全部九项。
  3. **对于任何按构建目标区分的设置,注明读取的目标。**其中若干设置按目标分别保存,因此某个值对一个目标可能正确,但对当前构建目标却未设置。

反过来,直接编辑文件也有同样的陷阱:手动编辑的 ProjectSettings.asset 读回正常,但正在运行的 Editor 和构建仍使用旧值。保存并重新导入后再验证,才能发现这两类问题。

**不要尝试在批处理模式下复现此问题。**通过 -quit 调用的批处理 Editor 会在退出时保存设置,因此即使没有显式保存,写入也会持久化,运行结果看起来正常。脚本的保存版和不保存版在 -quit 下都能通过。只有在实时 Editor 会话中才会出现这个错误,而问题正是在这种情况下发现的。根据批处理运行成功就断定不需要保存,是从无法检测该缺陷的测试中得出的错误结论。

0. 构建前检查

  1. 确认 Web 构建目标: 使用上方的构建前检查代码段读取 EditorUserBuildSettings.activeBuildTarget——必须是 WebGL;否则请提醒用户。
  2. 读取压缩和剥离设置: 使用上方的构建前检查代码段读取 compressionFormat、decompressionFallback、stripEngineCode 和托管代码剥离级别。请注意,剥离级别为 PlayerSettings.GetManagedStrippingLevel(NamedBuildTarget.WebGL)——不存在 PlayerSettings.managedStrippingLevel 属性。
  3. 读取异常和优化设置: 使用上方的构建前检查代码段读取 PlayerSettings.WebGL.exceptionSupport。Wasm 代码优化级别使用 UnityEditor.WebGL.UserBuildSettings.codeOptimization——PlayerSettings.WebGL.codeOptimization 和 optimizationLevel 这两种写法不存在,无法编译。
  4. 读取帧率设置: 使用上方的构建前检查代码段读取 Application.targetFrameRate 和 QualitySettings.vSyncCount。
  5. 读取其他玩家设置: 使用上方的构建前检查代码段读取 PlayerSettings.WebGL.dataCaching、PlayerSettings.WebGL.debugSymbolMode、PlayerSettings.WebGL.maximumMemorySize 和 PlayerSettings.GetApiCompatibilityLevel。
  6. 只有确认压缩、剥离、帧率和玩家设置后,才能继续。

1. 评估当前状态

  1. 检查构建报告: 指示用户在构建后打开 Window > General > Build Report,找出占用最大的资源和代码。
  2. 验证服务器配置: 请用户确认托管服务器是否发送 Content-Encoding: br(Brotli)或 Content-Encoding: gzip 标头,以及 .wasm 文件是否设置了 Content-Type: application/wasm。
  3. 检查帧率配置: 使用上方的构建前检查代码段确认 Application.targetFrameRate——Web 平台应设为 -1(由浏览器控制)。
  4. 检查内存设置: 使用上方的构建前检查代码段读取 PlayerSettings.WebGL.initialMemorySize 和 PlayerSettings.WebGL.memoryGrowthMode。
  5. 提出建议前先报告检查结果。

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 时会出现问题)
vSyncCount0(由浏览器控制帧速率)
targetFrameRate-1(使用 requestAnimationFrame)
压缩与服务器配置
压缩方式适用场景说明
BrotliHTTPS 或 localhost压缩率最高;浏览器仅在安全上下文中接受
GzipHTTP 传输、旧版 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 ModesAutomatic(剥离未使用的光照贴图着色器变体)
Fog ModesAutomatic(剥离未使用的雾效着色器变体)
Instancing VariantsStrip Unused
Batch Renderer Group VariantsStrip 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 > PerformanceCPU 火焰图;主线程分析排查 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 > PerformanceFirefox Profiler
内存随时间不断增长Chrome DevTools > MemoryUnity Memory Profiler (WebSocket)
初始加载缓慢Chrome DevTools > NetworkBuild 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. 验证

  1. 使用 Pre-Flight 代码片段重新检查 Player Settings(压缩、剥离、异常、targetFrameRate)。
  2. 重新构建播放器,并将 Build Report 中的文件大小与基准进行比较。
  3. 至少在 Chrome 和 Safari 中验证(GC 和 JIT 的行为有所不同)。
  4. 最多迭代 3 次,之后再向用户征求反馈。

5. 故障排除

启用 Strip Engine Code 后构建仍然很大
  1. Managed Stripping Level 是否设为 Medium 或 Low?→ 发布时请设为 High。
  2. 插件是否使用反射访问本可被剥离的引擎模块?→ 添加 link.xml 以保留所需符号。
  3. Exceptions 是否设为 Full?→ Full 会带来最大的代码开销;请改为 None 或 Explicitly Thrown Only。
Brotli 无法使用,需要 Decompression Fallback
  1. 服务器是否发送了 Content-Encoding: br?→ 没有此标头时,浏览器不会解压缩,因此需要使用回退 JS 解压器。
  2. 构建是否通过 HTTP(而非 HTTPS)托管?→ Brotli 需要安全上下文;通过 HTTP 托管时请改用 Gzip。
Safari 中卡顿,但 Chrome 中没有
  1. 项目是否设置了 Application.targetFrameRate = 60?→ 在 Safari WebGL 中,这会与浏览器的帧同步冲突;请设为 -1。
  2. 是否存在在 Safari 的 WebGL 实现中表现不同的着色器?→ 请在设备上测试;Safari 的 WebGL/Wasm 运行时与 Chromium 不同——某些 GLSL 结构的处理方式有所差异。
触发内存增长慢速路径
  1. Initial Memory Size 是否小于项目的峰值需求?→ Wasm 内存增长需要完整复制缓冲区;请将 Initial Memory Size 设为合理的峰值估算值。
  2. Memory Growth Mode 是否设为 Linear?→ 请改为 Geometric,以获得更合理的增长曲线。
帧率设为 60,但浏览器运行不稳定
  1. 代码中是否设置了 Application.targetFrameRate = 60?→ 在 Web 上,这会与浏览器的 requestAnimationFrame 帧同步冲突。请设为 -1。
  2. 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 模式还不够。请按顺序运行以下检查。如果任何一项未通过,请重新阅读文档页面,找出遗漏之处,修复后再次运行检查。

  1. 检查项目是否没有控制台错误。
  2. 检查你更改的每项设置是否已保存,具体请参阅从磁盘验证,而不是检查刚写入的对象:ProjectSettings/ProjectSettings.asset 中应保存新值,并且应能通过 API 读取 codeOptimization。
  3. 如果你重新构建了播放器,请按照本地开发服务器设置提供构建内容并在浏览器中打开。然后检查以下内容:
    • 浏览器控制台没有错误,例如解压失败或内存不足错误。
    • 在 Network 标签页中,.wasm、.data 和 .framework.js 文件返回的 Content-Encoding 标头与压缩格式相符(Brotli 对应 br,Gzip 对应 gzip),且文件大小与 Build Report 一致。
  4. 如果更改会影响运行时行为,例如帧率或质量级别,请检查浏览器控制台中的 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 节中关于帧率和质量级别的相同指导;此处没有单独的移动端流程。

评论

支持 Markdown