// TECH //
Wie wir Online-Multiplayer-Browserspiele bauen
Die Entscheidungen hinter jedem Echtzeit-Multiplayer-Spiel, das wir für den Browser veröffentlichen: welches Netzwerkmodell wir wählen, was die Server tun, wie Fremde einander finden und wie Freunde mit einem einzigen Link ins Spiel kommen.
Ein Online-Multiplayer-Spiel in einem Browser-Tab hat dieselben Probleme wie eines auf der Konsole, nur mit weniger Werkzeugen zu ihrer Lösung: kein Installer, keine rohen Sockets außer WebSockets und WebRTC, und ein Spieler, der den Tab schließt, sobald sich etwas langsam anfühlt. So gehen wir bei MobX Games vor, auf der Ebene der Entscheidungen statt anhand eines einzelnen Spiels, und das ist der Grund, warum die Antworten so ausfallen, wie sie ausfallen.
Die erste Entscheidung: Wer besitzt die Wahrheit?
Jedes Multiplayer-Spiel muss vor allem anderen eine Frage beantworten: Wenn zwei Spieler uneins darüber sind, was passiert ist, wessen Version gilt? Es gibt drei gängige Antworten, und wir entscheiden pro Spiel statt pro Studio.
- Ein autoritativer Server. Ein Rechner führt das echte Spiel aus, die Spieler schicken Eingaben, und der Server schickt zurück, was geschehen ist. Das ist die richtige Antwort, wenn versteckte Informationen oder Cheating eine Rolle spielen, und zugleich die teuerste, denn der Server muss jedes Match simulieren, jeden Tick, so lange es dauert.
- Deterministischer Lockstep. Jeder Spieler führt dieselbe Simulation aus, und nur Befehle laufen über das Netzwerk. Wenn jeder Rechner vom selben Zustand startet und im selben Tick dieselben Befehle anwendet, berechnet jeder Rechner dasselbe Ergebnis. Eine Schlacht mit Hunderten Einheiten kostet ein paar hundert Bytes pro Sekunde, weil niemand je eine Einheitenposition verschickt.
- Client-Vorhersage mit Rollback. Das Spiel jedes Spielers nimmt an, dass der Gegner weitermacht wie zuletzt, und simuliert sofort voraus. Kommt die echte Eingabe an und weicht ab, spult das Spiel zum letzten gemeinsamen Frame zurück und spielt den Unterschied neu ab. Deine eigene Steuerung wartet nie auf das Netzwerk, und genau das braucht ein schnelles Actionspiel mit wenigen bewegten Objekten.
Strategiespiele mit vielen Einheiten passen zu Lockstep, weil der Datenverkehr mit dem wächst, was Spieler tun, und nicht mit dem, was auf dem Bildschirm zu sehen ist. Schnelle Physikspiele für zwei passen zu Rollback, weil die Verzögerung, die du bei deiner eigenen Steuerung spürst, wichtiger ist als alles andere. Einen vollständigen autoritativen Server setzen wir im Browser am seltensten ein, denn er macht aus jedem Match laufende Kosten.
Ein Relay statt eines Spielservers
Lockstep und Rollback lassen dem Server beide sehr wenig zu tun: Er muss nur Nachrichten zwischen den Spielern im selben Raum weiterreichen. Unser Backend ist deshalb ein Relay, und ein Relay passt gut zu serverlosem Hosting. Spieler verbinden sich per WebSocket mit einem AWS-API-Gateway-Endpunkt; eine kleine Lambda-Funktion nimmt jede Nachricht entgegen, schlägt in einer DynamoDB-Tabelle die anderen Spieler in diesem Raum nach und leitet sie weiter. Es gibt keinen Spielserver, der um vier Uhr morgens untätig darauf wartet, dass jemand spielt, und keinen Rechner, den wir patchen müssen.
Der Preis dafür: Ein serverloses Relay rechnet pro Nachricht ab, die Form des Datenverkehrs ist also genauso eine Kostenentscheidung wie eine Frage der Latenz. Wir schicken weniger, etwas größere Pakete und wiederholen in jedem Paket die letzten Eingaben, sodass sich ein verlorenes Paket selbst repariert. So bleibt die Rechnung proportional zur Zahl der Spieler. Wo das Netzwerk es erlaubt, öffnet das Spiel außerdem einen direkten WebRTC-Datenkanal zwischen den Spielern, ungeordnet und ohne erneute Übertragungen, denn eine verspätete Eingabe ist wertlos. Das Relay bleibt als Sicherheitsnetz: Öffnet sich der direkte Kanal nie, läuft das Match über das Relay weiter, statt zu scheitern.
Regionen und Matchmaking
Das Relay läuft in mehreren AWS-Regionen, denn die Lichtgeschwindigkeit über einen Ozean hinweg ist eine Verzögerung, die kein Code beseitigen kann. Ein Spiel wählt seine Region entweder, indem es anhand deiner Zeitzone rät, mit einer Einstellung zum Überschreiben, oder indem es zu jeder Region eine Verbindung öffnet und die behält, die zuerst antwortet.
Das Matchmaking versucht zuerst die langweilige Variante. Wartet eine öffentliche Lobby mit einem freien Platz und einem aktiven Host, trittst du ihr sofort bei: In einem echten Match zu landen schlägt das Warten auf ein perfekt faires. Andernfalls kommst du in eine Warteschlange, deren zulässiger Skill-Abstand wächst, je länger du wartest, und zwei Spieler werden nur gepaart, wenn beide Fenster den Abstand abdecken. So bekommt jemand, der lange wartet, keinen Neuling zugewiesen, den er zerlegen würde. Der Server führt dafür keine Timer. Dein Client fragt einfach alle paar Sekunden erneut an, und das hält den Matchmaker zustandslos und günstig. Wird ein Paar gefunden, starten beide Clients das Match zu einem vereinbarten Zeitpunkt und nicht in dem Moment, in dem jeder zufällig davon erfahren hat.
Nichts davon zaubert zu ruhigen Zeiten einen Gegner herbei. Matchmaking dauert Sekunden, wenn jemand anderes sucht, und ist Warten, wenn niemand sucht. Deshalb lässt dich jedes unserer Multiplayer-Spiele auch direkt gegen einen Freund spielen.
Lobby-Codes und Einladungslinks
Ein privates Match beginnt mit einem kurzen Lobby-Code, kurz genug, um ihn quer durch einen Raum laut vorzulesen oder am Handy einzutippen. Das Spiel des Hosts bietet außerdem einen Button „Einladung kopieren“, der einen Link mit dem Code in die Zwischenablage legt. Wer den Link öffnet, landet auf der Seite des Spiels auf mobx.games, die Seite reicht den Code an das Spiel weiter, und das Cover wird übersprungen, denn ein Link von einem Freund ist eine Bitte mitzuspielen und keine Einladung zum Lesen.
Reconnects und Determinismus-Prüfungen
Lockstep funktioniert nur, wenn wirklich jeder Rechner dasselbe berechnet, und Determinismus ist eine Regel, die der Code befolgt, keine Hoffnung. Nichts innerhalb der Simulation liest die Systemuhr oder eine Zufallsquelle ohne Seed. Würfe kommen aus einem Generator mit festem Seed, den jeder Spieler in derselben Reihenfolge weiterschaltet, die Simulation läuft auf festen Ticks statt auf Frame-Zeit, und alles, was über eine Sammlung iteriert, tut das in stabiler Reihenfolge. Darüber hinaus bildet jeder Client in regelmäßigen Abständen einen Hash seines Spielzustands und vergleicht das Ergebnis mit den anderen Spielern. Eine Abweichung fällt in dem Tick auf, in dem sie auftritt, statt dass auf zwei Bildschirmen stillschweigend zwei verschiedene Matches ablaufen.
Verbindungen brechen ab, besonders auf Handys. Weil der Bot-Gegner in derselben deterministischen Simulation läuft und keinen Netzwerkverkehr braucht, kann ein Spieler, der die Verbindung verliert, auf jedem Rechner im selben Tick an den Bot übergeben werden, und das Match läuft für alle anderen weiter. Ein Spieler, der zurückkommt, holt sich einen Snapshot des aktuellen Zustands und nimmt seinen Platz wieder ein. In einem Rollback-Spiel überbrücken die wiederholten Eingaben in jedem Paket kurze Lücken, ohne dass es jemand bemerkt.
Konten, die anonym beginnen
Niemand sollte sich vor dem ersten Match registrieren müssen. Sobald ein Spiel zum ersten Mal eine Identität braucht, legt unser Konto-Dienst ein anonymes Gerätekonto an: eine Spieler-ID und ein Token, keine E-Mail-Adresse und kein Passwort. Wertungen, Freunde und Fortschritt hängen vom ersten Spiel an an dieser ID. Möchtest du dasselbe Konto auf Handy und Laptop, fügst du eine E-Mail-Adresse hinzu und bestätigst einen sechsstelligen Code, und aus dem anonymen Konto wird ein dauerhaftes, ohne dass etwas verloren geht. Das Token liegt in einem Cookie auf der übergeordneten Domain, sodass jedes Spiel auf seiner eigenen mobx.games-Subdomain denselben Spieler erkennt, ohne erneut zu fragen.
Der Betrieb in einem Portal-Iframe
Auf mobx.games läuft jedes Spiel in einem Sandbox-Iframe auf seiner eigenen Subdomain. Eine eigene Origin bedeutet, dass das Spiel die Seite um sich herum nicht lesen kann und die Seite nicht das Spiel, und jedes Spiel hält seine Spielstände und seinen Cache getrennt von allen anderen. Der Frame bekommt genau das, was ein Multiplayer-Spiel braucht: Vollbild, Pointer Lock, Autoplay für Ton und Zugriff auf die Zwischenablage, damit der Button „Einladung kopieren“ im Frame funktioniert.
Spiel und Seite sprechen über einen kleinen postMessage-Vertrag miteinander. Das Spiel bittet die Seite, das Anmeldefenster zu öffnen, denn die Seite besitzt die Anmeldung; die Seite teilt dem Spiel mit, wenn du dich an- oder abmeldest, und das Spiel fragt seinen Spieler ohne Neuladen erneut ab. Beide Seiten prüfen, woher eine Nachricht kommt, und senden nur an die genau erwartete Origin.
Discord-Activities
Eine Discord-Activity ist ein Webspiel, das innerhalb von Discord läuft, in einem Sprachkanal oder einem Chat. Das macht sie zu einem natürlichen Zuhause für Multiplayer: Die Leute, mit denen du spielen willst, sind schon im Raum. Derselbe Build, der auf mobx.games läuft, kann als Activity dienen. Discord lädt ihn über einen eigenen Proxy, sodass das Spiel an seiner Adresse erkennt, dass es in Discord läuft, und erst dann das Embedded App SDK von Discord lädt; jeder andere Build lädt es nie herunter. Die Anmeldung wird zu einer Discord-Autorisierung, deren Code eine kleine serverlose Funktion gegen ein Token eintauscht, sodass das App-Secret nie den Browser erreicht, und Einladungen laufen über Discords eigenen Einladungsdialog. Alles darunter, das Relay, die Regionen und die Simulation, bleibt genau gleich.
Was wir jemandem raten würden, der anfängt
- Wähle das Netzwerkmodell nach dem Spiel, nicht nach Gewohnheit: viele Einheiten sprechen für Lockstep, zwei schnelle Spieler für Rollback.
- Mach den Server so einfach, wie das Spiel es zulässt. Ein Relay ist günstig, zustandslos und leicht in mehreren Regionen zu betreiben.
- Behandle Determinismus als Regel mit einer Prüfung dahinter, und bilde einen Hash deines Zustands.
- Lass Leute zuerst spielen und sich später registrieren, und mach den Link eines Freundes zum kürzesten Weg in ein Match.