返回「计算机、信息技术与工程」

P3 Unity3D WebGL

P3 Unity3D WebGL 基本结构

更多
Markdown 结构化数据
本文目录 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服务器.

assets/image-20250516000357725.png

CDN 配置

阿里云OSS 上传webgl 文件夹

assets/image-20250519154647442.png

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

assets/image-20250519162928466.png

导出配置CDN和AppID

assets/image-20250519162957254.png