Wie Slow Roads sowohl Gamer als auch Entwickler fasziniert und die überraschenden Möglichkeiten von 3D im Browser hervorhebt

Entdecken Sie das Potenzial von WebGL mit der unendlichen, prozedural generierten Landschaft dieses Casual-Fahrspiels.

Slow Roads ist ein Casual-Rennspiel mit Schwerpunkt auf endlos per Zufallsgenerator generierten Landschaften, die im Browser als WebGL-Anwendung gehostet werden. Für viele mag eine so intensive Erfahrung im begrenzten Kontext des Browsers fehl am Platz erscheinen. Tatsächlich war es eines meiner Ziele mit diesem Projekt, diese Einstellung zu ändern. In diesem Artikel erkläre ich einige der Techniken, mit denen ich die Leistungshürde überwunden habe, um das oft übersehene Potenzial von 3D im Web zu zeigen.

3D-Entwicklung im Browser

Nach der Veröffentlichung von „Langsame Straßen“ kam immer wieder derselbe Kommentar: „Ich wusste nicht, dass das im Browser möglich ist.“ Wenn Sie diese Meinung teilen, sind Sie sicherlich nicht in der Minderheit. Laut der Umfrage State of JS 2022 haben etwa 80% der Entwickler noch nicht mit WebGL experimentiert. Ich finde es schade, dass so viel Potenzial verschenkt wird, vor allem bei browserbasierten Spielen. Mit Slow Roads möchte ich WebGL weiter in den Vordergrund rücken und vielleicht die Anzahl der Entwickler verringern, die vor der Formulierung „Hochleistungs-JavaScript-Game-Engine“ zurückschrecken.

WebGL mag für viele mysteriös und komplex erscheinen, aber in den letzten Jahren haben sich die Entwicklungsumgebungen zu leistungsfähigen und praktischen Tools und Bibliotheken entwickelt. Für Frontend-Entwickler ist es jetzt einfacher denn je, 3D-UX in ihre Arbeit einzubinden, auch ohne Vorkenntnisse in Computergrafik. Three.js, die führende WebGL-Bibliothek, bildet die Grundlage für viele Erweiterungen, darunter react-three-fiber, mit der 3D-Komponenten in das React-Framework eingebunden werden. Es gibt jetzt auch umfassende webbasierte Spiele-Editoren wie Babylon.js oder PlayCanvas, die eine vertraute Benutzeroberfläche und integrierte Toolchains bieten.

Trotz der bemerkenswerten Nützlichkeit dieser Bibliotheken sind ehrgeizige Projekte jedoch an technische Grenzen gebunden. Skeptiker gegenüber browserbasierten Spielen könnten darauf hinweisen, dass JavaScript ein einzelner Thread ist und Ressourcen begrenzt sind. Wenn Sie diese Einschränkungen jedoch berücksichtigen, können Sie den verborgenen Wert nutzen: Keine andere Plattform bietet die gleiche sofortige Barrierefreiheit und Massenkompatibilität wie der Browser. Nutzer mit einem beliebigen browserfähigen System können mit nur einem Klick mit der Wiedergabe beginnen, ohne Anwendungen installieren oder sich in Diensten anmelden zu müssen. Außerdem haben Entwickler die Möglichkeit, robuste Front-End-Frameworks für die Erstellung von UIs oder die Netzwerkverwaltung für Multiplayer-Modi zu verwenden. Diese Werte machen den Browser meiner Meinung nach zu einer hervorragenden Plattform sowohl für Spieler als auch für Entwickler. Wie Slow Roads zeigt, lassen sich die technischen Einschränkungen oft auf ein Designproblem zurückführen.

Reibungslose Leistung auf langsamen Straßen

