Bild mit freundlicher Genehmigung von Joy Way

STRIDE-Entwickler Joy Way teilt plattformübergreifende VR-Mehrspieler-Praktiken

Artem Tarasov, Multiplayer Lead bei Joy Way |
16. November 2022
Hi, ich bin Artem Tarasov, Multiplayer Lead bei Joy Way.

Joy Way ist ein VR-Entwickler-/Publishing-Team aus Zypern. Wir haben vor sechs Jahren damit begonnen, an mehreren Projekten für PC und Standalone-VR zu arbeiten, darunter SteamVR, Meta Quest, PlayStation VR und Pico.

Die Entwicklung von STRIDE, unserem Action-Parkour-VR-Spiel, war eine Hürde für das Team, insbesondere der plattformübergreifende Mehrspielermodus. Wir mussten diverse Plattformen, Eingabegeräte, physikbasierte Fortbewegung, korrekte Treffererkennung und andere Aufgaben in Betracht ziehen.

Dieser Blog behandelt Lösungen für einige Probleme, auf die wir bei der Entwicklung von Mehrspielermodi für STRIDE stießen.
 

Nutzen Sie diesen Blog als Guide zur Verwendung der UE, um ein plattformübergreifendes Spiel zu entwickeln und es mit Backend-Diensten zu verbinden. Dieser Blog ist nützlich für Unreal-Programmierer, die mit C++ sowie Blueprints arbeiten.

Plattformspezifischen Code schreiben

Jeder Entwickler steht bei der Entwicklung eines plattformübergreifenden Spiels zunächst vor der Aufgabe, plattformspezifischen Code zu schreiben.

Der erste und häufigste Fall: Nutzung plattformspezifischer Plugins und Module. In Unreal kann man für jedes Modul Plattformen auf die White- und Blacklist setzen:
Für Plugins kann man dasselbe mit BlacklistPlatforms, SupportedTargetPlatforms und WhitelistPlatforms tun (wählen Sie einen oder mehrere Parameter).
Nach Aufnahme von Plattformen in die White-/Blacklist zur Nutzung von Modulen als C++-Abhängigkeiten müssen Sie den Modulnamen in PublicDependencyModuleNames und/oder PrivateDependencyModuleNames einschließen. Ab diesem Punkt werden die Dinge weniger trivial.

Zunächst muss man Module ausschließen, die manche Plattformen nicht unterstützen. Der einfachste Weg dazu ist die Verwendung von if/else-Anweisungen in den Build.cs-Dateien Ihrer Module.

Zum Beispiel:
Zweitens brauchen Sie plattformspezifische Kompilierungsflags. Zum Beispiel:
Mehr zu den C++-Präprozessor-Direktiven hier.
Mehrspielerkarte in STRIDE

Handhabung mehrerer Plattformen auf einem Betriebssystem

Beim Entwickeln von STRIDE standen wir vor der Herausforderung, dass einige VR-Zielplattformen (Pico & Meta Quest) beide auf Android liefen. Dies führte zu Problemen: Nun konnten wir die Configs der Unreal Platform nicht mehr verwenden, um Pico- und Quest-übergreifende Config-Werte zu unterscheiden. Im Code lässt sich normalerweise nicht klären, auf welcher Plattform man ist. Unsere Lösungen sahen wie folgt aus:

Das Konfigurationsproblem lässt sich mit einer Argument-Injektion per -ini im Unreal Buildwerkzeug lösen. Sie können sie in CI/CD-Build-Skripten implementieren und -ini-Konfigurationen ans Unreal Automatisierungswerkzeug senden, um Configs aufzuheben. Übergabe im Format:
Mehr zu Konfigurationsinjektionen hier. Injizierte Config-Werte sind auf gepackte Spiele durch Modifikation des Unreal-Automatisierungswerkzeugs anwendbar. Mehr dazu hier.

Nun können Sie Config-Werte ans UAT senden und sehen, auf welcher Plattform Sie sind.

