paint-brush
Singularity: Optimierte Spieleentwicklung mit einem universellen Backend-Frameworkby@makhorin

Singularity: Optimierte Spieleentwicklung mit einem universellen Backend-Framework

Andrei Makhorin13m2024/07/25
Read on Terminal Reader

Was ist ein „universelles Framework“ im Hinblick auf das Game-Design? Warum wird es benötigt oder ist es nützlich? Wie können sie zur Rationalisierung der Entwicklung beitragen? Wir beantworten all diese Fragen (und mehr) und präsentieren unsere Lösung: Singularity.
featured image - Singularity: Optimierte Spieleentwicklung mit einem universellen Backend-Framework
Andrei Makhorin HackerNoon profile picture


Hallo! Ich bin Andrey Makhorin, Serverentwickler bei Pixonic (MY.GAMES). In diesem Artikel erzähle ich Ihnen, wie mein Team und ich eine universelle Lösung für die Backend-Entwicklung entwickelt haben. Sie erfahren mehr über das Konzept, sein Ergebnis und wie sich unser System namens Singularity in realen Projekten bewährt hat. Ich gehe auch ausführlich auf die Herausforderungen ein, denen wir uns stellen mussten.

Hintergrund

Wenn ein Spielestudio anfängt, ist es entscheidend, schnell eine überzeugende Idee zu formulieren und umzusetzen: Dutzende Hypothesen werden getestet und das Spiel wird ständig geändert; neue Funktionen werden hinzugefügt und erfolglose Lösungen werden überarbeitet oder verworfen. Dieser Prozess der schnellen Iteration, gepaart mit knappen Fristen und einem kurzen Planungshorizont, kann jedoch zur Anhäufung technischer Schulden führen.


Bei bestehender technischer Schuld kann die Wiederverwendung alter Lösungen kompliziert sein, da verschiedene Probleme damit gelöst werden müssen. Das ist offensichtlich nicht optimal. Aber es gibt einen anderen Weg: ein „universelles Framework“. Durch die Entwicklung generischer, wiederverwendbarer Komponenten (wie Layoutelemente, Fenster und Bibliotheken, die Netzwerkinteraktionen implementieren) können Studios den Zeit- und Arbeitsaufwand für die Entwicklung neuer Funktionen erheblich reduzieren. Dieser Ansatz reduziert nicht nur die Menge an Code, die Entwickler schreiben müssen, sondern stellt auch sicher, dass der Code bereits getestet wurde, was zu weniger Zeitaufwand für die Wartung führt.


Wir haben die Feature-Entwicklung im Rahmen eines Spiels besprochen, aber jetzt betrachten wir die Situation aus einem anderen Blickwinkel: Für jedes Spielestudio kann die Wiederverwendung kleiner Codeteile innerhalb eines Projekts eine effektive Strategie zur Rationalisierung der Produktion sein, aber irgendwann müssen sie ein neues Hit-Spiel entwickeln. Die Wiederverwendung von Lösungen aus einem bestehenden Projekt könnte diesen Prozess theoretisch beschleunigen, aber es ergeben sich zwei erhebliche Hürden. Erstens gelten hier dieselben technischen Schuldenprobleme, und zweitens waren alle alten Lösungen wahrscheinlich auf die spezifischen Anforderungen des vorherigen Spiels zugeschnitten und daher für das neue Projekt ungeeignet.


Zu diesen Problemen kommen noch weitere hinzu: Das Datenbankdesign entspricht möglicherweise nicht den Anforderungen des neuen Projekts, die Technologien sind möglicherweise veraltet und dem neuen Team fehlt möglicherweise die notwendige Fachkompetenz.


Darüber hinaus wird das Kernsystem häufig für ein bestimmtes Genre oder Spiel entwickelt, was die Anpassung an ein neues Projekt erschwert.


Auch hier kommt ein universelles Framework ins Spiel. Obwohl die Entwicklung von Spielen, die sich stark voneinander unterscheiden, wie eine unüberwindbare Herausforderung erscheinen mag, gibt es Beispiele für Plattformen, die dieses Problem erfolgreich gelöst haben: PlayFab, Photon Engine und ähnliche Plattformen haben unter Beweis gestellt, dass sie die Entwicklungszeit erheblich verkürzen können, sodass sich die Entwickler auf die Entwicklung der Spiele statt auf die Infrastruktur konzentrieren können.


Lassen Sie uns nun in unsere Geschichte eintauchen.

Das Bedürfnis nach Singularität

Für Multiplayer-Spiele ist ein robustes Backend unerlässlich. Ein typisches Beispiel ist unser Flaggschiff-Spiel War Robots. Es handelt sich um einen mobilen PvP-Shooter, den es seit über 10 Jahren gibt und der zahlreiche Funktionen angesammelt hat, die Backend-Unterstützung erfordern. Und obwohl unser Servercode auf die Besonderheiten des Projekts zugeschnitten war, verwendete er Technologien, die veraltet waren.


Als es an der Zeit war, ein neues Spiel zu entwickeln, erkannten wir, dass der Versuch, die Serverkomponenten von War Robots wiederzuverwenden, problematisch sein würde. Der Code war zu projektspezifisch und erforderte Fachwissen in Technologien, über die das neue Team nicht verfügte.


Wir erkannten auch, dass der Erfolg des neuen Projekts nicht garantiert war, und selbst wenn es erfolgreich wäre, müssten wir irgendwann ein weiteres neues Spiel entwickeln und stünden vor demselben „unbeschriebenen Blatt“-Problem. Um dies zu vermeiden und uns für die Zukunft abzusichern, beschlossen wir, die wesentlichen Komponenten zu identifizieren, die für die Spieleentwicklung erforderlich sind, und dann ein universelles Framework zu erstellen, das für alle zukünftigen Projekte verwendet werden könnte.


Unser Ziel war es, Entwicklern ein Tool bereitzustellen, das ihnen das wiederholte Entwerfen von Backend- Architekturen , Datenbankschemata, Interaktionsprotokollen und spezifischen Technologien erspart. Wir wollten die Leute von der Last befreien, Autorisierung, Zahlungsabwicklung und Speicherung von Benutzerinformationen zu implementieren, damit sie sich auf die Kernaspekte des Spiels konzentrieren können: Gameplay, Design, Geschäftslogik und mehr.


Darüber hinaus wollten wir mit unserem neuen Framework nicht nur die Entwicklung beschleunigen, sondern es Client-Programmierern auch ermöglichen, serverseitige Logik zu schreiben, ohne über umfassende Netzwerk-, DBMS- oder Infrastrukturkenntnisse verfügen zu müssen.


Darüber hinaus könnte unser DevOps-Team durch die Standardisierung einer Reihe von Diensten alle Spieleprojekte ähnlich behandeln, wobei sich nur die IP-Adressen ändern würden. Dies würde es uns ermöglichen, wiederverwendbare Bereitstellungsskriptvorlagen und Überwachungs-Dashboards zu erstellen.


Während des gesamten Prozesses haben wir Architekturentscheidungen getroffen, die die Möglichkeit der Wiederverwendung des Backends in zukünftigen Spielen berücksichtigten. Dieser Ansatz stellte sicher, dass unser Framework flexibel, skalierbar und an unterschiedliche Projektanforderungen anpassbar ist.


(Es ist auch erwähnenswert, dass die Entwicklung des Frameworks keine Insel war – es wurde parallel zu einem anderen Projekt erstellt.)

Erstellen der Plattform

Wir haben uns entschieden, Singularity eine Reihe von Funktionen zu geben, die unabhängig vom Genre, Setting oder Kern-Gameplay eines Spiels sind, darunter:

  • Authentifizierung
  • Speicherung von Benutzerdaten
  • Spieleinstellungen und Balance-Analyse
  • Zahlungsabwicklung
  • AB-Testverteilung
  • Integration von Analytics-Diensten
  • Server-Admin-Panel


Diese Funktionen sind für jedes mobile Mehrbenutzerprojekt von grundlegender Bedeutung (zumindest sind sie für in Pixonic entwickelte Projekte relevant).


Zusätzlich zu diesen Kernfunktionen wurde Singularity so konzipiert, dass es mehr projektspezifische Funktionen bietet, die näher an der Geschäftslogik liegen. Diese Funktionen werden mithilfe von Abstraktionen erstellt, sodass sie über verschiedene Projekte hinweg wiederverwendbar und erweiterbar sind.