Da die Hauptelemente von Slow Roads eine schnelle Bewegung und eine aufwendige Szeneriegenerierung beinhalten, war die flüssige Leistung bei jeder Designentscheidung entscheidend. Meine Hauptstrategie bestand darin, mit einem reduzierten Gameplay-Design zu beginnen, das es ermöglichte, innerhalb der Engine-Architektur kontextbezogene Verknüpfungen zu erstellen. Der Nachteil besteht darin, dass im Streben nach Minimalismus einige nützliche Funktionen geopfert werden. Das Ergebnis ist jedoch ein maßgeschneidertes, hyperoptimiertes System, das in verschiedenen Browsern und auf verschiedenen Geräten gut funktioniert.

Im Folgenden finden Sie eine Aufschlüsselung der wichtigsten Komponenten, die Slow Roads schlank halten.

Die Umgebungs-Engine an das Gameplay anpassen

Als Hauptkomponente des Spiels ist die Engine zur Umgebungserzeugung unvermeidlich teuer und beansprucht zu Recht den größten Teil des Speicher- und Rechenbudgets. Der Trick besteht darin, die intensiven Berechnungen über einen bestimmten Zeitraum hinweg zu planen und zu verteilen, damit die Framerate nicht durch Leistungsspitzen unterbrochen wird.

Die Umgebung besteht aus geometrischen Kacheln, die sich in Größe und Auflösung unterscheiden (als „Detailebenen“ oder LoDs kategorisiert), je nachdem, wie nah sie an der Kamera erscheinen. In typischen Spielen mit einer kostenlos beweglichen Kamera müssen ständig verschiedene LODs geladen und entladen werden, um die Umgebung des Spielers zu detaillieren, wohin er sich auch bewegt. Das kann teuer und ineffizient sein, insbesondere wenn die Umgebung selbst dynamisch generiert wird. Glücklicherweise kann diese Konvention bei langsamen Straßen vollständig aufgehoben werden, da davon auszugehen ist, dass der Nutzer auf der Straße bleiben sollte. Stattdessen kann die detaillierte Geometrie für den schmalen Korridor reserviert werden, der direkt an der Route liegt.

Ein Diagramm, das zeigt, wie die Generierung der Straße im Voraus proaktives Planen und Caching der Umgebungsgenerierung ermöglicht.
Eine Ansicht der Umgebungsgeometrie in „Langsame Straßen“, die als Wireframe dargestellt wird und Korridore mit hochauflösender Geometrie entlang der Straße zeigt. Weit entfernte Teile der Umgebung, die nie aus der Nähe betrachtet werden sollten, werden mit einer viel niedrigeren Auflösung gerendert.

Die Mittellinie der Straße selbst wird weit vor der Ankunft des Spielers generiert, sodass genau vorhergesagt werden kann, wann und wo die Umgebungsdetails benötigt werden. Das Ergebnis ist ein schlankes System, mit dem sich kostenintensive Arbeit proaktiv planen lässt. Dabei wird zu jedem Zeitpunkt nur das Minimum generiert, das erforderlich ist, und es werden keine unnötigen Ressourcen für Details verschwendet, die nicht zu sehen sind. Diese Technik ist nur möglich, weil die Straße ein einzelner, nicht verzweigter Pfad ist. Dies ist ein gutes Beispiel für Kompromisse beim Gameplay, die architektonische Abkürzungen berücksichtigen.

Ein Diagramm, das zeigt, wie die Generierung der Straße im Voraus proaktives Planen und Caching der Umgebungsgenerierung ermöglicht.
Wenn Sie einen bestimmten Abstand entlang der Straße betrachten, können Umgebungs-Chunks vorab abgerufen und nach und nach generiert werden, kurz bevor sie benötigt werden. Außerdem können alle Chunks, die in naher Zukunft noch einmal besucht werden, identifiziert und im Cache gespeichert werden, um unnötige Regenerationen zu vermeiden.

Die Gesetze der Physik nicht zu sehr beachten

Die Physiksimulation ist die zweitgrößte Rechenanforderung der Umgebungsengine. Slow Roads verwendet eine benutzerdefinierte, minimale Physik-Engine, die alle verfügbaren Abkürzungen nutzt.

