Retour
Comment nous avons amélioré le modèle Speedway Race dans Fortnite avec la persistance Verse, entre autres choses
L'équipe Fortnite
La mise en place de la persistance Verse nous a donné l'occasion d'améliorer le modèle d'île "Concevoir un circuit – Speedway Race" du mode Créatif de Fortnite dans l'UEFN. De l'ajout de cinématiques et de paysages à l'arrêt des déclencheurs pour passer à Verse, nous sommes ravis de partager avec vous les détails du développement de ce modèle. Bienvenue dans Speedway Race avec la persistance Verse !
Le modèle "Concevoir un circuit – Speedway Race" a été initialement créé pour utiliser les ressources de pistes de course récemment créées et pour proposer une expérience de course à la fois originale et accessible au plus grand nombre.
La dernière mise à jour a été l'occasion pour nous d'adopter une approche holistique et de remplacer et améliorer beaucoup de fonctionnalités sur l'ensemble de la carte. Découvrons certaines nouveautés qui tirent parti des puissantes capacités de l'UEFN.
L'éclairage plus avancé de Fortnite Battle Royale Chapitre 4 est quant à lui venu remplacer l'ancien cycle jour/nuit du projet. Ce nouveau cycle nous a permis d'utiliser Lumen pour créer des ombres plus douces et une illumination globale réaliste. Nous avons également ajouté des éléments, comme une cascade, et avons rendu la piste plus intéressante visuellement.
La persistance Verse a réglé le problème, permettant le suivi des données tout au long des sessions de jeu. Cette fonctionnalité nous permet de surveiller la totalité des statistiques des joueurs et de créer des classements locaux.
Notre objectif était de mettre à jour le meilleur temps au tour d'un joueur et d'enregistrer ses points et victoires à chaque course terminée.
Mettre à jour les temps au tour a été simple, car nous n'avions qu'à attendre que le LapCompletedEvent du gestionnaire de course détecte un joueur ayant terminé un tour. Lorsque la course commence, le chronomètre de chaque joueur est lancé. La fonction WaitForPlayerToFinishLap attend qu'un joueur ait terminé un tour, calcule son temps au tour, met à jour son tableau de stats, puis réinitialise le chronomètre pour l'enregistrement du tour suivant.
Pour y remédier, nous avons utilisé la fonction ArraySync. Elle appelle une fonction asynchrone sur chaque élément transmis dans la matrice et attend que toutes ces fonctions soient terminées. En transmettant une matrice des joueurs et la fonction WaitForPlayerToFinishRace, nous avons pu appeler la fonction sur chacun d'eux et attendre qu'elle se finisse pour tous.
Cela nous a permis d'attribuer des points et la victoire en fonction de leur position à l'issue de la course, puis de mettre à jour leurs tableaux de statistiques. À la fin d'ArraySync, nous savons que tous les joueurs ont terminé la course et nous pouvons y mettre fin.
Étant donné que nous disposions des stats persistantes pour chaque joueur, nous pouvions les récupérer et les afficher via des panneaux d'affichage. Dans la zone d'attente d'avant-jeu, nous avons ajouté les appareils Panneau d'affichage et Référence de joueur pour afficher leurs statistiques et les tenues utilisées. Mais trier les joueurs en fonction de leurs performances ne s'est pas avéré une mince affaire.
Pour y parvenir, nous avons implémenté l'algorithme Merge Sort. Il s'agit d'un algorithme de tri qui divise une matrice en deux de manière récursive, trie chaque partie de la matrice divisée, puis les fusionne à nouveau. Nous avons aussi inclus une fonction de comparaison à cet algorithme et créé des fonctions de comparaison pour les différentes statistiques de chaque joueur.
Lorsque nous devions trier les joueurs, nous pouvions récupérer leurs stats depuis leurs tableaux de stats, les ajouter à une matrice et les transmettre – ainsi qu'une fonction de comparaison – à l'algorithme Merge Sort. En choisissant quelle fonction de comparaison transmettre, nous avons pu retrouver la matrice des joueurs triés selon n'importe quelle statistique. Nous avons inclus l'algorithme Merge Sort et un fichier de test dans le nouveau modèle, pour que vous puissiez tester cet algorithme et l'adapter à vos propres fonctions.
Avec les mécanismes de tri en place, nous avons pu finaliser nos classements. Avant la première manche, les joueurs apparaissent dans une zone d'attente d'avant-jeu avec leurs références et panneaux d'affichage. Nous trions ces références de joueur selon la totalité des points des joueurs depuis leur première partie et nous affichons chacune de leurs statistiques sur le panneau d'affichage qui leur fait face. Cela leur permet d'examiner leurs concurrents et de déterminer à qui faire attention pour mettre toutes les chances de leur côté.
Continuez à jouer et vous pourriez vous retrouver en tête du classement !

