mcpField
O
optimize-web

optimize-web

github.com/unity-technologies开发1.2k5.5k 次安装1 天前更新
代码仓库

关于 optimize-web

用于审查和调整 Unity Web 构建设置的技能,关注构建体积与性能。它先记录基线并创建还原点,再通过 Unity CLI 和 Editor 指导文档化的修改与验证。

主要功能

  1. 01审查 Web 构建设置
  2. 02建立基线并创建还原点
  3. 03压缩与代码裁剪指导
  4. 04通过 Unity CLI 操作 Editor 设置
  5. 05构建与运行时验证流程

适用场景

  1. 01缩小 Unity Web 构建体积
  2. 02检查压缩与裁剪设置
  3. 03应用有文档依据的 Web Player 设置
  4. 04比较修改前后的构建设置

安装

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

$npx skills add https://github.com/unity-technologies/skills --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#中的说明设置 eval。如果无法连接到 Editor,或缺少 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 和卡在 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.codeOptimizationPlayerSettings.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 并报告完成,但磁盘上的文件始终没有变化。

因此,每次写入后都要:

  1. **保存。**使用 UnityEditor.AssetDatabase.SaveAssets()。WebOptimizer.cs 现在会自行执行此操作;内联 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 Settings 审核

通过 eval 验证并设置以下值:

设置发布建议
Compression FormatBrotli(HTTPS 托管);HTTP 使用 Gzip
Decompression Fallback服务器配置正确时设为 Off
Strip Engine CodeOn
Managed Stripping LevelHigh(发布)/ Medium(开发)
Code OptimizationDisk Size with LTO(发布)/ Build Times(开发)
WebAssembly Language Features浏览器基准版本允许时设为 2023
Enable ExceptionsNone(体积最小);需要 try/catch 时设为 Explicitly Thrown Only
Initial Memory Size根据峰值估算调整;过小会导致昂贵的内存增长
Memory Growth ModeGeometric
API Compatibility Level.NET Standard 2.1——比 .NET Framework 小
IL2CPP Code GenerationOptimize Size——以略微增加运行时开销为代价,缩小 Wasm 体积
Debug Symbols发布时设为 Off;仅开发构建时开启
Data CachingOn——在浏览器 IndexedDB 中缓存资源数据,加快重复加载速度
Strip Unused Mesh ComponentsOn——移除未使用的顶点属性
Maximum Memory Size默认 2048 MB;复杂的 3D 项目最高可设为 4096(Firefox 和低于 119 版的 Chrome 在超过 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:仅将 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 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 质量级别。
  • 使用 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 > 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