在上一篇文章中,我提到过想要编写一个游戏引擎,但并未具体说明目标是什么。现在,先为这个游戏引擎设定一个初步构想:它必须具备跨平台能力。到了 2025 年,尽管大部分游戏依然以 Windows 为主,但越来越多游戏已同时支持 Linux 和 macOS,而且主流游戏引擎(如 Unreal、Unity)都已实现跨平台支持。
作为一个 Web 出身的开发者,我认为 Web 平台也不容忽视。因此,本文将重点探讨如何让以 C++ 编写的游戏引擎在 Web 平台上运行的可行性。
C++ 方面的考虑
WebAssembly 目前已经发布了很多年,许多语言都可以编译成 wasm 的二进制,从而让 Web 运行。那要让 C++ 编译成 wasm 就得使用 Emscripten。目前来说 Emscripten 也支持了绝大多数 C++ 20/23 的特性,不过很可惜的是,不支持 C++ modules。这点真的很可惜,模块功能自从 C++ 20 提出,到 C++ 23 支持 std 模块,已经过去了很多年。Emscripten 是基于 llvm,而最新的 llvm 版本已经支持 std 模块了。从 Github 的相关 issue 也看不到什么时候会提供支持。
如果要考虑支持 Web 平台,就得放弃 C++ 模块这个特性。
系统 API 的差异
众所周知,浏览器中的 Wasm 虚拟机运行在沙箱环境中,无法直接访问操作系统底层 API,例如文件系统、音频、图形和网络。而这些功能对游戏引擎而言至关重要。下面逐项探讨在 Web 平台上如何应对这些差异。
文件系统
游戏运行时往往需要加载大量资源文件。在原生平台,通常直接调用文件读写 API 即可。然而在 Web 环境下,资源文件一般托管在服务器上,需要通过网络获取。对于普通的 JavaScript 应用,只需使用 fetch
即可完成远程资源加载;但 Wasm 无法直接调用浏览器的 Web API,需要通过 JavaScript 桥接。好在 Emscripten 已经完成了这方面的封装。根据官方文档,只需调用 emscripten_fetch
即可从远端下载数据。
例如:
1#include <stdio.h> 2#include <string.h> 3#include <emscripten/fetch.h> 4 5void downloadSucceeded(emscripten_fetch_t *fetch) { 6 printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); 7 // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1]; 8 emscripten_fetch_close(fetch); // Free data associated with the fetch. 9} 10 11void downloadFailed(emscripten_fetch_t *fetch) { 12 printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); 13 emscripten_fetch_close(fetch); // Also free data on failure. 14} 15 16int main() { 17 emscripten_fetch_attr_t attr; 18 emscripten_fetch_attr_init(&attr); 19 strcpy(attr.requestMethod, "GET"); 20 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; 21 attr.onsuccess = downloadSucceeded; 22 attr.onerror = downloadFailed; 23 emscripten_fetch(&attr, "myfile.dat"); 24}
只要在引擎中对原生文件 I/O 与 Emscripten 的 fetch
做一层抽象,就能实现统一的资源加载接口。
音频系统
在浏览器环境中,播放音频需要使用 <audio>
标签或 Web Audio API。令人惊喜的是,Emscripten 对 OpenAL 1.1 提供了完整支持,并以 Web Audio API 作为其后端实现。
这意味着,只要在引擎中使用 OpenAL,就无需额外关注底层浏览器音频细节,Emscripten 会自动将 OpenAL 调用映射到 Web Audio API,从而保证跨平台一致性。
网络
浏览器无法直接访问底层 TCP/UDP 套接字,只能使用 WebSocket、Fetch 等高层网络 API。而大多数游戏在实时通信时底层往往依赖 UDP,这与浏览器环境存在差异。
在这里,Web 与原生平台的行为差异可能是无法避免的。但对我而言,这不是当前的重点:构建一个复杂到服务端逻辑也要支持的“大型”游戏引擎并不我的目标。
因此,网络层的兼容性问题可以暂时搁置。
图形API
在图形渲染方面,Web 平台的杀手锏是 WebGPU。WebGPU 1.0 规范已稳定,同时在原生平台上也有对应实现——在 Windows 上通过 DirectX 12,在 Linux 上通过 Vulkan,在 macOS 上通过 Metal 等。
这正好契合跨平台开发的需求:引擎只需使用 WebGPU 接口编写渲染模块,便可在原生平台调用对应后端,在浏览器端调用 WebGPU。Emscripten 同样支持调用 WebGPU API,让 Wasm 代码能够直接与浏览器的图形管线对接。这样一来,就无需在引擎内部再维护不同平台下的图形后端实现。
结论
Web 平台完全可以作为游戏引擎的一个支持目标。不过,目前我对整个项目还处于探索阶段,短期内会优先在 Windows、Linux、macOS 三大原生平台上进行开发与验证,在未来适当的时机再考虑输出至 Web 平台