Pour ce faire, nous avons créé une seconde variable de mappage faible intitulée CircuitInfo. Elle contient toutes les informations à réinitialiser à la fin de la partie ou lorsqu'un joueur la quitte.
Nous avons utilisé une seconde variable de mappage faible pour bien déterminer les informations à réinitialiser (infos de circuit) et celles à conserver éternellement (stats des joueurs).
Nous enregistrons les informations suivantes pour chaque joueur dans la variable de mappage faible CircuitInfo :
La fonction OnBegin de l'appareil Verse, exécutée au début de chaque manche, nous permet de savoir quelle manche est en cours en itérant pour tous les joueurs actifs et en obtenant la plus haute valeur de la dernière manche terminée à partir de leurs données persistantes. Nous la lançons une fois par manche et enregistrons les infos de la manche dans une variable de mappage faible de session, pour que le code Verse du projet puisse accéder à cette valeur à tout moment sans devoir la recalculer.
Lorsqu'un joueur termine la course, ce qui est détecté par le RaceCompletedEvent du gestionnaire de course, nous enregistrons sa position finale et la manche actuelle dans la variable de mappage faible CircuitInfo associée au joueur. Nous réinitialisons ces informations selon deux conditions :
Dans la mise à jour du modèle, nous avons remplacé ce déclencheur d'impulsions par Sequencer pour obtenir notre cinématique d'ouverture. Sequencer nous a permis d'ajouter différentes caméras, des éléments d'affichage tête haute (ATH) ainsi qu'une vue d'alignement dynamique qui s'ajuste en fonction du nombre de joueurs actifs.
Comme pour la configuration du déclencheur d'impulsions, nous avons lié des appareils à la séquence à des moments clés, ce qui nous a permis de savoir à quel moment afficher le prochain score d'un joueur ou couper l'intro.

