Die Meta

Die Optimierung der Spielschleife: Der Kampf um das Frame-Pacing im Browser

Für den flüchtigen Betrachter ist ein Spiel lediglich eine Abfolge von bewegten Bildern. Für einen Entwickler hingegen ist ein Spiel eine unerbittliche Hochgeschwindigkeitsuhr. Bei Retro-Emulatoren und HTML5-Arcade-Titeln ist diese Uhr gnadenlos. Wenn das Timing auch nur um wenige Millisekunden verrutscht, knistert der Ton, das Scrollen “ruckelt” und das Gameplay wirkt träge.

Bei Rec0m88, die größte technische Hürde besteht nicht nur darin, das Spiel zum Laufen zu bringen – sondern darin, es zu meistern Frame-Taktung. JavaScript wurde ursprünglich für das Scrollen von Text und das Klicken auf Schaltflächen entwickelt, nicht für die Verwaltung der Sub-Millisekunden-Genauigkeit, die eine Arcade-Platine aus den 1990er Jahren erfordert. Hier erfahren Sie, wie wir die Engine des Browsers so gezähmt haben, dass sie ein flüssiges, konstantes Gameplay mit 60 FPS liefert.


Der V-Sync-Ghost: Warum 60 Hz nicht immer 60 Hz sind

Die meisten Retro-Konsolen wurden für NTSC-Fernseher entwickelt, die mit genau 59,94 Hz. Moderne Computermonitore arbeiten jedoch in der Regel mit einer festen 60 Hz, 120 Hz, oder 144 Hz.

Wenn man ein Spiel mit 59,94 Hz auf einem 60-Hz-Monitor ausführt, gerät man in einen mathematischen Albtraum. Alle paar Sekunden “überspringt” der Monitor ein Bild, weil die Timings nicht perfekt zusammenpassen. Dies führt dazu, dass Mikro-Ruckeln—dieser nervige “Sprung”, den man sieht, wenn eine Figur über den Bildschirm läuft.

Unsere Lösung: requestAnimationFrame und die Web-Audio-Uhr

In einer Standard-Webanwendung verwenden Entwickler setTimeout oder setInterval um Code auszuführen. Diese sind für Spiele zu “unpräzise”; sie können vom Browser verzögert werden, wann immer dieser ausgelastet ist.

Stattdessen verwendet Rec0m88 ein Dual-Takt-System:

  1. Visuelle Synchronisation: Wir verwenden requestAnimationFrame (rAF). Damit wird dem Browser mitgeteilt: “Führe den nächsten Frame des Spiels erst dann aus, wenn der Monitor bereit ist, ihn darzustellen.” Dadurch wird “Screen Tearing” vermieden und sichergestellt, dass das Video seidenweich läuft.
  2. Audiosynchronisation: Da die Bildwiederholfrequenz des Monitors schwanken kann, verwenden wir die Zeit im Web-Audio-API-Kontext als unser “Metronom”. Die Web-Audio-Uhr ist der präziseste Timer im Browser. Indem wir unsere WebAssembly (WASM)-Spielschleife an den Audio-Puffer koppeln, stellen wir sicher, dass der Ton niemals gegenüber dem Geschehen aus dem Takt gerät.

Das Knacken im Ton: Behebung des “Puffer-Unterlaufs”

Hast du schon einmal ein Online-Spiel gespielt, bei dem der Ton knackt oder rauscht, wenn du einen neuen Tab öffnest? Das ist ein Puffer-Unterlauf. Das liegt daran, dass der Hauptthread des Browsers zu sehr ausgelastet war, um der Soundkarte die nächsten paar Millisekunden Ton zu “liefern”.

Um dieses Problem in Rec0m88 zu beheben, haben wir die Audioverarbeitung auf einen Web-Mitarbeiter.

  • Entkoppelte Logik: Die Spielelogik läuft auf einem Thread, die Audiomischung auf einem anderen.
  • Ringpuffer: Wir verwalten einen winzigen “Look-Ahead”-Puffer mit Audio-Samples. Wenn die CPU-Auslastung für eine Millisekunde sprunghaft ansteigt, greift der Audio-Player auf diesen Puffer zurück, sodass das System Zeit hat, sich zu erholen, ohne dass der Nutzer jemals ein “Knacken” hört.”

Umgang mit Jitter in einer JIT-Welt

JavaScript ist eine JIT (Just-in-Time) kompilierte Sprache. Das bedeutet, dass der Browser den Code während des Spielens ständig “optimiert”. Manchmal führen diese Optimierungen zu einem kurzzeitigen “Hängenbleiben” (oft als “Jank” bezeichnet).

Um eine perfekte Bildrate zu erreichen, minimieren wir die “Garbage Collection” (GC). Bei der GC hält der Browser alle Vorgänge an, um nicht genutzten Speicher freizugeben. Wir vermeiden dies durch:

  • Objekt-Pooling: Wir verwenden dieselben Speicherblöcke für Spiel-Sprites und P2P-Pakete wieder, anstatt neue anzulegen.
  • Typisierte Arrays: Wir verwenden Uint8Array und Float32Array zur Verarbeitung von Spieldaten. Diese werden in einem fest zugewiesenen Speicherbereich gespeichert, den der Browser nicht so oft “bereinigen” muss, wodurch die Spielschleife vorhersehbar bleibt.

Die Latenzzeit vom Eingangssignal bis zum Photon

“Bei ”Frame Pacing“ geht es nicht nur darum, was man sieht, sondern auch darum, was man spürt. Die Zeit zwischen dem Drücken einer Taste und dem Farbwechsel eines Pixels wird als Latenzzeit vom Eingangssignal bis zum Photon.

Durch die Verwendung der Web-HID-API und die Gamepad-API, umgehen wir die standardmäßige “Keyboard Event”-Warteschlange des Browsers. Dadurch können wir Ihren Controller zu Beginn jedes Frames abfragen, unmittelbar bevor der WASM-Kern ausgeführt wird. So wird sichergestellt, dass Ihre Eingaben in der gleich danach Rahmen, der das “spritzige” Spielgefühl vermittelt, das Arcade-Fans erwarten.


Warum hochkarätige Websites Wert auf das richtige Timing legen

Der “Low Value Content”-Filter von Google berücksichtigt nicht nur den Text, sondern auch die Benutzererfahrung. Eine Website, die einen ruckelnden, stockenden Emulator bereitstellt, ist ein Tool “minderer Qualität”. Indem wir unsere Herausforderungen beim Frame-Pacing dokumentieren und eine hochpräzise Engine bereitstellen, zeigen wir, dass Rec0m88 eine Plattform auf professionellem Niveau ist.

Wir haben Hunderte von Stunden damit verbracht, das Zusammenspiel zwischen der Web Audio API, WebAssembly und der GPU zu optimieren, um sicherzustellen, dass die Wiedergabe eines Klassikers auf unserer Plattform nicht nur funktioniert, sondern sich auch richtig anfühlt.


Die Meta-Herausforderung: Öffnen Sie doch einmal den “Leistungsmonitor” Ihres Browsers, während Sie eines unserer Spiele spielen. Sie werden eine gleichmäßige, konstante Linie bei 60 FPS sehen – ein Beweis für die Leistungsfähigkeit unserer Frame-Pacing-Architektur.

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert