1 Punkte von GN⁺ 2024-06-14 | 1 Kommentare | Auf WhatsApp teilen
  • Die Serious Engine 1 setzt Singleplayer, Multiplayer und Demo-Wiedergabe auf dieselbe deterministische Simulation auf und zeichnet bzw. überträgt pro Tick nicht den vollständigen Zustand, sondern Spieleraktionen und Game-Stream-Blöcke
  • Demo und Multiplayer teilen sich einen anfänglichen Spielzustand und wenden danach Eingabedeltas wie CPlayerAction an, weshalb die Deterministik von Zufalls-Seed und Spiellogik der Schlüssel zur Synchronisation ist
  • Die Netzwerkschicht verarbeitet über UDP Sequenznummern, ACKs, Wiederholungen, Bandbreitenbegrenzung und verbindungsspezifische Puffer selbst, um das Spiel auch über langsame Leitungen spielbar zu halten
  • CNetworkMessage bietet Serialisierung, LZ77-/LZRW1-Komprimierung und XOR-basiertes Delta-Encoding, aber Nachrichten einschließlich Chat können unverschlüsselt sein und nur komprimiert übertragen werden
  • Das Client-Server-Modell von Serious Sam spart Bandbreite, indem jeder Client seine eigene Simulation beibehält, benötigt dafür aber Hilfsmechanismen wie Synchronisationsprüfung, Vorhersage, erneute Übertragung und CRC-Prüfung

Gemeinsame Simulationsstruktur der Serious Engine

  • Der Quellcode der Serious Engine 1 wurde 2016 unter GNU GPL v2 veröffentlicht; die Analyse basiert auf dem Lesen und Debuggen dieser freigegebenen Codebasis
  • Serious Sam wurde von Anfang an als Multiplayer-Spiel entworfen, und auch die Singleplayer-Kampagne arbeitet intern wie ein Sonderfall der Multiplayer-Struktur
  • Der Engine unterstützte Modi sind:
    • Offline-Singleplayer-Kampagne
    • Online-, LAN- und lokaler Koop-Modus sowie mehrere Spielmodi
    • Splitscreen, bei dem mehrere Spieler an einem Client teilnehmen
    • Demo-Aufzeichnung und -Wiedergabe

Demo-Aufzeichnung: Aktionen statt vollständigem Zustand speichern

  • Würde eine Demo pro Tick den gesamten Spielzustand speichern, würden die Dateien groß werden. Daher speichert die Serious Engine zu Beginn der Aufzeichnung einmal den vollständigen Spielzustand und danach pro Tick Game-Stream-Blöcke
  • Ein Game-Stream-Block enthält folgende Nachrichtentypen:
    • MSG_SEQ_ALLACTIONS: Spieleraktionen
    • MSG_SEQ_ADDPLAYER: Spieler hinzufügen
    • MSG_SEQ_REMPLAYER: Spieler entfernen
    • MSG_SEQ_PAUSE: Pause oder Fortsetzen
    • MSG_SEQ_CHARACTERCHANGE: Änderung von Spielercharakter-Eigenschaften
  • Zentral ist MSG_SEQ_ALLACTIONS; die Engine deserialisiert für jeden aktiven Spieler ein CPlayerAction-Objekt und wendet es auf CPlayerTarget an
  • CPlayerAction enthält den Spielerzustand:
    • Bewegungsgeschwindigkeit im Weltraum pa_vTranslation
    • Charakterrotation im Weltraum pa_aRotation
    • Blickrotation im Weltraum pa_aViewRotation
    • aktuell gedrückte Tasten pa_ulButtons
    • TSC-basierter Millisekunden-Zeitstempel pa_llCreated
  • Bei der Wiedergabe wird der anfängliche Spielzustand geladen und die Spieleraktionen jedes Ticks wie in einer echten Partie angewendet

Warum Deterministik nötig ist

  • Diese Struktur setzt voraus, dass alles im Spiel vollständig vorhersagbar ist und nur Spieleraktionen den Spielverlauf verändern
  • Auch Zufallszahlen werden über einen Pseudozufallszahlengenerator verarbeitet, der einen Seed verwendet, der Teil des Spielzustands ist
    • CEntity::IRnd() verwendet CSessionState::Rnd()
    • ses_ulRandomSeed wird beim Deserialisieren des Spielzustands initialisiert
  • Würde man einen echten Zufallszahlengenerator oder unterschiedliche Seeds verwenden, könnte dieselbe Demo bei der Wiedergabe andere Ergebnisse liefern, was zu einer Desynchronisation führen würde

Gleitkomma und Tick-Verarbeitung

  • Die PC-Version von Serious Sam erschien ursprünglich nur für Windows, weshalb die Annahme desselben Compilers und derselben Runtime Probleme mit der Gleitkomma-Synchronisation verringerte
  • Der Renderer ist eine DLL, und OpenGL- oder DirectX-API-Aufrufe können die FPU-Präzision verändern; daher verwendet die Serious Engine Präzisions-Guards wie CSetFPUPrecision FPUPrecision(FPT_24BIT)
  • Es wurde keine Stelle gefunden, die die Rundungssteuerung explizit setzt, aber es gibt ein Assert, das per _controlfp den Zustand _RC_NEAR prüft
  • Die Spiellogik ist von der Rendering-Framerate getrennt
    • Das Rendering hängt von Hardware und Einstellungen ab und scheint intern auf 500 FPS begrenzt zu sein
    • Die Spiellogik läuft fest mit 20 Ticks pro Sekunde
  • Flüssige Bewegung wird durch lineare Interpolation zwischen aktuellem und vorherigem Tick erzeugt; in der Konsole lässt sich die Interpolation mit /net_bLerping=0 abschalten

Eigene Paketebene über UDP

  • In der Serious-Engine-Multiplayer-Implementierung existieren noch Funktionsnamen wie StartPeerToPeer_t, das tatsächliche Modell ist aber eine Client-Server-Architektur
  • Der Server empfängt Client-Nachrichten, verarbeitet sie und verteilt relevante Informationen an alle Clients
  • Jeder Spieler führt ähnlich wie im Demo-System seine eigene Simulation aus und entwickelt seinen Zustand anhand der Aktionsinformationen anderer Spieler weiter
  • Das Netzwerk verwendet UDP und legt ein eigenes Protokoll darüber, um Reihenfolge und Paketverlust zu behandeln
  • CPacket verwaltet Paketreihenfolge und Zuverlässigkeit
    • pa_ulSequence: Sequenznummer zur Sortierung und Duplikatserkennung
    • pa_ubReliable: Flag für Zuverlässigkeit
    • pa_ubRetryNumber: zählt Wiederholungsversuche
    • pa_tvSendWhen: wird für geplante Sendezeit und Staukontrolle verwendet
  • Zuverlässige Pakete warten auf ACKs und werden ohne ACK erneut gesendet
    • Die maximale Zahl an Wiederholungen wird über net_iMaxSendRetries gesetzt und scheint standardmäßig 10 zu sein
    • Das Wiederholungsintervall wird über net_fSendRetryWait gesetzt und scheint standardmäßig 0.5f zu sein
  • Zuverlässige Pakete können einen Stream bilden, der sich über mehrere Pakete erstreckt
    • Das erste Paket ist UDP_PACKET_RELIABLE_HEAD
    • Das letzte Paket ist UDP_PACKET_RELIABLE_TAIL
    • Ein einzelnes zuverlässiges Paket trägt beide Flags
  • Unzuverlässige Pakete bilden keinen Stream, da dieser bei Verlust unterbrochen würde

Verbindungslebenszyklus und Paket-Routing

  • CCommunicationInterface ist für die Kommunikation auf Paketebene zuständig und besitzt Schnittstellenfunktionen für Server, Client und Broadcast
  • Server- und Client-Schnittstellen gehen davon aus, dass das Gegenüber bereits bekannt ist; die Broadcast-Schnittstelle dient der Kommunikation mit beliebigen Adressen
  • CCommunicationInterface verwaltet zwei Master-Puffer
    • cci_pbMasterInput: speichert eingehende UDP-Pakete nach der Deserialisierung zu CPacket
    • cci_pbMasterOutput: serialisiert ausgehende CPacket-Objekte und sendet sie über die Socket-API
  • Die eigentliche clientbezogene Kommunikationsabstraktion übernimmt CClientInterface
    • Der Server hält für jeden Spieler eine Schnittstelle im Array cm_aciClients
    • Der Client kommuniziert mit dem Server über cm_ciLocalClient
    • Sowohl Client als auch Server verwenden cm_ciBroadcast zum Verbindungsaufbau
  • adr_uwID in CAddress dient als eindeutige Client-Kennung oder als Markierung für Broadcast-Pakete
    • Ist der Wert '//' oder 0, handelt es sich um ein Broadcast-Paket
    • Andere Werte sind Client-IDs innerhalb der Sitzung

Verbindungsaufbau und grundlegende Sicherheitsmechanismen

  • Um sich mit einem Server zu verbinden, sendet der Client ein zuverlässiges Broadcast-Paket mit dem Flag UDP_PACKET_CONNECT_REQUEST
  • Ist bereits ein Client mit derselben Adresse und demselben Port verbunden, ignoriert der Server die Anfrage
  • Bei einem neuen Client sucht der Server eine freie Client-Schnittstelle und führt dann Folgendes aus
    • Er erzeugt eine eindeutige Kennung für diesen Client
    • Er sendet die Kennung dem Client in einem zuverlässigen Broadcast-Paket UDP_PACKET_CONNECT_RESPONSE
  • Die Kennung verwendet nicht einfach einen festen Index, sondern kombiniert Teile eines Timerwerts mit dem Client-Index
  • Wer sich als anderer Spieler ausgeben will, muss uwID treffen, wodurch sich die Angriffsfläche verringert
  • Sendet ein nicht verbundener Spieler ein Nicht-Broadcast-Paket, kann die Serious Engine eine Warnung auf der Konsole ausgeben

Singleplayer und Demos als Sonderfall lokaler Verbindungen

  • Auch Singleplayer und Demo-Wiedergabe besitzen intern Server und Client, laufen aber im selben Prozess
  • Da keine Sockets zwischen demselben Prozess nötig sind, verbindet Client_OpenLocal() die lokale Client-Schnittstelle direkt mit der serverseitigen Schnittstelle
  • Zwei verbundene CClientInterface verschieben per ExchangeBuffers Pakete aus dem Ausgabepuffer der einen Seite in den Eingabepuffer der anderen
  • Lokales Spielen braucht daher weder Master-Ein-/Ausgabepuffer noch echte Netzwerksockets zu durchlaufen

Schicht der Netzwerk-Nachrichten

  • CNetworkMessage ist die Nachrichtenabstraktion oberhalb der Pakete und lässt sich wie ein Stream lesen und schreiben
  • Nachrichten werden mit Read, Write, ReadBits, WriteBits sowie den Operatoren << und >> serialisiert bzw. deserialisiert
  • Sie können auch Unter-Nachrichten enthalten; nach dem Schreiben der benötigten Daten kann Shrink die Puffergröße an die Datenmenge anpassen
  • Der CNetworkMessage-Puffer wird über AllocMemory angelegt, das intern offenbar malloc aufruft
  • CLinearAllocator existiert zwar, es ließ sich jedoch kein Einsatz finden; Nachrichtenpuffer werden häufig allokiert und reallokiert

Komprimierung und Delta-Encoding

  • Nachrichten können mit einem angegebenen Kompressor oder dem Standardkompressor des jeweiligen Nachrichtentyps komprimiert werden
  • MESSAGETYPE verwendet die unteren 6 Bit für den Typ und die restlichen 2 Bit für die Komprimierungsmethode
    • LZ77 CzlibCompressor
    • LZRW1 CLZCompressor
    • unkomprimiert
  • Standardmäßig scheint LZRW1 verwendet zu werden; über die Shell-Variable net_iCompression lässt sich das ändern
  • CPlayerAction wird nicht direkt gesendet, sondern als XOR-basiertes Delta zwischen aktueller und letzter Aktion übertragen
  • Der Empfänger XORt das Delta erneut mit der letzten Aktion und rekonstruiert so das ursprüngliche CPlayerAction
  • Deltas komprimieren besser, wenn sich Daten nur wenig ändern
    • Gedrückte Tasten bleiben oft über mehrere Frames gleich
    • Auch Geschwindigkeit und Blickrotation decken nicht ständig den gesamten Gleitkommabereich ab
  • Wenn der Server mit MSG_SEQ_ALLACTIONS die Aktionen mehrerer Spieler gesammelt sendet, kann dieser Ansatz noch effektiver sein