Einige Beispiele:

  • Quests
  • Bietet an
  • Freundesliste
  • Spielersuche
  • Bewertungstabellen
  • Online-Status der Spieler
  • Benachrichtigungen im Spiel



Technisch besteht die Singularity-Plattform aus vier Komponenten:

  • Server-SDK: Dies ist ein Satz von Bibliotheken, auf deren Grundlage Spieleprogrammierer ihre Server entwickeln können.
  • Client-SDK: Ebenfalls ein Satz von Bibliotheken, aber für die Entwicklung einer mobilen Anwendung.
  • Eine Reihe vorgefertigter Microservices: Dabei handelt es sich um vorgefertigte Server, die keiner Änderung bedürfen. Dazu gehören der Authentifizierungsserver, der Balance-Server und andere.
  • Erweiterungsbibliotheken: Diese Bibliotheken implementieren bereits verschiedene Features wie Angebote, Quests usw. Spieleprogrammierer können diese Erweiterungen aktivieren, wenn ihr Spiel dies erfordert.


Lassen Sie uns als Nächstes jede dieser Komponenten untersuchen.


Server-SDK

Einige Dienste, wie der Profildienst und das Matchmaking, erfordern spielspezifische Geschäftslogik. Um dies zu ermöglichen, haben wir diese Dienste so konzipiert, dass sie als Bibliotheken verteilt werden können. Indem sie dann auf diesen Bibliotheken aufbauen, können Entwickler Anwendungen erstellen, die Befehlshandler, Matchmaking-Logik und andere projektspezifische Komponenten enthalten.


Dieser Ansatz ist analog zum Erstellen einer ASP.NET-Anwendung, bei der das Framework eine HTTP-Protokollfunktionalität auf niedriger Ebene bereitstellt, während sich der Entwickler auf das Erstellen von Controllern und Modellen konzentrieren kann, die die Geschäftslogik enthalten.


Nehmen wir beispielsweise an, wir möchten die Möglichkeit hinzufügen, Benutzernamen im Spiel zu ändern. Dazu müssten die Programmierer eine Befehlsklasse schreiben, die den neuen Benutzernamen und einen Handler für diesen Befehl enthält.


Hier ist ein Beispiel für einen ChangeNameCommand:

 public class ChangeNameCommand : ICommand { public string Name { get; set; } }


Ein Beispiel für diesen Befehlshandler:

 class ChangeNameCommandHandler : ICommandHandler<ChangeNameCommand> { private IProfile Profile { get; } public ChangeNameCommandHandler(IProfile profile) { Profile = profile; } public void Handle(ICommandContext context, ChangeNameCommand command) { Profile.Name = command.Name; } }


In diesem Beispiel muss der Handler mit einer IProfile-Implementierung initialisiert werden, die automatisch durch Abhängigkeitsinjektion gehandhabt wird. Einige Modelle, wie IProfile, IWallet und IInventory, sind ohne zusätzliche Schritte zur Implementierung verfügbar. Allerdings ist die Arbeit mit diesen Modellen aufgrund ihrer abstrakten Natur möglicherweise nicht sehr bequem, da sie Daten bereitstellen und Argumente akzeptieren, die nicht auf die spezifischen Projektanforderungen zugeschnitten sind.


Um die Dinge einfacher zu machen, können Projekte ihre eigenen Domänenmodelle definieren, sie ähnlich wie Handler registrieren und sie nach Bedarf in Konstruktoren einfügen. Dieser Ansatz ermöglicht eine individuellere und bequemere Erfahrung bei der Arbeit mit Daten.


Hier ist ein Beispiel für ein Domänenmodell:

 public class WRProfile { public readonly IProfile Raw; public WRProfile(IProfile profile) { Raw = profile; } public int Level { get => Raw.Attributes["level"].AsInt(); set => Raw.Attributes["level"] = value; } }


Standardmäßig enthält das Spielerprofil die Eigenschaft „Level“ nicht. Durch die Erstellung eines projektspezifischen Modells kann diese Art von Eigenschaft jedoch hinzugefügt werden, sodass Informationen auf Spielerebene in Befehlshandlern problemlos gelesen oder geändert werden können.