Nous sommes impatients de mettre à ce jour ce modèle et de l'embellir davantage à l'aide de nouvelles fonctionnalités. En attendant, vous pouvez le télécharger, découvrir les fonctionnalités déjà présentes et les intégrer à vos projets via l'onglet Modèles de l'UEFN. Nous avons hâte de voir prendre forme vos courses et vos classements, entre autres !
Le modèle "Concevoir un circuit – Speedway Race" a été initialement créé pour utiliser les ressources de pistes de course récemment créées et pour proposer une expérience de course à la fois originale et accessible au plus grand nombre.
La dernière mise à jour a été l'occasion pour nous d'adopter une approche holistique et de remplacer et améliorer beaucoup de fonctionnalités sur l'ensemble de la carte. Découvrons certaines nouveautés qui tirent parti des puissantes capacités de l'UEFN.
Conception visuelle
Le mode Paysage de l'UEFN nous a permis de revenir aux origines des jeux de course et de créer une piste tout-terrain à l'aide des nouveaux outils de modification de paysage. Un paysage visuellement plus naturel a remplacé les montagnes créées avec un amoncellement de ressources de rochers qui habillaient l'arrière-plan de notre île d'origine.L'éclairage plus avancé de Fortnite Battle Royale Chapitre 4 est quant à lui venu remplacer l'ancien cycle jour/nuit du projet. Ce nouveau cycle nous a permis d'utiliser Lumen pour créer des ombres plus douces et une illumination globale réaliste. Nous avons également ajouté des éléments, comme une cascade, et avons rendu la piste plus intéressante visuellement.
Persistance Verse : des classements affinés
Sur la carte d'origine, une tour utilisant l'appareil Référence de joueur affichait le joueur à la première place ainsi que son nombre de points. Cependant, les données ne persistaient pas d'une session à l'autre.La persistance Verse a réglé le problème, permettant le suivi des données tout au long des sessions de jeu. Cette fonctionnalité nous permet de surveiller la totalité des statistiques des joueurs et de créer des classements locaux.
Suivi des statistiques des joueurs
Nous avons développé une classe persistante de tableaux de stats des joueurs qui suit leurs victoires, leurs meilleurs temps et les points gagnés par course terminée. Nous avons aussi utilisé un mappage faible persistant, PlayerStatsMap, pour mapper les joueurs à leurs tableaux de statistiques, permettant la conservation de ces statistiques sur l'ensemble des manches et sessions. Nous avons ensuite créé une classe de gestionnaire de statistiques pour gérer l'initialisation, la récupération et la mise à jour des statistiques de chaque joueur.Notre objectif était de mettre à jour le meilleur temps au tour d'un joueur et d'enregistrer ses points et victoires à chaque course terminée.
Mettre à jour les temps au tour a été simple, car nous n'avions qu'à attendre que le LapCompletedEvent du gestionnaire de course détecte un joueur ayant terminé un tour. Lorsque la course commence, le chronomètre de chaque joueur est lancé. La fonction WaitForPlayerToFinishLap attend qu'un joueur ait terminé un tour, calcule son temps au tour, met à jour son tableau de stats, puis réinitialise le chronomètre pour l'enregistrement du tour suivant.
Enregistrement des victoires et des points
Enregistrer les victoires et les points s'est avéré plus complexe. Nous savions que nous pouvions utiliser la fonction semblable WaitForPlayerToFinishRace pour attendre le RaceCompletedEvent du gestionnaire de course et savoir quand un joueur avait terminé. Mais nous voulions que la partie se termine uniquement lorsque tous les joueurs auraient terminé la course, et le gestionnaire de course n'avait aucun moyen de surveiller cette condition ni de mettre fin à la partie le cas échéant.Pour y remédier, nous avons utilisé la fonction ArraySync. Elle appelle une fonction asynchrone sur chaque élément transmis dans la matrice et attend que toutes ces fonctions soient terminées. En transmettant une matrice des joueurs et la fonction WaitForPlayerToFinishRace, nous avons pu appeler la fonction sur chacun d'eux et attendre qu'elle se finisse pour tous.
Cela nous a permis d'attribuer des points et la victoire en fonction de leur position à l'issue de la course, puis de mettre à jour leurs tableaux de statistiques. À la fin d'ArraySync, nous savons que tous les joueurs ont terminé la course et nous pouvons y mettre fin.
Affichage des résultats
Enregistrer les statistiques était une chose, mais il fallait également que les joueurs puissent les voir. Nous voulions créer des classements visibles dans les niveaux, et trier les joueurs en fonction de la totalité de leurs points acquis depuis leur toute première partie, pour mettre les meilleurs joueurs en avant.Étant donné que nous disposions des stats persistantes pour chaque joueur, nous pouvions les récupérer et les afficher via des panneaux d'affichage. Dans la zone d'attente d'avant-jeu, nous avons ajouté les appareils Panneau d'affichage et Référence de joueur pour afficher leurs statistiques et les tenues utilisées. Mais trier les joueurs en fonction de leurs performances ne s'est pas avéré une mince affaire.
Pour y parvenir, nous avons implémenté l'algorithme Merge Sort. Il s'agit d'un algorithme de tri qui divise une matrice en deux de manière récursive, trie chaque partie de la matrice divisée, puis les fusionne à nouveau. Nous avons aussi inclus une fonction de comparaison à cet algorithme et créé des fonctions de comparaison pour les différentes statistiques de chaque joueur.
Lorsque nous devions trier les joueurs, nous pouvions récupérer leurs stats depuis leurs tableaux de stats, les ajouter à une matrice et les transmettre – ainsi qu'une fonction de comparaison – à l'algorithme Merge Sort. En choisissant quelle fonction de comparaison transmettre, nous avons pu retrouver la matrice des joueurs triés selon n'importe quelle statistique. Nous avons inclus l'algorithme Merge Sort et un fichier de test dans le nouveau modèle, pour que vous puissiez tester cet algorithme et l'adapter à vos propres fonctions.
Avec les mécanismes de tri en place, nous avons pu finaliser nos classements. Avant la première manche, les joueurs apparaissent dans une zone d'attente d'avant-jeu avec leurs références et panneaux d'affichage. Nous trions ces références de joueur selon la totalité des points des joueurs depuis leur première partie et nous affichons chacune de leurs statistiques sur le panneau d'affichage qui leur fait face. Cela leur permet d'examiner leurs concurrents et de déterminer à qui faire attention pour mettre toutes les chances de leur côté.
Continuez à jouer et vous pourriez vous retrouver en tête du classement !

