# 最佳实践:Unity 小游戏开启 Wasm Exception 指南
# 背景
# 问题:JS 模拟异常的性能与包体开销
Unity WebGL 构建默认使用 JS 模拟异常(-fexceptions)。在这种模式下,所有 try 块内的函数调用在进入 Wasm 执行前,都必须先跳转到 JavaScript 层记录堆栈信息,执行完再跳回 Wasm。
这带来两方面开销:
- 性能开销:JS ↔ Wasm 的跨层跳转发生在每一次函数调用上,无论是否真的抛出了异常。在异常密集或调用链较深的代码路径中,这一开销尤为显著。
- 包体开销:JS 模拟异常需要在 Wasm 二进制中保留大量辅助数据和跳转表,导致
.wasm文件体积偏大。更隐蔽的问题是:即使将 PlayerSettings 中的 Enable Exceptions 设为 None,旧版 Unity 也并不会彻底移除这些异常处理指令,JS ↔ Wasm 跳转依然存在。
原生 Wasm Exception(-fwasm-exceptions) 是 WebAssembly 规范层面的异常支持,由 V8 / JavaScriptCore 引擎直接处理,异常完全在 Wasm 内部流转,无需 JS 中转。非异常路径的正常执行几乎零额外开销,包体中的异常相关指令也更精简。
# 平台兼容性
| 运行环境 | 支持情况 | 备注 |
|---|---|---|
| iOS 真机(微信) | ✅ 支持 | Safari/WebKit JSC 15.2+ 原生支持 Wasm EH |
| Android 真机(微信) | ✅ 支持 | 完整支持 |
| PC | ✅ 支持 | 完整支持 |
# Wasm-Exception 开启方法详解
# 团结引擎开启方法
如果你使用的是团结引擎(TuanJie Engine),上述问题已得到官方修复,无需手动处理。团结引擎在 PlayerSettings 中直接提供了以下选项:
- Enable Exceptions → Wasm Exception:一键开启原生 Wasm 异常处理
- Enable Exceptions Only For User Codes:仅对用户 C# 代码和 Library 开启异常,IL2CPP Runtime / CoreLib 关闭异常,进一步减少开销
设置入口:Project Settings → Player → Publish Settings
详见官方文档:团结引擎 — 异常处理优化
# Unity 2021.3.x 方案
# 原理
Unity 2021 使用 Emscripten 1.x,其构建系统在 WebGLSupport/BuildTools/Emscripten/emscripten/ 目录下提供了一个 emcc.py 包装脚本(非 Emscripten 原生文件)。
需要修改的核心逻辑有三处:
- 编译阶段:将
-fexceptions(JS 模拟)替换为-fwasm-exceptions(原生) - 链接阶段:处理 response file(
.rsp)时,无条件追加-fwasm-exceptions+ 显式置零DISABLE_EXCEPTION_THROWING=0和DISABLE_EXCEPTION_CATCHING=0,消解 Unity Bee 构建系统注入的参数冲突 - 导出函数:在
EXPORTED_FUNCTIONS中追加_main,防止链接时卡死
# 快速操作(推荐)
下载 wasm-exception-patch-scripts.zip,解压后找到 apply_wasm_eh_2021.py:
# 1. 用文本编辑器打开脚本,修改第一行 UNITY_EDITOR_ROOT 为你的 Unity 2021 Editor 安装路径
# Mac 示例: /Applications/Unity/Hub/Editor/2021.3.30f1c1
# Windows 示例: C:\Program Files\Unity\Hub\Editor\2021.3.30f1c1\Editor
# 2. 执行脚本
python apply_wasm_eh_2021.py
# 3. 重启 Unity Editor,重新构建 WebGL
脚本会自动:
- 备份原始
emcc.py为emcc.py.original(只备份一次,已有则跳过) - 用 patch 版本替换
emcc.py
# 手动操作
找到并编辑以下文件:
- Mac:
/Applications/Unity/Hub/Editor/<版本>/PlaybackEngines/WebGLSupport/BuildTools/Emscripten/emscripten/emcc.py - Windows:
<Unity安装根目录>\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\emscripten\emcc.py
修改 1:在文件顶部 sys.argv 赋值处添加替换逻辑(原文件约第 40 行附近):
# 将 JS 模拟异常替换为原生 Wasm Exception(作用于编译阶段)
sys.argv = ['-fwasm-exceptions' if item == '-fexceptions' else item for item in sys.argv]
修改 2:在 substitute_response_files 函数的 if arg.startswith('@'): 分支末尾追加(处理链接阶段):
# 确保链接阶段原生 Wasm Exception 相关功能全部被链接进去
new_args += ['-fwasm-exceptions']
new_args += ['-sDISABLE_EXCEPTION_THROWING=0']
new_args += ['-sDISABLE_EXCEPTION_CATCHING=0']
修改 3:在同一分支中,处理 EXPORTED_FUNCTIONS 参数时追加 _main:
if 'EXPORTED_FUNCTIONS=' in resp_arg:
new_args.append(resp_arg + ',_main')
else:
new_args.append(resp_arg)
# Unity 2022.3.x 方案
# 原理
Unity 2022 使用 Emscripten 3.1.8,构建系统(Bee)直接调用 Emscripten 原生脚本。需要修改 3 个文件、4 处:
| 文件 | 修改位置 | 作用 |
|---|---|---|
emcc.py | substitute_response_files() 之后 | 编译 + 链接阶段注入 wasm-eh 参数 |
emcc.py | 互斥检查(DISABLE_EXCEPTION_THROWING) | 消解 Bee rsp 与 wasm-eh 的参数冲突 |
building.py | JS-EH LLVM flag 注入处 | 防止 -enable-emscripten-cxx-exceptions 与 wasm-eh 冲突 |
system_libs.py | force_include 处理处 | 强制 --whole-archive 链接 libunwind-except.a,解决 __wasm_lpad_context 未定义 |
# 快速操作(推荐)
下载 wasm-exception-patch-scripts.zip,解压后找到 apply_wasm_eh_2022.py:
# 1. 用文本编辑器打开脚本,修改第一行 UNITY_EDITOR_ROOT 为你的 Unity 2022 Editor 安装路径
# Mac 示例: /Applications/Unity/Hub/Editor/2022.3.62f1c1
# Windows 示例: C:\Program Files\Unity\Hub\Editor\2022.3.62f1c1\Editor
# 2. 执行脚本
python apply_wasm_eh_2022.py
# 3. 重启 Unity Editor,重新构建 WebGL
脚本会自动:
- 备份
emcc.py、building.py、system_libs.py为.original文件 - 应用 4 处 patch,并输出每处的应用状态
# 手动操作
所有文件位于:
- Mac:
/Applications/Unity/Hub/Editor/<版本>/PlaybackEngines/WebGLSupport/BuildTools/Emscripten/emscripten/ - Windows:
<Unity安装根目录>\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\emscripten\
修改 1 — emcc.py(substitute_response_files() 调用之后,约 L1051):
# --- Unity 2022 wasm-eh patch ---
# Bee 在链接阶段不传 -fexceptions,必须无条件追加 -fwasm-exceptions
args = ['-fwasm-exceptions' if a == '-fexceptions' else a for a in args]
args.append('-fwasm-exceptions')
args.append('-sDISABLE_EXCEPTION_THROWING=0')
args.append('-sDISABLE_EXCEPTION_CATCHING=0')
修改 2 — emcc.py(DISABLE_EXCEPTION_THROWING 互斥检查处,约 L1465):
# 原来:
if settings.DISABLE_EXCEPTION_THROWING and not settings.DISABLE_EXCEPTION_CATCHING:
exit_with_error("DISABLE_EXCEPTION_THROWING was set ...")
# 改为:
if settings.DISABLE_EXCEPTION_THROWING and not settings.DISABLE_EXCEPTION_CATCHING:
if settings.EXCEPTION_HANDLING:
settings.DISABLE_EXCEPTION_CATCHING = 1
else:
exit_with_error("DISABLE_EXCEPTION_THROWING was set ...")
修改 3 — tools/building.py(-enable-emscripten-cxx-exceptions 注入处,约 L230):
# 原来:
if not settings.DISABLE_EXCEPTION_CATCHING:
args += ['-enable-emscripten-cxx-exceptions']
# 改为:
if not settings.DISABLE_EXCEPTION_CATCHING and not settings.EXCEPTION_HANDLING:
args += ['-enable-emscripten-cxx-exceptions']
修改 4 — tools/system_libs.py(force_include 处理,约 L1756):
# 原来:
force_include += forced
if force_include:
# 改为:
force_include += forced
if settings.EXCEPTION_HANDLING:
force_include.append('libunwind') # ensure __wasm_lpad_context resolved
if force_include:
# 验证
构建完成后,创建一个测试脚本挂载到场景中验证:
using System;
using UnityEngine;
public class WasmExceptionTest : MonoBehaviour
{
void Start()
{
// 测试 1:捕获异常
try
{
throw new Exception("这是一个测试异常,会被 catch");
}
catch (Exception e)
{
Debug.Log("捕获异常: " + e.Message);
}
finally
{
Debug.Log("finally 执行");
}
// 测试 2:未被捕获的异常(预期会在 console 看到报错)
throw new Exception("这是一个测试异常,不会被 catch");
}
}
预期结果:
捕获异常: 这是一个测试异常,会被 catch✅finally 执行✅- 第二个
throw在 console 中正常报错,不会导致游戏静默挂起 ✅
# 恢复原始文件
所有脚本在第一次执行时会备份原文件(后缀 .original),如需回退:
# 2021:恢复 emcc.py
cp emcc.py.original emcc.py
# 2022:恢复三个文件
cp emcc.py.original emcc.py
cp tools/building.py.original tools/building.py
cp tools/system_libs.py.original tools/system_libs.py
# 常见问题
Q:升级 Unity 小版本后 patch 还有效吗?
A:Unity 同大版本(如 2022.3.x)内,Emscripten 工具链脚本通常不变,patch 可继续使用。升级后建议重新运行一键脚本(它会检测 patch 是否已应用并跳过已有的部分)。
Q:Windows 微信开发者工具报 CompileError,怎么办?
A:
Windows 微信开发者工具内嵌的 Chromium V8 默认未启用 --experimental-wasm-eh flag,加载含 Exception section 的 wasm 文件时会报:
CompileError: WebAssembly.instantiate(): unexpected section <Exception>
(enable with --experimental-wasm-eh)
下载 微信开发者工具 Nightly Build,其内嵌 V8 默认启用了 --experimental-wasm-eh,可正常加载。
Q:2022 一键脚本提示 Patch A not found (may already be patched)?
A:说明该 patch 已经应用过,无需重复操作。