Ein Beispiel für einen Befehlshandler, der das Domänenmodell verwendet:

 class LevelUpCommandHandler : ICommandHandler<LevelUpCommand> { private WRProfile Profile { get; } public LevelUpCommandHandler(WRProfile profile) { Profile = profile; } public void Handle(ICommandContext context, LevelUpCommand command) { Profile.Level += 1; } }


Dieser Code zeigt deutlich, dass die Geschäftslogik für ein bestimmtes Spiel von den zugrunde liegenden Transport- oder Datenspeicherschichten isoliert ist. Diese Abstraktion ermöglicht es Programmierern, sich auf die Kernmechanik des Spiels zu konzentrieren, ohne sich um Transaktionalität, Race Conditions oder andere allgemeine Backend-Probleme kümmern zu müssen.


Darüber hinaus bietet Singularity umfassende Flexibilität zur Verbesserung der Spiellogik. Das Spielerprofil ist eine Sammlung von „Schlüssel-Wert-Paaren“, die es Spieleentwicklern ermöglicht, problemlos beliebige Eigenschaften hinzuzufügen, ganz wie sie es sich vorstellen.


Über das Profil hinaus besteht die Spielerentität in Singularity aus mehreren wesentlichen Komponenten, die für Flexibilität sorgen sollen. Dazu gehören insbesondere eine Brieftasche, die den Betrag jeder darin enthaltenen Währung verfolgt, sowie ein Inventar, das die Gegenstände des Spielers auflistet.


Interessanterweise sind Elemente in Singularity abstrakte Entitäten, die Profilen ähneln. Jedes Element hat eine eindeutige Kennung und eine Reihe von Schlüssel-Wert-Paaren. Ein Element muss also nicht unbedingt ein greifbares Objekt wie eine Waffe, Kleidung oder eine Ressource in der Spielwelt sein. Stattdessen kann es jede allgemeine Beschreibung darstellen, die Spielern eindeutig gegeben wird, wie eine Quest oder ein Angebot. Im folgenden Abschnitt werde ich detailliert beschreiben, wie diese Konzepte in einem bestimmten Spielprojekt implementiert werden.


Ein wesentlicher Unterschied in Singularity besteht darin, dass Gegenstände einen Verweis auf eine allgemeine Beschreibung in der Bilanz speichern. Während diese Beschreibung statisch bleibt, können sich die Eigenschaften des einzelnen ausgegebenen Gegenstands ändern. Beispielsweise können Spieler die Möglichkeit erhalten, Waffen-Skins zu ändern.


Darüber hinaus verfügen wir über robuste Optionen zum Migrieren von Spielerdaten. Bei der herkömmlichen Backend-Entwicklung ist das Datenbankschema häufig eng mit der Geschäftslogik verknüpft, und Änderungen an den Eigenschaften einer Entität erfordern normalerweise direkte Schemaänderungen.


Der traditionelle Ansatz ist jedoch für Singularity ungeeignet, da das Framework die mit einer Spielerentität verbundenen Geschäftseigenschaften nicht kennt und das Spieleentwicklungsteam keinen direkten Zugriff auf die Datenbank hat. Stattdessen werden Migrationen als Befehlshandler entworfen und registriert, die ohne direkte Repository-Interaktion funktionieren. Wenn ein Spieler eine Verbindung zum Server herstellt, werden seine Daten aus der Datenbank abgerufen. Wenn auf dem Server registrierte Migrationen noch nicht auf diesen Spieler angewendet wurden, werden sie ausgeführt und der aktualisierte Status wird wieder in der Datenbank gespeichert.


Die Liste der angewendeten Migrationen wird auch als Spielereigenschaft gespeichert, und dieser Ansatz hat einen weiteren wesentlichen Vorteil: Er ermöglicht es, Migrationen zeitlich zu staffeln. Dadurch können wir Ausfallzeiten und Leistungsprobleme vermeiden, die ansonsten durch massive Datenänderungen verursacht werden könnten, z. B. wenn allen Spielerdatensätzen eine neue Eigenschaft hinzugefügt und auf einen Standardwert gesetzt wird.

Client-SDK

Singularity bietet eine unkomplizierte Schnittstelle für die Backend-Interaktion, sodass sich Projektteams auf die Spieleentwicklung konzentrieren können, ohne sich um Protokollprobleme oder Netzwerkkommunikationstechnologien kümmern zu müssen. (Allerdings bietet das SDK die Flexibilität, bei Bedarf die Standard-Serialisierungsmethoden für projektspezifische Befehle zu überschreiben.)