Der größte Vorteil besteht darin, zu vermeiden, zu viele Objekte zu simulieren. Setzen Sie auf einen minimalistischen, zenartigen Kontext, indem Sie Dinge wie dynamische Kollisionen und zerstörbare Objekte ausschließen. Da davon ausgegangen wird, dass das Fahrzeug auf der Straße bleibt, können Kollisionen mit Objekten abseits der Straße vernünftigerweise ignoriert werden. Außerdem ermöglicht die Codierung der Straße als spärlich vorhandene Mittellinie elegante Tricks für eine schnelle Kollisionserkennung mit der Fahrbahn und den Leitplanken, die alle auf einer Entfernungsprüfung zum Straßenmittelpunkt basieren. Das Fahren im Gelände wird dann teurer, aber dies ist ein weiteres Beispiel für einen fairen Kompromiss, der zum Kontext des Gameplays passt.

Speicherbedarf verwalten

Als weitere vom Browser eingeschränkte Ressource ist es wichtig, den Arbeitsspeicher mit Bedacht zu verwalten – trotz der Tatsache, dass JavaScript automatisch beseitigt wird. Es kann leicht übersehen werden, aber selbst kleine Mengen an neuem Arbeitsspeicher innerhalb einer Gameloop können bei einer Ausführung mit 60 Hz zu erheblichen Problemen führen. Große Garbage Collection-Vorgänge verbrauchen nicht nur die Ressourcen des Nutzers in einem Kontext, in dem er wahrscheinlich Multitasking ausführt, sondern können auch mehrere Frames in Anspruch nehmen, was zu merklichen Rucklern führt. Um dies zu vermeiden, kann der Speicher für Schleifen bei der Initialisierung in Klassenvariablen vorab zugewiesen und in jedem Frame wiederverwendet werden.

Ein Vorher-Nachher-Vergleich des Speicherprofils während der Optimierung der Codebasis für „Langsame Straßen“ mit deutlichen Einsparungen und einer geringeren Rate der automatischen Speicherbereinigung.
Die Gesamtspeichernutzung ändert sich kaum, aber durch die Voralokasion und das Recycling von Schleifenspeicher kann die Auswirkung teurer Garbage Collection-Vorgänge erheblich reduziert werden.

Es ist auch sehr wichtig, dass größere Datenstrukturen wie Geometrien und zugehörige Datenpuffer effizient verwaltet werden. In einem unendlich generierten Spiel wie „Slow Roads“ befindet sich der Großteil der Geometrie auf einer Art Laufband. Sobald ein altes Stück in die Ferne verschwindet, können seine Datenstrukturen gespeichert und für ein anstehendes Stück der Welt wiederverwendet werden. Dieses Designmuster wird als Objekt-Pooling bezeichnet.

Diese Praktiken helfen, die schlanke Ausführung zu priorisieren, wobei die Code-Einfachheit etwas eingeschränkt wird. Bei Hochleistungsanwendungen ist es wichtig, zu beachten, dass sich praktische Funktionen manchmal vom Client zum Vorteil des Entwicklers leihen. Methoden wie Object.keys() oder Array.map() sind beispielsweise sehr praktisch, aber es ist leicht zu übersehen, dass für jede Methode ein neues Array für den Rückgabewert erstellt wird. Wenn Sie die Funktionsweise solcher Blackboxes kennen, können Sie Ihren Code optimieren und unerwartete Leistungseinbußen vermeiden.

Ladezeit mit prozedural generierten Assets reduzieren