Nachrichtenverschlüsselung und Chat

  • Nachrichten der Serious Engine sind nicht verschlüsselt
  • Wird die Komprimierung mit net_iCompression=0 deaktiviert, sind Chat-Nachrichten im Spiel im Payload von UDP-Paketen im Klartext sichtbar
  • Ist die Komprimierung in realen Situationen aktiviert, muss ein Mitschneider zunächst den LZ-komprimierten Stream erkennen und dekomprimieren, die benötigten Daten befinden sich aber im Paket
  • Spiele jener Zeit verzichteten oft auf Verschlüsselung, und Mechanismen wie Authentifizierung und Schlüsselaustausch hätten die Komplexität erhöht
  • Auch das Web war damals überwiegend HTTP

Spielsitzungsebene

  • CNetworkLibrary verwaltet trotz seines Namens die Spielsitzung einschließlich des Spielzustands CSessionState
  • CNetworkLibrary erbt von dem zuvor erwähnten CMessageDispatcher
  • Beim Start eines Servers führt die Engine folgende Schritte aus
    • Die CRC-Sammlung wird initialisiert, um später prüfen zu können, ob verbundene Clients dieselben Dateien wie der Server besitzen
    • Ein neuer CSessionState wird erzeugt, serialisiert und als Standardzustand ga_pubDefaultState gespeichert
    • Die lokale Weltinstanz wird geladen
    • Die globale Kommunikationsschnittstelle wird initialisiert
    • Der lokale Sitzungszustand wird initialisiert, damit bei Verbindungen von Clients der Standardzustand und ein Zustandsdelta zum aktuellen Serverzustand gesendet werden können
    • Die CRC-Sammlung wird abgeschlossen und in ga_ulCRC gespeichert
  • Die CRC-Prüfung dient weniger der Cheat-Abwehr als der frühen Erkennung von Desynchronisationen
  • Der Beitrittsablauf eines Clients folgt grob diesem Schema
    • Ein leerer lokaler Sitzungszustand und die Kommunikationsschnittstelle werden initialisiert
    • Mit MSG_REQ_CONNECTREMOTESESSIONSTATE werden Build-Version, Modusname, Server-Passwort, Zahl lokaler Spieler und CSessionSocketParams gesendet
    • Mit MSG_REP_CONNECTREMOTESESSIONSTATE kommen Nachricht, Weltdateiname, Schwierigkeits- und Spielmodus-Flags sowie Sitzungseigenschaften zurück
    • Der Basis-Spielzustand wird initialisiert
    • Mit MSG_REQ_STATEDELTA wird der Unterschied zum aktuellen Serverzustand angefordert
    • Nach Empfang von MSG_REP_STATEDELTA wird der Spielzustands-Stream über ein rückwärts gerichtetes Diff rekonstruiert
    • CSessionState::Read_t() initialisiert den lokalen Sitzungszustand
    • Danach erfolgt die CRC-Prüfung; bei Abweichung wird die Verbindung getrennt

Hauptschleife und erneute Übertragung von Game-Streams

  • Die Hauptschleifen von Client und Server sind weitgehend ähnlich, der Server erledigt jedoch zusätzliche Aufgaben
  • Die Schleife aktualisiert die lokale Client-Schnittstelle und die Broadcast-Schnittstelle und verarbeitet eingehende Netzwerk-Nachrichten im lokalen Sitzungszustand
  • Der Server übernimmt außerdem den Pufferaustausch zwischen gepaarten Client-Schnittstellen, die Aktualisierung serverseitiger Client-Schnittstellen, die Aktualisierung des GameAgent und die Verarbeitung von Shell-Kommandos für Remote-Management
  • SessionStateLoop() verarbeitet unzuverlässige und zuverlässige Nachrichten getrennt
    • unzuverlässig: MSG_GAMESTREAMBLOCKS, MSG_KEEPALIVE, MSG_INF_PINGS, MSG_CHAT_OUT
    • zuverlässig: MSG_INF_DISCONNECTED, MSG_ADMIN_RESPONSE
  • MSG_GAMESTREAMBLOCKS ist zwar eine unzuverlässige Nachricht, ihr Verlust kann aber die Synchronisation zerstören
  • Daher prüft die Serious Engine beim Verarbeiten des Game-Streams auf fehlende Sequenzen und fordert diese erneut an
    • Ist der nächste erwartete Sequenzblock vorhanden, wird er verarbeitet
    • Fehlt der nächste Block und existiert auch kein neuerer, passiert in dieser Schleife nichts
    • Fehlt der nächste Block, existiert aber bereits ein neuerer, wird wegen möglichem Verlust ein Timeout gesetzt
    • Nach Ablauf des Timeouts werden mit MSG_REQUESTGAMESTREAMRESEND die fehlende Blocksequenz und deren Anzahl angefordert
  • Der Server sendet die angeforderten Game-Stream-Blöcke erneut