Das SDK ermöglicht die direkte Interaktion mit der API, enthält aber auch einen Wrapper, der Routineaufgaben automatisiert. Wenn beispielsweise ein Befehl im Profildienst ausgeführt wird, wird eine Reihe von Ereignissen generiert, die Änderungen im Profil des Spielers anzeigen. Der Wrapper wendet diese Ereignisse auf den lokalen Status an und stellt sicher, dass der Client die aktuelle Version des Profils beibehält.


Hier ein Beispiel für einen Befehlsaufruf:

 var result = _sandbox.ExecSync(new LevelUpCommand())


Fertige Microservices

Die meisten Dienste in Singularity sind vielseitig einsetzbar und müssen nicht für bestimmte Projekte angepasst werden. Diese Dienste werden als vorgefertigte Anwendungen verteilt und können für verschiedene Spiele verwendet werden.


Die Suite vorgefertigter Dienste umfasst:

  • Ein Gateway für Clientanfragen
  • Ein Authentifizierungsdienst
  • Ein Dienst zum Parsen und Speichern von Einstellungen und Bilanztabellen
  • Ein Online-Statusdienst
  • Ein Service für Freunde
  • Ein Leaderboard-Dienst


Einige Dienste sind für die Plattform von grundlegender Bedeutung und müssen bereitgestellt werden, wie etwa der Authentifizierungsdienst und das Gateway. Andere sind optional, wie etwa der Freundesdienst und die Bestenliste, und können aus der Umgebung von Spielen ausgeschlossen werden, die sie nicht benötigen.

Ich werde später auf die Probleme eingehen, die mit der Verwaltung einer großen Anzahl von Diensten verbunden sind. Zunächst ist es jedoch wichtig zu betonen, dass optionale Dienste optional bleiben sollten. Mit der wachsenden Anzahl von Diensten steigen auch die Komplexität und die Onboarding-Hürde für neue Projekte.


Erweiterungsbibliotheken

Obwohl das Kern-Framework von Singularity durchaus leistungsfähig ist, können wichtige Funktionen von Projektteams unabhängig voneinander implementiert werden, ohne den Kern zu ändern. Wenn eine Funktion als potenziell nützlich für mehrere Projekte eingestuft wird, kann sie vom Framework-Team entwickelt und als separate Erweiterungsbibliotheken veröffentlicht werden. Diese Bibliotheken können dann in Befehlshandler im Spiel integriert und verwendet werden.


Einige Beispielfunktionen, die hier anwendbar sein könnten, sind Quests und Angebote. Aus der Sicht des Kernframeworks sind diese Entitäten einfach den Spielern zugewiesene Elemente. Erweiterungsbibliotheken können diesen Elementen jedoch bestimmte Eigenschaften und Verhaltensweisen verleihen und sie so in Quests oder Angebote umwandeln. Diese Funktion ermöglicht eine dynamische Änderung der Elementeigenschaften, wodurch der Questfortschritt verfolgt oder das letzte Datum aufgezeichnet werden kann, an dem dem Spieler ein Angebot unterbreitet wurde.


Ergebnisse bisher

Singularity wurde erfolgreich in einem unserer neuesten weltweit verfügbaren Spiele, Little Big Robots, implementiert. Dadurch konnten die Client-Entwickler die Serverlogik selbst verwalten. Darüber hinaus konnten wir Prototypen erstellen, die vorhandene Funktionen nutzen, ohne dass direkte Unterstützung durch das Plattformteam erforderlich war.


Diese universelle Lösung bringt jedoch auch ihre Tücken mit sich. Mit der zunehmenden Anzahl an Funktionen hat auch die Komplexität der Interaktion mit der Plattform zugenommen. Singularity hat sich von einem einfachen Tool zu einem anspruchsvollen, komplexen System entwickelt – in mancher Hinsicht ähnlich dem Übergang von einem einfachen Tastentelefon zu einem voll ausgestatteten Smartphone.


Während Singularity es Entwicklern erspart, sich mit den Komplexitäten von Datenbanken und Netzwerkkommunikation auseinanderzusetzen, hat es auch eine eigene Lernkurve mit sich gebracht. Entwickler müssen nun die Nuancen von Singularity selbst verstehen, was eine erhebliche Umstellung sein kann.