Die Laufzeitleistung sollte für Spieleentwickler zwar an erster Stelle stehen, die üblichen Axiome zur anfänglichen Ladezeit von Webseiten gelten aber weiterhin. Nutzer sind möglicherweise nachsichtiger, wenn sie wissentlich auf umfangreiche Inhalte zugreifen. Lange Ladezeiten können jedoch trotzdem die Nutzerfreundlichkeit beeinträchtigen, wenn nicht sogar die Nutzerbindung. Spiele erfordern oft große Assets in Form von Texturen, Sounds und 3D-Modellen. Diese sollten mindestens sorgfältig komprimiert werden, wo Details eingespart werden können.

Alternativ können Sie Assets auf dem Client prozedural generieren, um lange Übertragungen zu vermeiden. Das ist ein großer Vorteil für Nutzer mit langsamen Verbindungen und gibt den Entwicklern mehr direkte Kontrolle darüber, wie ihr Spiel aufgebaut ist – nicht nur beim ersten Ladeschritt, sondern auch bei der Anpassung der Detailebenen für verschiedene Qualitätseinstellungen.

Ein Vergleich, der veranschaulicht, wie sich die Qualität der prozedural generierten Geometrie in langsamen Straßen dynamisch an die Leistungsanforderungen des Nutzers anpassen lässt.

Die meisten Geometrien in Slow Roads sind prozedural generiert und einfach. Benutzerdefinierte Shader kombinieren mehrere Texturen, um Details hinzuzufügen. Der Nachteil ist, dass diese Texturen große Assets sein können. Es gibt jedoch weitere Möglichkeiten zur Einsparung. Mit Methoden wie der stochastischen Texturierung können Sie mit kleinen Quelltexturen mehr Details erzielen. Im Extremfall ist es auch möglich, Texturen mit Tools wie texgen.js vollständig auf dem Client zu generieren. Das gilt auch für Audio. Mit der Web Audio API können Sie Ton mit Audioknoten generieren.

Dank der prozeduralen Assets dauert das Generieren der ursprünglichen Umgebung durchschnittlich nur 3, 2 Sekunden. Um die geringe Downloadgröße zu nutzen, werden neue Besucher von einem einfachen Splashscreen begrüßt und die ressourcenintensive Szeneninitialisierung wird erst nach dem Drücken einer Bestätigungsschaltfläche gestartet. Außerdem dient er als praktischer Puffer für abgebrochene Sitzungen, wodurch die Übertragung dynamisch geladener Assets minimiert wird.

Ein Histogramm der Ladezeiten mit einem starken Anstieg in den ersten drei Sekunden, der über 60% der Nutzer ausmacht, gefolgt von einem schnellen Rückgang. Das Histogramm zeigt, dass bei über 97% der Nutzer die Ladezeit weniger als 10 Sekunden beträgt.

Agiler Ansatz für die späte Optimierung

Ich habe die Codebasis von Slow Roads immer als experimentell betrachtet und daher einen äußerst agilen Ansatz für die Entwicklung gewählt. Bei der Arbeit mit einer komplexen und sich schnell entwickelnden Systemarchitektur ist es oft schwierig vorherzusagen, wo wichtige Engpässe auftreten können. Der Schwerpunkt sollte darauf liegen, die gewünschten Funktionen schnell und nicht sauber zu implementieren und dann rückwärts zu arbeiten, um die Systeme dort zu optimieren, wo es wirklich zählt. Der Leistungsprofiler in den Chrome-Entwicklertools ist für diesen Schritt unverzichtbar und hat mir geholfen, einige schwerwiegende Probleme mit früheren Versionen des Spiels zu diagnostizieren. Ihre Zeit als Entwickler ist wertvoll. Sie sollten also nicht zu viel Zeit damit verbringen, über Probleme nachzudenken, die sich als unbedeutend oder redundant erweisen könnten.

Nutzerfreundlichkeit im Blick behalten

