In diesem Beitrag beschreibe ich, wie ich die Surreal Engine-Spiel-Engine auf WebAssembly portiert habe.
https://demensdeum.com/demos/SurrealEngine/”
Surreal Engine – eine Spiel-Engine, die die meisten Funktionen der Unreal Engine 1 implementiert, berühmte Spiele auf dieser Engine – Unreal Tournament 99, Unreal, Deus Ex, Undying. Es bezieht sich auf klassische Engines, die hauptsächlich in einer Single-Threaded-Ausführungsumgebung arbeiteten.
Ich hatte ursprünglich die Idee, ein Projekt zu übernehmen, das ich nicht in einem angemessenen Zeitrahmen abschließen konnte, und so meinen Twitch-Followern zu zeigen, dass es Projekte gibt, die selbst ich nicht schaffen kann. Während meines ersten Streams wurde mir plötzlich klar, dass die Aufgabe, Surreal Engine C++ mithilfe von Emscripten auf WebAssembly zu portieren, machbar war.
Einen Monat später kann ich meine Gabel- und Motorbaugruppe auf WebAssembly demonstrieren:
https://demensdeum.com/demos/SurrealEngine/
Die Steuerung erfolgt wie im Original über die Tastaturpfeile. Als nächstes plane ich, es für die mobile Steuerung (Tachi) anzupassen und dabei die richtige Beleuchtung und andere grafische Funktionen des Unreal Tournament 99-Renderings hinzuzufügen.
Wo soll ich anfangen?
Als erstes möchte ich sagen, dass jedes Projekt mit Emscripten von C++ nach WebAssembly portiert werden kann. Die Frage ist nur, wie vollständig die Funktionalität sein wird. Wählen Sie ein Projekt, dessen Bibliotheksports bereits für Emscripten verfügbar sind; Im Fall der Surreal Engine haben Sie großes Glück, denn die Engine verwendet SDL 2, OpenAL – Bibliotheken. Sie sind beide auf Emscripten portiert. Als Grafik-API kommt allerdings Vulkan zum Einsatz, das derzeit für HTML5 nicht verfügbar ist, an der Implementierung von WebGPU wird gearbeitet, befindet sich aber ebenfalls im Entwurfsstadium und es ist auch nicht bekannt, wie einfach die weitere Portierung von Vulkan auf WebGPU nach der vollständigen Standardisierung sein wird. Daher musste ich meinen eigenen grundlegenden OpenGL-ES/WebGL-Renderer für Surreal Engine schreiben.
Das Projekt erstellen
Build-System in Surreal Engine – CMake, was auch die Portierung vereinfacht, da Emscripten seine nativen Builder – emcmake, emmake.
Der Surreal Engine-Port basierte auf dem Code meines neuesten Spiels in WebGL/OpenGL ES und C++ namens Death-Mask. Dadurch war die Entwicklung viel einfacher, ich hatte alle notwendigen Build-Flags und Codebeispiele dabei.
Einer der wichtigsten Punkte in CMakeLists.txt sind die Build-Flags für Emscripten, unten ist ein Beispiel aus der Projektdatei:
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")
Das Build-Skript selbst:
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