Diese Herausforderungen werden von Entwicklern bis hin zu Infrastrukturadministratoren bewältigt. Diese Fachleute verfügen häufig über umfassende Erfahrung in der Bereitstellung und Wartung bekannter Lösungen wie Postgres und Kafka. Singularity ist jedoch ein internes Produkt, sodass sie sich neue Fähigkeiten aneignen müssen: Sie müssen die Feinheiten der Cluster von Singularity erlernen, zwischen erforderlichen und optionalen Diensten unterscheiden und verstehen, welche Kennzahlen für die Überwachung entscheidend sind.


Zwar können sich Entwickler innerhalb eines Unternehmens immer an die Entwickler der Plattform wenden, um Rat zu erhalten, aber dieser Prozess ist zwangsläufig zeitaufwändig. Unser Ziel ist es, die Einstiegshürde so weit wie möglich zu minimieren. Dies erfordert eine umfassende Dokumentation jeder neuen Funktion, was die Entwicklung verlangsamen kann, aber dennoch als Investition in den langfristigen Erfolg der Plattform gilt. Darüber hinaus ist eine robuste Unit- und Integrationstestabdeckung unerlässlich, um die Systemzuverlässigkeit sicherzustellen.


Singularity verlässt sich stark auf automatisierte Tests, da manuelle Tests die Entwicklung separater Spielinstanzen erfordern würden, was unpraktisch ist. Automatisierte Tests können die überwiegende Mehrheit – also 99 % – der Fehler erkennen. Es gibt jedoch immer einen kleinen Prozentsatz von Problemen, die nur bei bestimmten Spieltests auftreten. Dies kann sich auf die Veröffentlichungszeitpläne auswirken, da das Singularity-Team und die Projektteams häufig asynchron arbeiten. Ein blockierender Fehler könnte in vor langer Zeit geschriebenem Code gefunden werden und das Plattformentwicklungsteam könnte mit einer anderen wichtigen Aufgabe beschäftigt sein. (Diese Herausforderung ist nicht nur auf Singularity beschränkt und kann auch bei der Entwicklung benutzerdefinierter Backends auftreten.)


Eine weitere große Herausforderung ist die Verwaltung von Updates in allen Projekten, die Singularity verwenden. Normalerweise gibt es ein Vorzeigeprojekt, das die Entwicklung des Frameworks mit einem konstanten Strom von Funktionsanfragen und Verbesserungen vorantreibt. Die Zusammenarbeit mit dem Team dieses Projekts ist eng; wir verstehen ihre Bedürfnisse und wissen, wie sie unsere Plattform zur Lösung ihrer Probleme nutzen können.


Während einige Vorzeigeprojekte eng mit dem Framework-Team verbunden sind, laufen andere Spiele in frühen Entwicklungsphasen oft unabhängig voneinander und verlassen sich ausschließlich auf vorhandene Funktionen und Dokumentationen. Dies kann manchmal zu redundanten oder suboptimalen Lösungen führen, da Entwickler die Dokumentation missverstehen oder die verfügbaren Funktionen falsch verwenden könnten. Um dies zu vermeiden, ist es wichtig, den Wissensaustausch durch Präsentationen, Meetups und Teamaustausch zu erleichtern, obwohl solche Initiativen einen erheblichen Zeitaufwand erfordern.

Die Zukunft

Singularity hat seinen Wert in unseren Spielen bereits unter Beweis gestellt und wird sich weiter entwickeln. Wir planen zwar die Einführung neuer Funktionen, konzentrieren uns derzeit jedoch vor allem darauf, sicherzustellen, dass diese Verbesserungen die Benutzerfreundlichkeit der Plattform für Projektteams nicht beeinträchtigen.

Darüber hinaus ist es notwendig, die Einstiegshürde zu senken, die Bereitstellung zu vereinfachen und Flexibilität in Bezug auf Analysen zu schaffen, damit Projekte ihre Lösungen miteinander verbinden können. Dies ist eine Herausforderung für das Team, aber wir glauben und sehen in der Praxis, dass sich die in unsere Lösung investierten Anstrengungen definitiv voll auszahlen werden!