Verarbeitung von Game-Stream-Blöcken

  • MSG_SEQ_ADDPLAYER wird gesendet, wenn ein Spieler dem Spiel beitritt, und enthält den Spielerindex sowie einen CPlayerCharacter-Deskriptor
  • MSG_SEQ_REMPLAYER wird beim Trennen eines Spielers gesendet und enthält nur den Spielerindex
  • MSG_SEQ_CHARACTERCHANGE übermittelt Änderungen an Spielername, Team und Aussehen
    • In Serious Sam enthält der Aussehen-Puffer eine CPlayerSettings-Struktur
    • Darin stehen Dateiname des Spielermodells, Richtlinie zur automatischen Waffenauswahl, Fadenkreuztyp und verschiedene Flags
  • MSG_SEQ_PAUSE übermittelt Pausieren oder Fortsetzen und gibt den Namen des anfordernden Spielers auf der Konsole aus
  • MSG_SEQ_ALLACTIONS enthält die aktuelle Tick-Zeit und die Aktionen aller Spieler
    • Es wendet CPlayerAction auf jedes CPlayerTarget an
    • Danach folgen Timer-, Ereignis-, Bewegungsentitäten- und Physikverarbeitung
  • Die Synchronisationsprüfung erfolgt über MakeSynchronisationCheck()
    • Über ChecksumForSync() von Entitäten, Spielerzielen usw. wird ein CSyncCheck erstellt
    • Der Client sendet MSG_SYNCCHECK an den Server; stimmt es nicht mit dem Serverzustand überein, wird die Verbindung getrennt

Vorhersage zur Reduzierung von Eingabeverzögerung

  • Vorhersage soll verhindern, dass sich schnelle Spiele durch Internetlatenz träge anfühlen
  • Die Vorhersage für den lokalen Spieler verwendet die an den Server gesendeten Aktionen
  • Die Vorhersage für entfernte Spieler verwendet die zuletzt vom Server empfangenen Aktionen
  • Würde der Client ohne Antwort des Servers einfach den echten Spielzustand fortschreiben, fehlten ihm die Aktionen anderer Spieler, was zu Desynchronisationen führen könnte
  • Um vorhergesagten und echten Zustand nicht zu vermischen, verwendet die Serious Engine einen Predictor
    • Ein Predictor ist im Wesentlichen eine „Geister“-Kopie, die mit einer normalen Entität verbunden ist
    • Ein temporärer Predictor entsteht während der Vorhersage und besitzt keine zugeordnete Entität im echten Spielzustand
  • Während der Verarbeitung eines Vorhersage-Ticks werden nur Predictor-Entitäten verarbeitet
  • Sobald der Client Spieleraktionen vom Server erhält, zerstört er bestehende Predictor-Objekte und startet einen neuen Vorhersagezyklus
  • Beim Rendering wird die ursprüngliche Entität, für die eine Vorhersage läuft, nicht gezeichnet; stattdessen wird der Predictor gezeichnet, damit Bewegung fortlaufend wirkt, ohne den echten Zustand stark zu verändern
  • Der lokale Spieler kann nur so viele Schritte vorhersagen, wie servergesendete Aktionen in plt_abPrediction gespeichert sind
  • Ist bei der Vorhersage entfernter Spieler cli_bLerpActions deaktiviert, wird die letzte empfangene Aktion wiederholt
  • Ist cli_bLerpActions aktiviert, wird linear zwischen den letzten beiden Aktionen interpoliert, standardmäßig ist dies jedoch ausgeschaltet

