---
title: "P3 Unity3D WebGL"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/unity3d-webgl-overview/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/WebGL/P3 Unity3D WebGL.md"
content_hash: 6a49ecb470bcc4e4533d8e7af6df68179dc01bb4235dfa9ebc14e15bb10aec86
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# 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. 用户界面组件
- 进度条: 显示资源加载进度
- 全屏按钮: 控制游戏全屏显示
- 警告横幅: 显示错误或警告信息

## 框架解读

### 加载流程时序图

```html
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> 给用户
```

```php
┌────────────────────────────────────────────────────────────────────┐
│  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
// ...页面HTML结构...

  <canvas id="unity-canvas" width="960" height="600"></canvas>











```

核心JavaScript初始化代码（通常位于index.html底部）：

```javascript
// 定义配置参数
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加载器的核心代码：

```javascript
// 定义全局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）

```javascript
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` 关键片段

```javascript
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服务器.

![assets/image-20250516000357725.png](/media/c062ffba7ed884f56f19.png)


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

![assets/image-20250519154647442.png](/media/8b28820888fd4e612294.png)

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

![assets/image-20250519162928466.png](/media/15007e29ac88ccfa5a16.png)

导出配置CDN和AppID

![assets/image-20250519162957254.png](/media/4052330b706366faa06b.png)