Nächster Schritt: Plattformdefinitionen für Pico- und Quest-Geräte ergänzen. Man beachte den Unterschied bei GlobalDefinitions aus Zielregeln und PublicDefinitions aus Modulregeln. Normal erstellt UAT zwei Builds: einen für den Editor (um Commandlets während des Builds auszuführen), einen für die Zielplattform. GlobalDefinitions sind Definitions für das Gesamtziel. Das heißt: Eine Plattformdefinition für Pico/Quest in GlobalDefinitions enthält die unerwünschte Plattformdefinition im Editor-Build (zur Ausführung von Commandlets erforderlich). Sie möchten also eine Plattformdefinition in den PublicDefinitions der Modulregeln platzieren. So gehen Sie dabei vor:
Nun zeigen Präprozessor-Direktiven, auf welcher Plattform Sie im C++-Code sind, und nutzen BlueprintFunctionLibrary zur Bereitstellung von Plattformchecks für Blueprints.

Verbinden mit eigens erstellten Backend-Diensten

Dieser Blog-Teil dreht sich um unseren Ansatz zur Integration von Backend-Diensten. Unser Backend besteht aus HTTP-APIs und einer WebSocket-API. Die Implementierung von HTTP-Anfragen und WebSocket-Events ist simpel, also beschränke ich mich auf unseren Ansatz zur Verkettung asynchroner Aufrufe.
Plattformübergreifende Hub-Position in STRIDE
Wir fingen damit an, API-Aufrufe mit Lambda-Funktionscallbacks für Antworten zu implementieren. Bald hatten wir jedoch einen Haufen verschachtelter Aufrufe, und die Codepflege wurde zunehmend schwierig. So machten wir jede Anforderung zur eigenen UBlueprintAsyncActionBase.

Es ist recht einfach, automatisch generierte UBlueprintAsyncActionBase-Knoten in Blueprints zu verwenden. Hier ist der Programmier -Guide.
 
Get Stride Net User Data

Wir haben jedoch noch ein wichtiges Problem zu lösen: Wo rufen wir die Knoten auf? Vereinzelt könne man sie in Spielentitäten aufrufen, und das ist eine gute Platzierung. Aber was ist mit Aufrufen auf der GameInstance-Ebene? Die Lösung: Wir nutzen ein erweitertes UObject für die „Worker“-Entität .

Worker sind aus UObject abgeleitete Klassen zur Lebenszykluskontrolle, deren GameInstanceSubsystems wir nutzen. Mehr zur Programmierung von Teilsystemen hier. Man braucht keine GameInstanceSubsysteme zu nutzen. Überdies wären LocalPlayerSubsystems wahrscheinlich die bessere Lösung.

Workers machen die Pflege von Aufrufketten sehr einfach.
Jetzt entzaubere ich die UObject-Erweiterung und gebe ein paar weniger bekannte Tipps.

In erster Linie wollen Sie nur eines erweitern. GetWorld, um globale Funktionen mit WorldContext aufzurufen. Hier ein Codebeispiel für den Worker GetWorld-Override.
Achten Sie auf die erste Kontrolle bei CDO. Das ergibt dann später Sinn.

Hier ist das Code-Beispiel zur Erstellung von Worker.
Ich fasse zusammen. Sie sollten von UObject ableiten, GetWorld übergehen, dann vom C++-Worker ableiten und final das richtige Objekt im Lebenszykluskern Ihres Workers instanziieren. So erhält man eine Klasse aus Blueprints als Instanz eines korrekten Objekts.
Nun zu CDO: Wie Sie sehen, weisen wir die TSubclassOf-Variable beim Bau von GameInstanceSubsystem zu. Und da könnten Sie Probleme kriegen, es sei denn, Sie prüfen auf CDO. Wir hatten es ohne CDO-Check mit Editor-Crashs und Problemen im Assetsystem zu tun. Das könnte bei Ihnen also vielleicht auch auftreten.

Die Trennung der Anforderungen und die Offenlegung der Anforderungsketten in Blueprint-Graphen halfen uns, „Spaghetti-Code“ loszuwerden, und erleichterten die Wartung des Backend-Codes, was für kürzere Iterationen und weniger Bugs sorgte.
Falls Sie mehr über unsere Spiele erfahren und einen Blick hinter die Kulissen der VR-Spielentwicklung werfen möchten, folgen Sie Joy Way auf Twitter und unserem Discord-Server.

    Sichern Sie sich die Unreal Engine noch heute!

    Sichern Sie sich das offenste und fortschrittlichste Werkzeug der Welt.
    Die Unreal Engine wird mit allen Funktionen und vollem Zugriff auf den Quellcode geliefert und ist sofort einsatzbereit.