Bei der Implementierung all dieser Tricks ist es wichtig, dafür zu sorgen, dass das Spiel in der Praxis wie erwartet funktioniert. Die Unterstützung einer Vielzahl von Hardwarefunktionen ist ein wichtiger Aspekt bei der Entwicklung von Spielen. Webspiele können jedoch auf ein viel breiteres Spektrum ausgerichtet werden, das sowohl High-End-Computer als auch 10 Jahre alte Mobilgeräte umfasst. Am einfachsten ist es, Einstellungen für die Anpassung der wahrscheinlichsten Engpässe in Ihrer Codebasis anzubieten, sowohl für GPU- als auch für CPU-intensive Aufgaben, wie sie von Ihrem Profiler ermittelt wurden.

Das Profiling auf Ihrem eigenen Computer kann jedoch nur begrenzte Informationen liefern. Daher ist es sinnvoll, die Feedbackschleife mit Ihren Nutzern auf irgendeine Weise zu schließen. Bei „Langsame Straßen“ führe ich einfache Analysen durch, die neben der Leistung auch Kontextfaktoren wie die Bildschirmauflösung berücksichtigen. Diese Analysen werden über Socket.io an ein einfaches Node-Backend gesendet, zusammen mit jeglichem schriftlichen Feedback, das der Nutzer über das In-Game-Formular sendet. In der Anfangsphase wurden mit diesen Analysen viele wichtige Probleme erkannt, die mit einfachen Änderungen an der UX behoben werden konnten. So wurde beispielsweise das Einstellungsmenü hervorgehoben, wenn eine konstant niedrige FPS erkannt wurde, oder es wurde gewarnt, dass ein Nutzer die Hardwarebeschleunigung aktivieren muss, wenn die Leistung besonders schlecht ist.

Langsame Straßen vor Ihnen

Selbst nach all diesen Maßnahmen gibt es einen erheblichen Teil der Spieler, die mit niedrigeren Einstellungen spielen müssen – vor allem diejenigen, die leichte Geräte ohne GPU verwenden. Die verschiedenen Qualitätseinstellungen führen zwar zu einer relativ gleichmäßigen Leistungsverteilung, aber nur 52% der Spieler erreichen mehr als 55 FPS.

Eine Matrix, die durch die Einstellung der Entfernung und der Detaileinstellung definiert ist und die durchschnittlichen Frames pro Sekunde bei verschiedenen Kombinationen zeigt. Die Verteilung ist ziemlich gleichmäßig zwischen 45 und 60 verteilt, wobei 60 das Ziel für eine gute Leistung ist. Nutzer mit niedrigen Einstellungen haben in der Regel eine niedrigere FPS als Nutzer mit hohen Einstellungen. Dies unterstreicht die Unterschiede in der Clienthardware.
Hinweis: Diese Daten sind etwas verzerrt, da Nutzer ihren Browser häufig mit deaktivierter Hardwarebeschleunigung verwenden, was oft zu einer künstlich niedrigen Leistung führt.

Glücklicherweise gibt es noch viele Möglichkeiten, die Leistung zu steigern. Neben weiteren Rendering-Tricks zur Reduzierung der GPU-Anforderung hoffe ich, in naher Zukunft mit Webworkern zu experimentieren, um die Umgebungsgenerierung zu parallelisieren. Möglicherweise wird es auch erforderlich sein, WASM oder WebGPU in die Codebasis einzubinden. Je mehr Speicherplatz ich freigeben kann, desto reicher und vielfältiger werden die Umgebungen. Das ist das anhaltende Ziel für den Rest des Projekts.

Als Hobbyprojekt war Slow Roads eine überaus erfüllende Möglichkeit, zu zeigen, wie überraschend ausgefeilt, leistungsfähig und beliebt Browserspiele sein können. Wenn ich Ihr Interesse an WebGL geweckt habe, sollten Sie wissen, dass Slow Roads technologisch gesehen nur ein sehr einfaches Beispiel für die Möglichkeiten dieser Technologie ist. Ich empfehle den Lesern dringend, sich das Three.js-Showcase anzusehen. Wer sich insbesondere für die Entwicklung von Webspielen interessiert, ist in der Community unter webgamedev.com willkommen.