本文目录 15 个章节
P3 Unity3D WebGL
基本结构
1. 主要文件
- index.html: 主入口页面,负责加载和运行游戏
- Build/目录: 包含编译后的WebGL游戏核心文件
- StreamingAssets/目录: Unity流式资产
- TemplateData/目录: UI元素和样式资源
2. Build 目录核心文件
- BuildWebGL.data: 包含游戏资源数据(模型、贴图、音频等)
- BuildWebGL.framework.js: Unity运行时框架,处理游戏逻辑
- BuildWebGL.loader.js: Boot 脚本负责加载游戏并初始化Unity实例
- BuildWebGL.wasm: WebAssembly二进制文件,包含编译后的C#、C++代码,提供高性能执行
- BuildWebGL.symbols.json: 调试符号信息,用于错误追踪
- index.html 页面模板 (调用 createUnityInstance)
旧版 Unity 只有
UnityLoader.js;2020+ 拆成 loader + framework 便于缓存与模板自定义。
3. 加载机制
从代码中可以看出,游戏通过以下流程加载:
- JavaScript初始化UI元素(容器、画布、加载条等)
- 配置Unity WebGL加载器
- 加载器加载.wasm和资源文件
- 显示进度条指示加载进度
- 完成加载后启动游戏
4. 用户界面组件
- 进度条: 显示资源加载进度
- 全屏按钮: 控制游戏全屏显示
- 警告横幅: 显示错误或警告信息
框架解读
加载流程时序图
index.html
│ create <script src="*.loader.js">
▼
loader.js
├─ ① 解析 config (data / wasm / framework 路径)
├─ ② 动态 <script> 载入 framework.js
├─ ③ fetch .data ─┐
├─ ④ fetch .wasm ─┤ 并行下载 → onProgress()
├─ ⑤ fetch StreamingAssets -┘
│
framework.js
├─ ⑥ 构造 Module = { preInit, instantiateWasm, onRuntimeInitialized … }
├─ ⑦ 调 WebAssembly.instantiateStreaming(.wasm, importObj)
└─ ⑧ 运行时触发 Module.onRuntimeInitialized()
│
▼
return Promise<unityInstance> 给用户
┌────────────────────────────────────────────────────────────────────┐
│ L0 ── Host Shell │
│ (index.html + CDN/小程序容器) │
│ - 静态 UI(canvas, progress div, full-screen btn …) │
│ - <script src="BuildWebGL.loader.js"> │
└────────────────────────────────────────────────────────────────────┘
│ createUnityInstance(canvas, userConfig, onProgress)
▼
┌────────────────────────────────────────────────────────────────────┐
│ L1 ── Boot / Loader 层 <BuildWebGL.loader.js> │
│ │
│ ① 组装 **Module 原型** (变量 c) │
│ - 默认字段:canvas / webglContextAttributes / print / abort… │
│ - 混入 userConfig (dataUrl / codeUrl / companyName …) │
│ │
│ ② “环境钩子” │
│ - 禁用右键拖拽 (disabledCanvasEvents) │
│ - 全局 try-catch → 函数 **t(event)** │
│ - 全屏尺寸同步 → webkitfullscreenchange │
│ - 统一清理栈 → c.deinitializers[] │
│ │
│ ③ 进度聚合器 **b(name, progEvt)** │
│ - 维护 c.downloadProgress[name] │
│ - 计算整体进度 → onProgress(0–0.9) │
│ │
│ ④ 动态拉取 **framework.js** → 函数 **w()** │
│ - <script> 注入 + MIME 诊断 │
│ - 加载完执行: unityFramework(c) │
│ │
│ **向下接口** : 调用 w() 之后,控制权交给 framework.js │
└────────────────────────────────────────────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────────────┐
│ L2 ── Glue / Framework 层 <BuildWebGL.framework.js> │
│ │
│ ① 完整构造 **Module** (仍是同一个对象 c) │
│ • locateFile → 把 “build.wasm” 重定向到 c.codeUrl │
│ • preRun / postRun 数组 │
│ • instantiateWasm(imports, cb) ↴ │
│ fetch .wasm → WebAssembly.instantiate (可自定义) │
│ │
│ ② 网络包装 **fetchWithProgress(url, opts)** │
│ • readBodyWithProgress → onProgress(chunk) │
│ • 支持 enableStreamingDownload (边下边解包 .data) │
│ │
│ ③ 下载 & 解包 **.data** │
│ • 推入 Module.preRun: │
│ - fetch dataUrl │
│ - 解析 UnityFS header │
│ - FS_createDataFile 写入 MEMFS │
│ │
│ ④ ✱**SystemInfo**✱ 运行前检测 │
│ hasWebGL / hasWasm / hasThreads / GPU Vendor … │
│ │
│ ⑤ **Error 路由链** │
│ abort() ─► Module.abortHandler → m() → g() → UI/alert │
│ JS Error ─► window.onerror (同上) │
│ │
│ **向下接口** : 当 Wasm + 资源全部 OK → 触发 │
│ Module.onRuntimeInitialized() │
│ │
│ 此时 loader 的 Promise resolve(unityInstance) │
└────────────────────────────────────────────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────────────┐
│ L3 ── Runtime / Wasm 层 <BuildWebGL.wasm> │
│ │
│ - IL2CPP 转 C++ 转 wasm32 指令 │
│ - Unity Runtime (渲染/物理/Audio…) │
│ - _start() → UnityMain() │
│ - 导出: SendMessage / SetFullscreen / quit / _getMemInfo │
│ │
│ · 通过 import 表调用 glue 中的 │
│ • glBindBuffer / wasmFS / pthread… │
│ · 如果抛 fatal → `abort()` → L2 错误链 │
└────────────────────────────────────────────────────────────────────┘
代码解读
index.html 文件
这是主入口文件,包含初始化UI元素和启动游戏的核心代码:
// ...页面HTML结构...
<canvas id="unity-canvas" width="960" height="600"></canvas>
核心JavaScript初始化代码(通常位于index.html底部):
// 定义配置参数
var container = document.querySelector("#unity-container");
var canvas = document.querySelector("#unity-canvas");
var loadingBar = document.querySelector("#unity-loading-bar");
var progressBarFull = document.querySelector("#unity-progress-bar-full");
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
var warningBanner = document.querySelector("#unity-warning");
// 移动设备检测和配置
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
// 移动设备配置...
}
// 创建Unity实例配置
var buildUrl = "Build";
var loaderUrl = buildUrl + "/BuildWebGL.loader.js";
var config = {
dataUrl: buildUrl + "/BuildWebGL.data",
frameworkUrl: buildUrl + "/BuildWebGL.framework.js",
codeUrl: buildUrl + "/BuildWebGL.wasm",
streamingAssetsUrl: "StreamingAssets",
companyName: "DefaultCompany",
productName: "Stick Guys TD",
productVersion: "1.0",
showBanner: unityShowBanner,
};
// 加载脚本和启动Unity
var script = document.createElement("script");
script.src = loaderUrl;
script.onload = () => {
createUnityInstance(canvas, config, (progress) => {
progressBarFull.style.width = 100 * progress + "%";
}).then((unityInstance) => {
loadingBar.style.display = "none";
fullscreenButton.onclick = () => {
unityInstance.SetFullscreen(1);
};
}).catch((message) => {
alert(message);
});
};
document.body.appendChild(script);
BuildWebGL.loader.js 文件
这个文件包含Unity WebGL加载器的核心代码:
// 定义全局UnityLoader对象
var UnityLoader = {
// 初始化相关配置和方法
Compression: {
// 解压缩方法定义
},
UnityCache: {
// 缓存管理相关代码
},
// 加载和解析.wasm文件的核心方法
SystemInfo: function() {
// 检测浏览器兼容性
},
// 内存管理相关方法
cachedFetch: function() {
// 资源加载和缓存
},
// 创建Unity实例的方法
instantiateModule: function(binary, moduleOptions) {
// 实例化WebAssembly模块
}
}
// createUnityInstance函数 - 最关键的函数,负责:
function createUnityInstance(canvas, config, onProgress) {
// 1. 设置进度回调函数
// 2. 根据配置加载BuildWebGL.data(游戏资源)
// 3. 加载BuildWebGL.framework.js(Unity运行时框架)
// 4. 加载BuildWebGL.wasm(WebAssembly二进制代码)
// 5. 初始化Unity实例并返回Promise
}
createUnityInstance(位于 loader.js)
export function createUnityInstance(canvas, cfg, onProgress) {
const { frameworkUrl, dataUrl, codeUrl } = cfg;
// (1) 注入样式 && 预加载条
let pr = 0;
const setProgress = p => { pr = p; onProgress?.(p); };
// (2) 下载 Framework (await)
await import(/* webpackIgnore: true */ frameworkUrl);
// (3) 构造 Module 对象 (由 framework 定义全局变量)
const Module = window.Module || {};
Module.canvas = canvas;
Module.preInit = [() => setProgress(0.1)];
Module.onRuntimeInitialized = () => setProgress(1.0);
// (4) 设置文件路径,Framework 内 fetch() 时会用
Module.dataUrl = dataUrl;
Module.codeUrl = codeUrl;
// (5) 调用 createUnityInstanceImpl (framework.js 注入的函数)
return window.createUnityInstanceImpl(Module, setProgress);
}
要点
- 进度条纯 JS 计数,无 GPU;你可 override
setProgress做自定义动画。 - Framework 和 Wasm 并行下载,通过
instantiateStreaming边下边编译。
framework.js 关键片段
var Module = {
preRun: [],
postRun: [],
preInit: [],
wasmBinaryFile: config.codeUrl,
locateFile: (path) => {
if (path.endsWith('.data')) return config.dataUrl;
return path;
},
instantiateWasm: (imports, successCb) => {
fetch(Module.wasmBinaryFile)
.then(r => r.arrayBuffer())
.then(bin => WebAssembly.instantiate(bin, imports))
.then(out => successCb(out.instance, out.module));
return {}; // Emscripten stub
}
};
instantiateWasm可被你改写为自行缓存 .wasm 或使用WebAssembly.compileStreaming。asmLibraryArg:Emscripten 生成,包含所有导入(memory,table,glBindBuffer…)。
Unity构建微信小程序
微信小游戏开发指南: https://developers.weixin.qq.com/minigame/dev/guide/develop/start.html
Unity转微信小游戏: https://github.com/wechat-miniprogram/minigame-unity-webgl-transform
构建流程
https://wechat-miniprogram.github.io/minigame-unity-webgl-transform/Design/Transform.html
微信小程序申请测试号, 即可生成测试用的AppID
生成后, 可将webgl 目录 作为资源包, 上传到CDN服务器. Github Pages 可以作为CDN服务器.

CDN 配置
阿里云OSS 上传webgl 文件夹

小程序测试号需要配置合法域名

导出配置CDN和AppID