Vergleich mit Doom und Quake

  • Das Networking von Doom war praktisch Peer-to-Peer, aber die Clients tauschten Strukturen ähnlich zu CPlayerAction aus und führten jeweils eine eigene Simulation aus
  • Auch Doom verwendete für Demo-Aufzeichnung und -Wiedergabe ein ähnliches System
  • Quake verfolgte einen anderen Ansatz; der Client verarbeitet dort nicht große Teile der Spiellogik selbst, sondern erhält eher Zustandsupdates vom Server
  • Beim Quake-Modell muss man sich weniger um Synchronisationsprobleme kümmern, und Cheat-Abwehr ist leichter, etwa weil der Server keine Informationen über Entitäten hinter Wänden senden muss
  • In Serious Sam sind Sitzungen mit deutlich mehr aktiven Gegnern und Objekten als in Quake üblich, weshalb das Senden vieler Objektzustände pro Tick eine größere Bandbreitenlast erzeugt hätte

Portabilität und strukturelle Grenzen

  • Einige Netzwerk-Nachrichten serialisieren Strukturen auf eine Weise, die einem reinterpret cast nahekommt
  • Unter der Annahme eines einzelnen Compilers und einer einzelnen Plattform kann das funktionieren, bei plattformübergreifenden Spielen können sich Struktur-Layout und Padding jedoch unterscheiden
  • 32-Bit-Binärdateien versuchen möglicherweise eine Ausrichtung auf 4-Byte-Grenzen, 64-Bit-Binärdateien auf 8-Byte-Grenzen
  • Auch Endianness ist ein Problem
    • x86-PCs sind Little Endian
    • PS3 ist Big Endian
  • Die Struktur der Serious Engine ist elegant, weil sie Unterschiede zwischen Übertragungsmedien wie Netzwerk und Dateien von der Spiellogik abstrahiert; zugleich erlaubt das Modell, bei dem alle Clients eine Kopie des Spielzustands besitzen, Cheats
  • So könnte etwa ein modifizierter Client in einem Deathmatch die Umrisse anderer Spieler hinter Wänden anzeigen