Ordre de course sur la ligne de départ
La version mode Créatife de Fortnite de la carte place les joueurs dans un ordre aléatoire au début de chaque partie. Afin de les motiver à grimper dans le classement, nous définissons l'ordre de la ligne de départ en fonction de leur position finale dans la manche précédente.Suivi des infos de manches
Contrairement aux données persistantes utilisées pour le classement, il fallait que les informations sur l'ordre de la course et sur le circuit soient conservées manche après manche, mais pas sur l'ensemble des sessions de jeu. Une variable de mappage faible de session dans Verse réinitialise ces données à chaque manche. Nous devions donc conserver les informations pour chaque joueur et les réinitialiser à la fin de la partie.Pour ce faire, nous avons créé une seconde variable de mappage faible intitulée CircuitInfo. Elle contient toutes les informations à réinitialiser à la fin de la partie ou lorsqu'un joueur la quitte.
Nous avons utilisé une seconde variable de mappage faible pour bien déterminer les informations à réinitialiser (infos de circuit) et celles à conserver éternellement (stats des joueurs).
Nous enregistrons les informations suivantes pour chaque joueur dans la variable de mappage faible CircuitInfo :
- Position finale
- Dernière manche terminée
Logique propre aux manches
Nous devons savoir quelle manche est en cours pour lui appliquer une logique spécifique (comme trier les joueurs selon leur position d'arrivée à la fin de la manche précédente si ce n'est pas la première manche), mais aussi savoir quand réinitialiser les données des joueurs. Il n'y a actuellement pas d'API pour connaître la manche en cours, c'est pourquoi nous devons l'enregistrer dans les données persistantes pour chaque joueur.La fonction OnBegin de l'appareil Verse, exécutée au début de chaque manche, nous permet de savoir quelle manche est en cours en itérant pour tous les joueurs actifs et en obtenant la plus haute valeur de la dernière manche terminée à partir de leurs données persistantes. Nous la lançons une fois par manche et enregistrons les infos de la manche dans une variable de mappage faible de session, pour que le code Verse du projet puisse accéder à cette valeur à tout moment sans devoir la recalculer.
Lorsqu'un joueur termine la course, ce qui est détecté par le RaceCompletedEvent du gestionnaire de course, nous enregistrons sa position finale et la manche actuelle dans la variable de mappage faible CircuitInfo associée au joueur. Nous réinitialisons ces informations selon deux conditions :
- Le joueur quitte la partie. Nous nous abonnons au PlayerRemovedEvent de l'espace de jeu pour savoir quand il quitte la partie et appelons notre fonction ResetCircuitInfo.
- Au début de chaque manche, nous calculons la dernière manche terminée. S'il s'agit de l'ultime manche, nous appelons ResetCircuitInfo pour actualiser les données du joueur. Nous connaissons le nombre de manches grâce à une propriété modifiable sur l'appareil Verse définie sur le nombre total de manches par partie. Le programme Créateurs d'îles intègre cette fonctionnalité pour assurer une correspondance avec les paramètres de manche.
Quand utiliser les mappages faibles de session et quand utiliser les mappages faibles de joueur
Ce nouveau modèle Speedway Race illustre bien les différences et le raisonnement derrière l'utilisation de la variable de mappage faible de session ou de joueur dans votre code :- Les variables de mappage faible de session sont utiles pour les singletons et le stockage des données de la manche en cours, pour vous éviter de procéder à un nouveau calcul à chaque fois.
- Les variables de mappage faible de joueur sont conçues pour les informations à faire persister d'une manche ou d'une session de jeu à une autre et à associer à chaque joueur.
Du Déclencheur d'impulsions à la séquence d'ouverture
Dans la version initiale du modèle Speedway Race, nous utilisions un appareil appelé "déclencheur d'impulsions" pour orchestrer le lancement de la course (à vos marques, prêts, partez !). Cet appareil jouait une séquence d'évènements pendant une durée déterminée en activant des déclencheurs pour afficher le texte et allumer des lumières sur la ligne de départ.Dans la mise à jour du modèle, nous avons remplacé ce déclencheur d'impulsions par Sequencer pour obtenir notre cinématique d'ouverture. Sequencer nous a permis d'ajouter différentes caméras, des éléments d'affichage tête haute (ATH) ainsi qu'une vue d'alignement dynamique qui s'ajuste en fonction du nombre de joueurs actifs.
Comme pour la configuration du déclencheur d'impulsions, nous avons lié des appareils à la séquence à des moments clés, ce qui nous a permis de savoir à quel moment afficher le prochain score d'un joueur ou couper l'intro.

Et ensuite ?
Nous espérons faire évoluer ce modèle à mesure qu'évolue l'UEFN. Notre objectif : vous donner les moyens de créer du contenu toujours plus complexe avec l'UEFN, en tirant parti des dernières fonctionnalités pour créer des îles divertissantes et stimulantes.Nous sommes impatients de mettre à ce jour ce modèle et de l'embellir davantage à l'aide de nouvelles fonctionnalités. En attendant, vous pouvez le télécharger, découvrir les fonctionnalités déjà présentes et les intégrer à vos projets via l'onglet Modèles de l'UEFN. Nous avons hâte de voir prendre forme vos courses et vos classements, entre autres !