Zurück
Wie wir die Speedway-Rennen-Vorlage in Fortnite mit Verse-Persistenz und weiteren Features verbessert haben
Das Fortnite-Team
Die Einführung der Verse-Persistenz hat uns die Gelegenheit gegeben, die „Ein Speedway-Rennen gestalten“-Vorlageinsel für den Fortnite-Kreativmodus zu verbessern. Von Updates wie dem Hinzufügen von Filmsequenzen und Landschaftsbau bis hin zum Abschaffen von Auslösern und der Verwendung von Verse – Wir freuen uns, den Entwicklungsprozess dieser Vorlage mit euch zu teilen. Willkommen beim Speedway-Rennen mit Verse-Persistenz!
Die „Ein Speedway-Rennen gestalten“-Vorlage wurde ursprünglich erstellt, um die neuen Rennstrecken-Assets zu verwenden und eine ausgeprägte Spielerfahrung mit einigen benutzerfreundlichen Features zu erstellen.
Mit dem neuesten Update haben wir die Karte gänzlich überarbeitet und viele ihrer Features ersetzt oder verbessert. Schauen wir uns an, welche Verbesserungen wir mithilfe der leistungsstarken Möglichkeiten von UEFN durchgeführt haben.
Der Tag/Nacht-Zyklus des originalen Projekts wurde mit dem fortgeschritteneren Lichtsystem von Fortnite Battle Royale – Kapitel 4 ausgetauscht. Dieser neue Zyklus ermöglichte uns die Verwendung von Lumen, wodurch wir für sanftere Schatten und eine realistischere globale Beleuchtung sorgen konnten. Wir haben auch visuelle Features wie einen Wasserfall hinzugefügt und die Strecke selbst visuell interessanter gestaltet.
Verse-Persistenz bietet dafür eine Lösung, indem sie die Datenverfolgung über mehrere Spielsitzungen hinweg ermöglicht. Diese Funktionalität ermöglichte es uns, die gesamten Spieler-Statistiken einzusehen und lokale Ranglisten zu erstellen.
Unser Ziel dabei war es, die beste Rundenzeit eines Spieler nach einer abgeschlossenen Runde zu aktualisieren und dessen Punkte sowie Siege zu zählen, wenn er ein Rennen beendet.
Das Aktualisieren der Rundenzeiten war relativ einfach, da wir auf das LapCompletedEvent des Rennmanager-Geräts warten konnten, um festzustellen, wann ein Spieler eine Runde beendet. Sobald das Rennen beginnt, starten wir ein Timer-Gerät, das für jeden Spieler läuft. Die Funktion „WaitForPlayerToFinishLap“ wartet darauf, dass der Spieler eine Runde beendet, berechnet die Rundenzeit, aktualisiert die Statistiken-Tabelle und setzt dann den Timer zum Aufzeichnen der nächsten Runde zurück.
Um dieses Problem zu lösen, haben wir eine ArraySync-Funktion verwendet. ArraySync ruft eine asynchrone Funktion bei jedem weitergegebenen Element im Array ab und wartet, bis alle Funktionen abgeschlossen sind. Indem ein Array von Spielern und die Funktion „WaitForPlayToFinishRace“ weitergegeben wird, konnten wir die Funktion für jeden Spieler aufrufen und darauf warten, dass sie für alle abgeschlossen wurde.
Dadurch konnten wir Punkte und Siege basierend auf der Platzierung vergeben, die Spieler beim Abschluss des Rennens hatten, und ihre Statistiken entsprechend aktualisieren. Wenn ArraySync fertig ist, wissen wir, dass alle Spieler das Rennen abgeschlossen haben, und können das Spiel beenden.
Da wir bereits dauerhafte Statistiken für alle Spieler hatten, konnten wir diese Statistiken abrufen und mit Reklametafeln anzeigen. Im Wartebereich vor dem Spiel haben wir Reklametafel- und Spielerbezug-Geräte für jeden Spieler platziert, um sowohl ihre Statistiken als auch ihre zu dem Zeitpunkt genutzten Outfits zu zeigen. Es war allerdings immer noch schwer, diese Spieler nach ihrer Leistung zu sortieren.
Dafür haben wir den „Zusammenfügendes Sortieren“-Algorithmus verwendet. „Zusammenfügendes Sortieren“ ist ein oft verwendeter Algorithmus, der ein Array rekursiv in zwei teilt, jedes dieser Sub-Arrays sortiert und sie dann wieder zusammenfügt. Wir haben dem Algorithmus auch die Fähigkeit hinzugefügt, eine Vergleichsfunktion durchlaufen zu lassen, und wir haben Vergleichsfunktionen für alle verschiedenen Spieler-Statistiken erstellt.
Jedes Mal, wenn wir die Spieler sortieren mussten, konnten wir die Statistiken für jeden Spieler von ihrer Statistik-Tabelle abrufen und sie alle einem Array hinzufügen. Dann haben wir sie sowie eine Vergleichsfunktion dem „Zusammenfügendes Sortieren“-Algorithmus gegeben. Indem wir auswählten, welche Vergleichsfunktion wir durchlaufen lassen wollen, konnten wir das Array der Spieler nach einer beliebigen Statistik sortieren. Wir haben sowohl den „Zusammenführendes Sortieren“-Algorithmus als auch die Testdatei der neuen Vorlage beigefügt, damit ihr den Algorithmus selbst ausprobieren und ihn an eure eigenen Funktionen anpassen könnt!
Da wir unsere Sortierungsmechanismen nun hatten, haben wir unsere Ranglisten vervollständigt. Während der ersten Runde des Spiels, bevor es losgeht, spawnen Spieler in einem Wartebereich mit ihren Spielerbezügen und den Reklametafeln. Wir sortieren diese Spielerberzüge nach den insgesamt erzielten Punkten jedes Spielers und zeigen all ihre Statistiken auf den Reklametafeln vor ihnen an. Dadurch können Spieler einen Eindruck über ihre Gegenspieler gewinnen, bevor das Rennen startet, und erfahren, auf wen sie aufpassen müssen, um zu gewinnen.
Spielt weiter, dann landet ihr vielleicht auch irgendwann oben auf der Rangliste!
Dafür haben wir eine zweite schwache Map-Variable für Spieler hinzugefügt, die CircuitInfo heißt. Diese „CircuitInfo“-Variable beinhaltet alle Informationen, die wir am Ende des Spiels oder beim Verlassen eines Spielers zurücksetzen müssen.
Wir verwendeten eine zweite schwache Map-Variable, um zu verdeutlichen, welche Infos zurückgesetzt werden sollen (Streckeninfo) und welche dauerhaft beibehalten werden sollen (Spieler-Statistiken).
Für jeden Spieler haben wir die folgenden Informationen in der schwachen Map-Variable „CircuitInfo“ gespeichert:
In der Funktion „OnBegin“ des Verse-Geräts (welches zu Beginn jeder Runde startet) stellen wir fest, in welcher Runde wir uns befinden, indem wir alle aktiven Spieler durchgehen und den Wert der höchsten, zuletzt abgeschlossenen Runde aus ihren dauerhaften Daten abrufen. Wir tun dies einmal pro Runde und zeichnen die Rundeninfo in einer schwachen Map-Variable für die Sitzung auf, damit der gesamte Verse-Code im Projekt jederzeit auf diesen Wert zugreifen kann, ohne ihn neu berechnen zu müssen.
Wenn Spieler das Rennen abschließen, was dadurch erkannt wird, dass auf RaceCompletedEvent des Rennmanager-Geräts gewartet wird, halten wir die Reihenfolge, in der sie die Runde abgeschlossen haben, sowie die aktuelle Runde in der schwachen „CircuitInfo“-Map-Variable fest, die mit dem Spieler assoziiert ist. Wir setzen diese Information unter zwei Bedingungen zurück:
In der aktualisierten Vorlage haben wir den Impulsauslöser mit dem Sequencer ersetzt, um unsere Eröffnungsszene zu gestalten. Mit dem Sequencer waren wir in der Lage, verschiedene Kameras, Heads-up-Display-Elemente (HUD-Elemente) und eine dynamische Ansicht der Startreihenfolge, die sich an die Anzahl der aktiven Spieler anpasst, hinzuzufügen.
Wir haben Geräte ähnlich wie bei der Konfiguration des Impulsauslösers bei wichtigen Momenten mit der Sequenz verbunden, wodurch wir festlegen konnten, wann die Punktzahl des nächsten Spielers angezeigt oder die Eröffnungssequenz beendet werden soll.
Wir freuen uns darauf, diese Vorlage zu aktualisieren, wenn neue Features veröffentlicht werden, die ihr Design verbessern. In der Zwischenzeit könnt ihr euch diese Vorlage herunterladen, ihre Komponenten begutachten und ihre Funktionalität in eure eigenen Projekte mithilfe des Vorlage-Tabs in UEFN einbauen. Wir können es kaum erwarten, eure Rennen, Ranglisten und mehr zu sehen!
Die „Ein Speedway-Rennen gestalten“-Vorlage wurde ursprünglich erstellt, um die neuen Rennstrecken-Assets zu verwenden und eine ausgeprägte Spielerfahrung mit einigen benutzerfreundlichen Features zu erstellen.
Mit dem neuesten Update haben wir die Karte gänzlich überarbeitet und viele ihrer Features ersetzt oder verbessert. Schauen wir uns an, welche Verbesserungen wir mithilfe der leistungsstarken Möglichkeiten von UEFN durchgeführt haben.
Visuelles Design
Der Landschafts-Modus in UEFN ermöglichte es uns, zu den Wurzeln des Rennsports zurückzukehren und eine Offroad-Strecke mit den neuen Werkzeugen der Landschafts-Bearbeitung zu erstellen. Die Berge im Hintergrund unserer originalen Insel, die aus Felsen-Assets bestanden, wurden mit einem natürlicheren Landschaftsdesign ersetzt.Der Tag/Nacht-Zyklus des originalen Projekts wurde mit dem fortgeschritteneren Lichtsystem von Fortnite Battle Royale – Kapitel 4 ausgetauscht. Dieser neue Zyklus ermöglichte uns die Verwendung von Lumen, wodurch wir für sanftere Schatten und eine realistischere globale Beleuchtung sorgen konnten. Wir haben auch visuelle Features wie einen Wasserfall hinzugefügt und die Strecke selbst visuell interessanter gestaltet.
Verse-Persistenz: Eine bessere Rangliste
Auf der originalen Karte verwendete ein Turm das Spielerbezug-Gerät, um den Spieler auf dem 1. Platz sowie dessen Punkte anzuzeigen. Die Daten wurden allerdings nicht zwischen Spielsitzungen übertragen.Verse-Persistenz bietet dafür eine Lösung, indem sie die Datenverfolgung über mehrere Spielsitzungen hinweg ermöglicht. Diese Funktionalität ermöglichte es uns, die gesamten Spieler-Statistiken einzusehen und lokale Ranglisten zu erstellen.
Spieler-Statistiken verfolgen
Wir haben eine dauerhafte Tabellenklasse für Spieler-Statistiken erstellt, die Siege, beste Rundenzeit und verdiente Punkte pro Rennen für die gesamte Spielzeit eines Spielers mitverfolgen. Wir haben auch eine dauerhafte Schwache Map namens PlayerStatsMap verwendet, um Spieler ihren Spieler-Statistiken-Tabellen zuzuweisen, damit diese Statistiken über mehrere Runden und Sitzungen hinweg fortbestehen können. Dann haben wir eine Statistikmanager-Klasse erstellt, um das Initialisieren, Finden und Aktualisieren dieser Statistiken für jeden Spieler durchzuführen.Unser Ziel dabei war es, die beste Rundenzeit eines Spieler nach einer abgeschlossenen Runde zu aktualisieren und dessen Punkte sowie Siege zu zählen, wenn er ein Rennen beendet.
Das Aktualisieren der Rundenzeiten war relativ einfach, da wir auf das LapCompletedEvent des Rennmanager-Geräts warten konnten, um festzustellen, wann ein Spieler eine Runde beendet. Sobald das Rennen beginnt, starten wir ein Timer-Gerät, das für jeden Spieler läuft. Die Funktion „WaitForPlayerToFinishLap“ wartet darauf, dass der Spieler eine Runde beendet, berechnet die Rundenzeit, aktualisiert die Statistiken-Tabelle und setzt dann den Timer zum Aufzeichnen der nächsten Runde zurück.
Zählen von Punkten und Siegen
Das Zählen von Punkten und Siegen war etwas komplizierter. Wir wussten, dass wir die etwas ähnliche „WaitForPlayerToFinishRace“-Funktion nutzen konnten, um auf „RaceCompletedEvent“ des Rennmanagers zu warten und so festzustellen, wann ein Spieler ein Rennen beendet. Allerdings wollten wir, dass das Spiel erst endet, wenn alle Spieler das Rennen abgeschlossen haben, und der Rennmanager hatte keine Möglichkeit, um das mitzuverfolgen oder das Rennen zu diesem Zeitpunkt zu beenden.Um dieses Problem zu lösen, haben wir eine ArraySync-Funktion verwendet. ArraySync ruft eine asynchrone Funktion bei jedem weitergegebenen Element im Array ab und wartet, bis alle Funktionen abgeschlossen sind. Indem ein Array von Spielern und die Funktion „WaitForPlayToFinishRace“ weitergegeben wird, konnten wir die Funktion für jeden Spieler aufrufen und darauf warten, dass sie für alle abgeschlossen wurde.
Dadurch konnten wir Punkte und Siege basierend auf der Platzierung vergeben, die Spieler beim Abschluss des Rennens hatten, und ihre Statistiken entsprechend aktualisieren. Wenn ArraySync fertig ist, wissen wir, dass alle Spieler das Rennen abgeschlossen haben, und können das Spiel beenden.
Ergebnisse anzeigen
Die Aufzeichnung der Statistiken war eine Sache – aber wir mussten auch herausfinden, wie wir diese Statistiken Spielern anzeigen. Wir wollten Ranglisten im Spiel erstellen, die sichtbar sind und die Spieler nach ihrer Gesamtpunktzahl sortieren, um die Spieler mit den meisten Punkten hervorzuheben.Da wir bereits dauerhafte Statistiken für alle Spieler hatten, konnten wir diese Statistiken abrufen und mit Reklametafeln anzeigen. Im Wartebereich vor dem Spiel haben wir Reklametafel- und Spielerbezug-Geräte für jeden Spieler platziert, um sowohl ihre Statistiken als auch ihre zu dem Zeitpunkt genutzten Outfits zu zeigen. Es war allerdings immer noch schwer, diese Spieler nach ihrer Leistung zu sortieren.
Dafür haben wir den „Zusammenfügendes Sortieren“-Algorithmus verwendet. „Zusammenfügendes Sortieren“ ist ein oft verwendeter Algorithmus, der ein Array rekursiv in zwei teilt, jedes dieser Sub-Arrays sortiert und sie dann wieder zusammenfügt. Wir haben dem Algorithmus auch die Fähigkeit hinzugefügt, eine Vergleichsfunktion durchlaufen zu lassen, und wir haben Vergleichsfunktionen für alle verschiedenen Spieler-Statistiken erstellt.
Jedes Mal, wenn wir die Spieler sortieren mussten, konnten wir die Statistiken für jeden Spieler von ihrer Statistik-Tabelle abrufen und sie alle einem Array hinzufügen. Dann haben wir sie sowie eine Vergleichsfunktion dem „Zusammenfügendes Sortieren“-Algorithmus gegeben. Indem wir auswählten, welche Vergleichsfunktion wir durchlaufen lassen wollen, konnten wir das Array der Spieler nach einer beliebigen Statistik sortieren. Wir haben sowohl den „Zusammenführendes Sortieren“-Algorithmus als auch die Testdatei der neuen Vorlage beigefügt, damit ihr den Algorithmus selbst ausprobieren und ihn an eure eigenen Funktionen anpassen könnt!
Da wir unsere Sortierungsmechanismen nun hatten, haben wir unsere Ranglisten vervollständigt. Während der ersten Runde des Spiels, bevor es losgeht, spawnen Spieler in einem Wartebereich mit ihren Spielerbezügen und den Reklametafeln. Wir sortieren diese Spielerberzüge nach den insgesamt erzielten Punkten jedes Spielers und zeigen all ihre Statistiken auf den Reklametafeln vor ihnen an. Dadurch können Spieler einen Eindruck über ihre Gegenspieler gewinnen, bevor das Rennen startet, und erfahren, auf wen sie aufpassen müssen, um zu gewinnen.
Spielt weiter, dann landet ihr vielleicht auch irgendwann oben auf der Rangliste!
Die Fahrerreihenfolge an der Startlinie
Die Fortnite-Kreativmodus-Version der Karte platziert Spieler zu Beginn des Spiels in einer zufälligen Reihenfolge. Um Spieler dazu anzuregen, noch höhere Ränge auf der Rangliste zu erreichen, basiert die Reihenfolge an der Startlinie nun auf ihrer Endposition der letzten Runde.Rundeninfos verfolgen
Anders als die fortbestehenden Daten, die wir für die Rangliste verwendet haben, mussten wir auch die Fahrerreihenfolge und die Streckeninformationen über mehrere Runden hinweg beibehalten, allerdings nicht über mehrere Spielsitzungen hinweg. Eine schwache Map der Sitzung in Verse setzt die Daten jede Runde zurück, wir müssen diese Information also für jeden Spieler speichern und dann nach dem Spielende zurücksetzen.Dafür haben wir eine zweite schwache Map-Variable für Spieler hinzugefügt, die CircuitInfo heißt. Diese „CircuitInfo“-Variable beinhaltet alle Informationen, die wir am Ende des Spiels oder beim Verlassen eines Spielers zurücksetzen müssen.
Wir verwendeten eine zweite schwache Map-Variable, um zu verdeutlichen, welche Infos zurückgesetzt werden sollen (Streckeninfo) und welche dauerhaft beibehalten werden sollen (Spieler-Statistiken).
Für jeden Spieler haben wir die folgenden Informationen in der schwachen Map-Variable „CircuitInfo“ gespeichert:
- Abschlussreihenfolge
- Letzte abgeschlossene Runde
Rundenspezifische Logik
Wir mussten in Erfahrung bringen, in welcher Runde wir uns befinden, um rundenspezifische Logik (wie die Reihenfolge der Spieler basierend auf ihrer Rennleistung, wenn es nicht die erste Runde ist) anzuwenden und um zu erfahren, wann wir die Spielerdaten zurücksetzen müssen. Es gibt aktuell keine API dafür, um die aktuelle Runde abzurufen, weswegen wir sie in den dauerhaften Daten für jeden Spieler abrufen müssen.In der Funktion „OnBegin“ des Verse-Geräts (welches zu Beginn jeder Runde startet) stellen wir fest, in welcher Runde wir uns befinden, indem wir alle aktiven Spieler durchgehen und den Wert der höchsten, zuletzt abgeschlossenen Runde aus ihren dauerhaften Daten abrufen. Wir tun dies einmal pro Runde und zeichnen die Rundeninfo in einer schwachen Map-Variable für die Sitzung auf, damit der gesamte Verse-Code im Projekt jederzeit auf diesen Wert zugreifen kann, ohne ihn neu berechnen zu müssen.
Wenn Spieler das Rennen abschließen, was dadurch erkannt wird, dass auf RaceCompletedEvent des Rennmanager-Geräts gewartet wird, halten wir die Reihenfolge, in der sie die Runde abgeschlossen haben, sowie die aktuelle Runde in der schwachen „CircuitInfo“-Map-Variable fest, die mit dem Spieler assoziiert ist. Wir setzen diese Information unter zwei Bedingungen zurück:
- Der Spieler verlässt das Spiel. Wir abonnieren das PlayerRemovedEvent in Playspace, um zu erfahren, wann Spieler das Spiel verlassen, und rufen dann unsere Funktion „ResetCircuitInfo“ auf.
- Zu Beginn jeder Runde berechnen wir die zuletzt abgeschlossene Runde. Wenn es sich um die letzte Runde handelte, fordern wir ResetCircuitInfo dazu auf, die Daten des Spielers zurückzusetzen. Wir wissen, wie viele Runden es gibt, da wir dem Verse-Gerät eine bearbeitbare Eigenschaft hinzugefügt haben, bei der die Gesamtanzahl von Runden in einem Team eingestellt ist. Dies ist im Insel-Creator, um sicherzugehen, dass es mit den Rundeneinstellungen übereinstimmt.
Wann sollte man eine schwache Map der Sitzung gegenüber einer schwachen Map der Spieler verwenden?
Dieses neue Speedway-Rennen ist ein großartiges Beispiel, um die Unterschiede und verschiedenen Gründe bei der Verwendung von schwachen Map-Variablen für Sitzungen oder Spieler in eurem Code hervorzuheben:- Schwache Map-Variablen für Sitzungen sind nützlich für Singletons und um Daten für die aktuelle Runde zu speichern, die ihr nicht jedes Mal neu berechnen wollt.
- Die schwachen Map-Variablen für Spieler wurden für Informationen entworfen, die über mehrere Runden oder Spielsitzungen hinweg erhalten bleiben müssen, sie müssen allerdings mit individuellen Spielern assoziiert sein.
Impulsauslöser für die Eröffnungssequenz
In der originalen Version der Speedway-Rennen-Vorlage haben wir ein Gerät namens „Impulsauslöser“ verwendet, um den „Auf die Plätze, fertig, los“-Teil des Rennens einzurichten. Der Impulsauslöser spielt eine Event-Sequenz in einem festgelegten Zeitraum ab, indem er Auslöser aktiviert, um so Text anzuzeigen und die Lichter an der Startlinie einzuschalten.In der aktualisierten Vorlage haben wir den Impulsauslöser mit dem Sequencer ersetzt, um unsere Eröffnungsszene zu gestalten. Mit dem Sequencer waren wir in der Lage, verschiedene Kameras, Heads-up-Display-Elemente (HUD-Elemente) und eine dynamische Ansicht der Startreihenfolge, die sich an die Anzahl der aktiven Spieler anpasst, hinzuzufügen.
Wir haben Geräte ähnlich wie bei der Konfiguration des Impulsauslösers bei wichtigen Momenten mit der Sequenz verbunden, wodurch wir festlegen konnten, wann die Punktzahl des nächsten Spielers angezeigt oder die Eröffnungssequenz beendet werden soll.
Was kommt als Nächstes?
Während UEFN weiter Fortschritte erzielt, wollen wir auch diese Vorlage weiterentwickeln. Es ist unser Ziel, euch weiterhin dabei zu unterstützen, detaillierte Inhalte mit UEFN zu erstellen, indem ihr mit den neuesten Features spaßige und mitreißende Inseln baut.Wir freuen uns darauf, diese Vorlage zu aktualisieren, wenn neue Features veröffentlicht werden, die ihr Design verbessern. In der Zwischenzeit könnt ihr euch diese Vorlage herunterladen, ihre Komponenten begutachten und ihre Funktionalität in eure eigenen Projekte mithilfe des Vorlage-Tabs in UEFN einbauen. Wir können es kaum erwarten, eure Rennen, Ranglisten und mehr zu sehen!