在这篇文章中,我将描述如何将 Surreal Engine 游戏引擎移植到 WebAssembly。
超现实引擎–一个游戏引擎,实现了Unreal Engine 1的大部分功能,著名的游戏都使用这个引擎–虚幻竞技场 99、虚幻、杀出重围、不朽。它指的是主要在单线程执行环境中工作的经典引擎。
最初,我有一个想法,那就是承担一个我无法在任何合理时间内完成的项目,从而向我的 Twitch 粉丝表明,有些项目甚至是我也无法完成。在我的第一次直播中,我突然意识到使用 Emscripten 将 Surreal Engine C++ 移植到 WebAssembly 的任务是可行的。
一个月后,我可以在 WebAssembly 上演示我的前叉和引擎组装:
https://demensdeum.com/demos/SurrealEngine/
与原始版本一样,控制是使用键盘箭头进行的。接下来,我计划将其调整为移动控制 (tachi),添加正确的光照和 Unreal Tournament 99 渲染的其他图形功能。
从哪里开始?
我想说的第一件事是,任何项目都可以使用 Emscripten 从 C++ 移植到 WebAssembly,唯一的问题是功能有多完整。选择一个库端口已经可用于 Emscripten 的项目;对于 Surreal Engine,你非常幸运,因为该引擎使用 SDL 2、OpenAL 库。它们都被移植到 Emscripten。不过,Vulkan 用作图形 API,目前无法用于 HTML5,实现 WebGPU 的工作正在进行中,但也处于草案阶段,也未知从 Vulkan 到 WebGPU 的进一步移植有多简单,完全标准化后。因此,我必须为 Surreal Engine 编写自己的基本 OpenGL-ES / WebGL 渲染器。
构建项目
在 Surreal Engine 中构建系统 – CMake,这也简化了移植,因为Emscripten 提供其原生构建器“ emcmake,emmake。
Surreal Engine 移植基于我最新的 WebGL/OpenGL ES 和 C++ 游戏代码,称为 Death-Mask,因此开发更加简单,我随身携带了所有必要的构建标志和代码示例。
CMakeLists.txt 中最重要的一点是 Emscripten 的构建标志,下面是项目文件中的示例:
-s MAX_WEBGL_VERSION=2 \
-s EXCEPTION_DEBUG \
-fexceptions \
--preload-file UnrealTournament/ \
--preload-file SurrealEngine.pk3 \
--bind \
--use-preload-plugins \
-Wall \
-Wextra \
-Werror=return-type \
-s USE_SDL=2 \
-s ASSERTIONS=1 \
-w \
-g4 \
-s DISABLE_EXCEPTION_CATCHING=0 \
-O3 \
--no-heap-copy \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXIT_RUNTIME=1")
构建脚本本身:
emmake make -j 16
cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data
cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js
cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm
cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html
cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png
接下来我们将准备索引.html ,其中包括项目文件系统预加载器。为了上传到网络,我使用了 Unreal Tournament Demo 版本 338。正如您从 CMake 文件中看到的,解压后的游戏文件夹已添加到构建目录中,并作为 Emscripten 的预加载文件进行链接。
主要代码更改
那就需要改变游戏的游戏循环,不能无限循环,这样会导致浏览器卡住,而是需要使用emscripten_set_main_loop,我在2017年的笔记中写过这个功能< a href="https://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">将 SDL C++ 游戏移植到 HTML5 (Emscripten)”
我们将退出 while 循环的代码更改为 if,然后在全局范围内显示包含游戏循环的游戏引擎的主类,并编写一个全局函数,该函数将从全局对象中调用游戏循环步骤:
#include <emscripten.h>
Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;
void emscripten_game_loop_step() {
EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();
}
#endif
之后,您需要确保应用程序中没有后台线程;如果有,则准备将其重写为单线程执行,或者使用 Emscripten 中的 phtread 库。
Surreal Engine中的后台线程用于播放音乐,数据来自主引擎线程,有关当前曲目、是否需要播放音乐或是否缺少音乐,然后后台线程通过互斥体接收新状态并开始播放新音乐,或暂停。背景流还用于在播放期间缓冲音乐。
我尝试使用 pthread 为 Emscripten 构建 Surreal Engine,但没有成功,因为 SDL2 和 OpenAL 端口是在没有 pthread 支持的情况下构建的,而且我不想为了音乐而重建它们。因此,我使用循环将背景音乐流的功能转移到单线程执行。通过从 C++ 代码中删除 pthread 调用,我将缓冲和音乐播放移至主线程,这样就不会出现延迟,我将缓冲区增加了几秒钟。
接下来我会描述图形和声音的具体实现。
不支持 Vulkan!
是的,HTML5 不支持 Vulkan,尽管所有营销手册都将跨平台和广泛的平台支持作为 Vulkan 的主要优势。因此,我必须为简化的 OpenGL 类型编写自己的基本图形渲染器 – ES,它用在移动设备上,有时它不包含现代OpenGL的时尚功能,但它很好地移植到WebGL,这正是Emscripten所实现的。写了基本的tile渲染,bsp渲染,最简单的GUI显示,渲染模型+贴图,两周就完成了。这也许是该项目中最困难的部分。实现 Surreal Engine 渲染的全部功能还有很多工作要做,因此欢迎读者以代码和拉取请求的形式提供任何帮助。
支持 OpenAL!
幸运的是,Surreal Engine 使用 OpenAL 进行音频输出。在 OpenAL 中编写了一个简单的 hello world 并使用 Emscripten 在 WebAssembly 中将其组装后,我清楚地意识到一切是多么简单,然后我开始移植声音。
经过几个小时的调试,很明显 Emscripten 的 OpenAL 实现存在几个错误,例如,在初始化读取单通道数时,该方法返回无限数,并且在尝试初始化无限大小的向量后,C++崩溃并出现异常向量::length_error。
我们通过将单声道数量硬编码为 2048 来解决这个问题:
alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);
#if __EMSCRIPTEN__
monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?
#endif
有网络吗?
Surreal Engine目前不支持在线游戏,支持与机器人一起玩,但我们需要有人为这些机器人编写AI。理论上,您可以使用Websockets在WebAssembly/Emscripten上实现网络游戏。
结论
总之,我想说,由于使用了 Emscripten 移植的库,以及我过去在 WebAssembly 中使用 C++ 实现游戏的经验,Surreal Engine 的移植非常顺利在 Emscripten 上。以下是有关该主题的知识来源和存储库的链接。
M-M-M-杀死怪物!
此外,如果您想帮助该项目,最好使用 WebGL/OpenGL ES 渲染代码,请在 Telegram 中给我写信:
https://t.me/demenscave
链接
https://demensdeum.com/demos/SurrealEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten
https://github.com/dpjudas/SurrealEngine