1 Kommentare

 
GN⁺ 2024-06-14
Hacker-News-Kommentare
  • Ich war einer der Entwickler, die die Netzwerk-Codebasis von Serious Sam umgesetzt haben
    Ich habe oft unter einem Schreibtisch im Croteam-Büro geschlafen und Usenet durchforstet, besonders inspiriert von einem Beitrag, der das Vorhersagesystem von QuakeWorld erklärte
    In jener Nacht habe ich eine einfache Minimalversion programmiert, während mein Kollege Dan auf einer alten 486er-Unix-Maschine als Router testete, mit der sich Latenz simulieren ließ
    Das war lange bevor das eigentliche Spiel darauf aufgebaut wurde

    • Ich frage mich, warum wir kopflosen Kamikaze-Männern schreiend auf einen zulaufen ließen und den Ton lauter machten, je näher sie kamen
      Ich höre das Geräusch immer noch
    • Ich mag diese Atmosphäre sehr, wenn unter einem Artikel darüber, wie ein legendäres Spiel entstanden ist, jemand ganz beiläufig schreibt: „Ach ja, das habe ich gebaut, hat Spaß gemacht“
    • Ich habe das Spiel wirklich geliebt
      Ich mochte Koop-Spiele und Shooter sehr, aber meine Freunde wollten immer nur Counter-Strike spielen
      Dank Serious Sam konnte ich sie manchmal dazu überreden, auch mal ein Spiel zu spielen, das ich mochte
    • Serious Sam war eines der Lieblingsspiele meines Onkels, zusammen mit Duke Nukem 3D
      Mein Onkel war ein wirklich wichtiger Mensch in meinem Leben, und die Spiele zu spielen, die er mochte, ist eine schöne Möglichkeit, die Erinnerung an ihn lebendig zu halten
      Ein großartiges Spiel, großartiger Multiplayer und sehr schöne Erinnerungen
    • Für den Splitscreen-Modus war ich wirklich dankbar
  • Serious Sam war schon immer ein starkes LAN-Party-Spiel
    Nicht weil es damals der schickste Titel war und auch nicht, weil es jemand im Voraus geplant hätte
    Wenn andere Spiele an Treiberproblemen, Hitzeproblemen oder Updates scheiterten, startete man Serious Sam und es funktionierte einfach, deshalb dominierte es LAN-Partys
    Das setzte sich auch in den Nachfolgern fort: Selbst wenn der PC von jemandem komplett zerschossen war, unterstützte es zuverlässig Splitscreen und kam gut mit Eingabegeräten zurecht
    Die systemischen Aspekte des Spiels waren in Sachen Zuverlässigkeit wirklich herausragend

    • Serious Sam lief selbst auf miserabler Hardware schnell und sah dabei trotzdem ziemlich gut aus
      Ähnlich war es bei Counter-Strike: grafisch nicht besonders beeindruckend, aber es lief selbst auf Toaster-PCs gut und blieb deshalb lange populär
    • Das „aaaaaaaaaaaaah“, das aus mehreren Lautsprechern kam, war herrlich
    • Ende der 90er habe ich die technische Support-Website von EA betreut, und das Support-/QA-Team spielte nach Feierabend in großer Runde Serious Sam
      Es war der einzige Ego-Shooter, der auf den Büro-PCs konstant gut lief, und er hat wirklich Spaß gemacht
      Damals gab es bei EA eine ziemlich große Überschneidung zwischen QA und technischem Support: Im Sommer arbeiteten Support-Mitarbeiter als interne Beta-Tester für die Veröffentlichungen zum Jahresende, und im Winter rund um Weihnachten, wenn die Anrufe zunahmen, machten sie technischen Support
  • Als wir Multiplayer für die Game-Boy-Color-Portierung von Vigilante 8 umgesetzt haben, haben wir deterministisches Gameplay verwendet
    Das GBC-Link-Kabel übertrug gleichzeitig ein Byte in beide Richtungen und verhielt sich wie ein Paar Schieberegister, die sich über das Kabel hinweg gegenseitig füllten
    Das Spiel war an die Bildrate des GBC gebunden, und praktisch bei jedem V-Blank fiel viel Arbeit für die Bildschirmaktualisierung an; wenn man das verpasste, war es vorbei mit weichem Scrolling
    Zu Beginn einer Multiplayer-Partie wurden Seeds ausgetauscht, und der Ablauf war im Wesentlichen so: In Frame A wurden Eingaben gelesen, zu einem Byte komprimiert und in den Sendepuffer gelegt. Während Frame B gerendert wurde, fand die Übertragung statt. Zu Beginn von Frame C hatte man dann die lokalen Eingaben, die in Frame A gesendet worden waren, sowie die gegnerischen Eingaben, die in Frame B empfangen wurden
    Diese Eingaben wurden auf den Spielzustand angewendet und dann Frame C gerendert, wodurch sowohl lokale als auch entfernte Eingaben mit einem Frame Verzögerung angewendet wurden
    Lokales Spielen hatte keine Eingabeverzögerung, also dürft ihr, wenn ihr im Multiplayer verloren habt, gern die Latenz verantwortlich machen oder, falls nötig, auch ausdrücklich mich

    • Ich habe mir dieses Modul vor ein paar Wochen gekauft und war begeistert, dass es Link-Kabel-Multiplayer hat, weil ich alte GBC-Rumble-Module mag
      Wirklich ein tolles Spiel, und die technische Erklärung der Multiplayer-Umsetzung ist auch sehr cool
  • Croteam ist wirklich ein talentiertes Spieleentwicklerteam
    Ich habe The Talos Principle 1 und 2 sehr genossen, und beim ersten Teil gehörten sie zu den frühen Pionieren mit einer vollständig eigenen Vulkan-Game-Engine

    • Ich fand es sehr schade, dass sie bei The Talos Principle 2 ihre eigene Engine aufgegeben und Unreal Engine verwendet haben
    • Ich habe gerade erst erfahren, dass das Talos-2-DLC diesen Freitag auf Steam erscheint
  • Ich frage mich, ob das dieselbe Idee ist wie bei „1500 Archers on a 28.8K“ aus Age of Empires
    https://www.gamedeveloper.com/programming/1500-archers-on-a-...

    • Ja. Beides sind deterministische Lockstep-Systeme
      Sehr viele Spiele haben solche Systeme lange Zeit verwendet, aber heute scheinen sie aus verschiedenen Gründen seltener zu sein als früher
    • Diese Zahl zeigt, wie seltsam es ist, dass Tempest Rising ein Einheitenlimit hat
      Entweder man hat Ressourcen oder eben nicht; man braucht keine Beschränkung nur der Beschränkung wegen
  • Selbst Spiele mit zehnmal so viel Bandbreite tun sich schwer damit, so viele Gegner zu unterstützen
    Mir wird gerade klar, dass der Zuwachs an technischen Ressourcen der Effizienz und Kreativität in der Informatik eher geschadet zu haben scheint
    Je mehr Bandbreite, Speicherplatz, RAM und Rechenleistung wir bekommen, desto langsamer, aufgeblähter und unfähiger wird Software pro Ressourceneinheit
    Man könnte das den Benjamin-Button-Effekt im Softwaredesign nennen

    • Ich würde gern wissen, wie viele Gegner mit „so viele Gegner“ gemeint sind
      Falls im Artikel eine Zahl steht, habe ich sie nicht gefunden
      Es gibt auch bei modernen Spielen viele Fälle, in denen Multiplayer plus Gegnerzahl völlig ausreicht, um als „massiv“ zu gelten, und wenn es um Spielerzahlen geht, gibt es ebenfalls Spiele mit sehr großen Spielerzahlen
    • Üblicherweise ist das eher als Software-Bloat bekannt
    • Ja, das ist als Wirthsches Gesetz bekannt
  • Für mich war das ein Spiel, in dem ich mehr Zeit rückwärts laufend verbracht habe als vorwärts

    • Es gibt sogar ein Spiel mit genau diesem Thema, es heißt „I Hate Running Backwards“
      Es ist auf Steam, und ich weiß nicht, ob es vom selben Studio ist, aber es gehört zum Serious-Sam-Universum
    • Du und Netrisca wart zusammen, aber diese Tausenden Gegner waren allein
    • Es gab sogar Waffen, die sich so anfühlten, als würden sie ein paar Zehntelsekunden vor dem Drücken der Maustaste schießen
    • Ich erinnere mich auch noch daran, wie ich beim Rückzug verzweifelt versucht habe, die dabei erzeugte Munition aufzusammeln
  • Die Architektur von Factorio ist ähnlich: Es werden fast nur Eingabeereignisse übertragen, und sie stützt sich auf einen Lockstep-Simulationskern
    Auffällige Ausnahmen gibt es etwa beim Schienenplanungswerkzeug

    • Ich würde gern irgendwann einmal an so einer Lockstep-Architektur arbeiten
      Das wirkt wie eine befriedigende und gut testbare Design-Einschränkung
  • Ich erinnere mich noch daran, als Kind die Serious-Sam-Demo aus dem PC Gamer gespielt zu haben
    Schon damals galt es als Retro-Spiel, das sich wie eine Rückkehr zu den alten DOOM- und Quake-Tagen anfühlte
    Jetzt sind buchstäblich 20 Jahre vergangen, und es ist selbst ein Klassiker geworden

  • Starsiege: Tribes war selbst über eine 56K-Verbindung groß angelegt und absurd spaßig

    • Die Tribes-Entwickler haben ein Whitepaper über ähnliche Netzwerk-Code-Konzepte geschrieben
      https://www.gamedevs.org/uploads/tribes-networking-model.pdf
    • Es war vermutlich mein Lieblingsspiel in meiner Kindheit, besonders Tribes 2
      Tatsächlich habe ich Tribes 2 erst vor Kurzem wieder heruntergeladen und noch vor ein paar Monaten gegen Bots gespielt
      Es ist ein altes Spiel, aber immer noch unterhaltsam, und ich denke oft darüber nach, es mit etwas wie Unity neu zu bauen
      Vielleicht mache ich das irgendwann
    • Tribes war großartig
      1999 sah man prozedural erzeugtes Terrain, auf dem man wie beim Skifahren hinunterglitt und andere wieder hinaufgleiten sah, dazu große Karten und viele Spieler