在这篇文章中,我将描述如何将 Surreal Engine 游戏引擎移植到 WebAssembly。
https://demensdeum.com/demos/SurrealEngine/”
超现实引擎–一个游戏引擎,实现了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 的构建标志,下面是项目文件中的示例:
set(CMAKE_CXX_FLAGS "-s MIN_WEBGL_VERSION=2
-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")
构建脚本本身:
clear
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

接下来,让我们准备 index.html,其中包括项目文件系统预加载器。为了上传到网络,我使用了 Unreal Tournament Demo 版本 338。正如您从 CMake 文件中看到的,解压后的游戏文件夹已添加到构建目录中,并作为 Emscripten 的预加载文件进行链接。
主要代码更改
然后我们必须改变游戏的游戏循环,不能无限循环,这会导致浏览器冻结,而是需要使用 emscripten_set_main_loop,我在 2017 年的笔记中写过这个功能“将 SDL C++ 游戏移植到 HTML5 (Emscripten)”
我们将退出 while 循环的代码更改为 if,然后在全局范围内显示包含游戏循环的游戏引擎的主类,并编写一个将从全局对象调用游戏循环步骤的全局函数:
#if __EMSCRIPTEN__
#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显示,渲染模型+贴图,两周就完成了。这也许是该项目中最困难的部分。实现超现实引擎渲染的全部功能还有很多工作要做,因此欢迎读者以代码和拉取请求的形式提供任何帮助。
支持 OpenAL!
幸运的是,Surreal Engine 使用 OpenAL 进行音频输出。在 OpenAL 中编写了一个简单的 hello world 并使用 Emscripten 在 WebAssembly 中将其组装后,我清楚地意识到一切是多么简单,然后我开始移植声音。
经过几个小时的调试,我们发现 Emscripten 的 OpenAL 实现存在几个错误,例如,在初始化读取单声道通道数时,该方法返回无限数,在尝试初始化无限大小的向量后,C++ 崩溃并出现异常 vector::length_error。
我们通过将单声道数量硬编码为 2048 来解决这个问题:
alcGetIntegerv(alDevice, ALC_MONO_SOURCES, 1, &monoSources);
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 移植的库,以及我过去在 Emscripten 上使用 C++ 为 WebAssembly 实现游戏的经验,Surreal Engine 的移植非常顺利。以下是有关该主题的知识来源和存储库的链接。
M-M-M-杀死怪物!
另外,如果您想帮助该项目,最好使用 WebGL/OpenGL ES 渲染代码,请在 Telegram 中给我写信:
https://t.me/demenscave
链接
https://demensdeum.com/demos/SurrealEngine/
Leave a Reply