Als Nächstes bereiten wir index.html vor, das den Projektdateisystem-Preloader enthält. Zum Hochladen ins Web habe ich die Unreal Tournament Demo-Version 338 verwendet. Wie Sie der CMake-Datei entnehmen können, wurde der entpackte Spielordner zum Build-Verzeichnis hinzugefügt und als Preload-Datei für Emscripten verknüpft.
Hauptcodeänderungen
Dann mussten wir die Spielschleife des Spiels ändern, man kann keine Endlosschleife ausführen, das führt zum Einfrieren des Browsers, stattdessen muss man emscripten_set_main_loop verwenden, über diese Funktion habe ich in meiner Notiz von 2017 geschrieben „Portierung von SDL C++-Spielen nach HTML5 (Emscripten)“
Wir ändern den Code zum Beenden der while-Schleife in if, zeigen dann die Hauptklasse der Spiel-Engine, die die Spielschleife enthält, im globalen Bereich an und schreiben eine globale Funktion, die den Spielschleifenschritt vom globalen Objekt aus aufruft:
#if __EMSCRIPTEN__
#include <emscripten.h>
Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;
void emscripten_game_loop_step() {
EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();
}
#endif
Danach müssen Sie sicherstellen, dass in der Anwendung keine Hintergrundthreads vorhanden sind. Wenn ja, dann machen Sie sich bereit, sie für die Single-Thread-Ausführung neu zu schreiben, oder verwenden Sie die phtread-Bibliothek in Emscripten.
Der Hintergrund-Thread in Surreal Engine wird zum Abspielen von Musik verwendet. Vom Haupt-Engine-Thread kommen Daten über den aktuellen Titel, die Notwendigkeit, Musik abzuspielen, oder deren Fehlen. Anschließend erhält der Hintergrund-Thread über einen Mutex einen neuen Status und beginnt mit der Wiedergabe neuer Musik oder pausiert. Der Hintergrundthread wird auch zum Puffern von Musik während der Wiedergabe verwendet.
Meine Versuche, die Surreal Engine für Emscripten mit pthread zu erstellen, waren erfolglos, da die SDL2- und OpenAL-Ports ohne pthread-Unterstützung erstellt wurden und ich sie nicht aus Gründen der Musik neu erstellen wollte. Daher habe ich die Funktionalität des Hintergrundmusik-Streams mithilfe einer Schleife auf die Single-Threaded-Ausführung übertragen. Durch das Entfernen von pthread-Aufrufen aus dem C++-Code habe ich die Pufferung und Musikwiedergabe in den Hauptthread verschoben, damit es keine Verzögerungen gibt, und den Puffer um einige Sekunden erhöht.
Als nächstes werde ich spezifische Implementierungen von Grafik und Sound beschreiben.
Vulkan wird nicht unterstützt!
Ja, Vulkan wird in HTML5 nicht unterstützt, obwohl alle Marketingbroschüren die plattformübergreifende und breite Plattformunterstützung als Hauptvorteil von Vulkan darstellen. Aus diesem Grund musste ich meinen eigenen grundlegenden Grafikrenderer für einen vereinfachten OpenGL-Typ schreiben – ES, es wird auf mobilen Geräten verwendet, enthält manchmal nicht die modischen Funktionen des modernen OpenGL, lässt sich aber sehr gut auf WebGL portieren, was genau das ist, was Emscripten implementiert. Das Schreiben des grundlegenden Kachel-Renderings, des BSP-Renderings für die einfachste GUI-Anzeige und des Renderns von Modellen und Karten wurde in zwei Wochen abgeschlossen. Dies war vielleicht der schwierigste Teil des Projekts. Es liegt noch viel Arbeit vor uns, die volle Funktionalität des Surreal Engine-Renderings zu implementieren, daher ist jede Hilfe von Lesern in Form von Code und Pull-Requests willkommen.
OpenAL unterstützt!
Das große Glück ist, dass Surreal Engine OpenAL für die Audioausgabe verwendet. Nachdem ich eine einfache Hallo-Welt in OpenAL geschrieben und sie mit Emscripten in WebAssembly zusammengestellt hatte, wurde mir klar, wie einfach alles war, und ich machte mich an die Portierung des Sounds.
Nach mehreren Stunden des Debuggens stellte sich heraus, dass die OpenAL-Implementierung von Emscripten mehrere Fehler aufweist. Beispielsweise gab die Methode beim Initialisieren des Lesens der Anzahl der Monokanäle eine unendliche Zahl zurück, und nach dem Versuch, einen Vektor unendlicher Größe zu initialisieren, stürzte C++ mit der Ausnahme vector::length_error ab.
Wir haben es geschafft, dies zu umgehen, indem wir die Anzahl der Monokanäle auf 2048 fest codiert haben:
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
Gibt es ein Netzwerk?
Surreal Engine unterstützt derzeit kein Online-Spielen, das Spielen mit Bots wird unterstützt, aber wir brauchen jemanden, der KI für diese Bots schreibt. Theoretisch können Sie mithilfe von Websockets ein Netzwerkspiel auf WebAssembly/Emscripten implementieren.
Schlussfolgerung
Abschließend möchte ich sagen, dass die Portierung der Surreal Engine aufgrund der Verwendung von Bibliotheken, für die es Emscripten-Portierungen gibt, sowie meiner bisherigen Erfahrung bei der Implementierung eines Spiels in C++ für WebAssembly auf Emscripten recht reibungslos verlief. Nachfolgend finden Sie Links zu Wissensquellen und Repositorien zum Thema.
M-M-M-MONSTER TÖTEN!
Wenn Sie dem Projekt außerdem helfen möchten, vorzugsweise mit WebGL/OpenGL ES-Rendering-Code, dann schreiben Sie mir per Telegram:
https://t.me/demenscave
Links
https://demensdeum.com/demos/SurrealEngine/
Leave a Reply