Atrás
Cómo mejoramos la plantilla de Speedway Race en Fortnite con la persistencia de Verse y mucho más
El equipo de Fortnite
La introducción de la persistencia de Verse permitió pasar a UEFN la isla de la plantilla «Crea un juego de carreras en un circuito» del modo Creativo de Fortnite. Ha sido un placer compartir con vosotros la aventura que ha supuesto el desarrollo de esta plantilla, desde las actualizaciones, como en las que añadimos cinemáticas y paisajes, hasta como cuando dejamos de usar activadores y adoptamos Verse. ¡Bienvenidos a Circuito de carreras con persistencia de Verse!
La plantilla «Crea un juego de carreras en un circuito» se creó inicialmente para utilizar los recursos de circuito que acabábamos de añadir y construir una experiencia de carreras distinta con algunas funciones que mejoran la experiencia del usuario.
Con la última actualización, hemos adoptado un enfoque holístico y hemos sustituido y mejorado muchas funciones por todo el mapa. Echemos un vistazo a algunas de las actualizaciones que hemos realizado y que aprovechan las potentes prestaciones de UEFN.
El antiguo ciclo de día y noche del proyecto original fue sustituido por la iluminación más avanzada del capítulo 4 del Battle Royale de Fortnite. Este nuevo ciclo nos permitió utilizar Lumen, con el que creamos sombras más suaves y una iluminación global más realista. También añadimos algún detalle como una cascada e hicimos que la pista fuera visualmente más interesante.
La persistencia de Verse ofreció la solución al permitir el seguimiento de los datos entre sesiones de juego. Esta funcionalidad nos permitió controlar las estadísticas de los jugadores durante toda su vida y crear marcadores locales.
Nuestro objetivo era actualizar el mejor tiempo de vuelta de un jugador al completar una vuelta y también registrar sus puntos y victorias cada vez que terminase una carrera.
Actualizar los tiempos de vuelta fue sencillo, ya que podíamos esperar a que el evento LapCompletedEvent del dispositivo gestor de carreras los detectase cada vez que un jugador completaba una vuelta. Cuando comienza la carrera, ponemos en marcha un dispositivo cronómetro que funciona para cada jugador. La función WaitForPlayerToFinishLap espera a que un jugador complete una vuelta, calcula el tiempo de esta, actualiza la tabla de estadísticas del jugador y, acto seguido, reinicia el cronómetro para registrar la siguiente vuelta.
Para solucionarlo, usamos la función ArraySync. ArraySync llama a una función asíncrona en cada elemento de la matriz que se le pasa y espera a que se completen todas esas funciones. Pasando una matriz de jugadores y la función WaitForPlayerToFinishRace, pudimos llamar a la función en cada uno de ellos y esperar hasta que terminase para todos.
Esto nos permitió conceder puntos y una victoria a los jugadores según su posición al finalizar la carrera y, luego, actualizar su tabla de estadísticas en consonancia. Cuando acaba ArraySync, sabemos que todos los jugadores han terminado la carrera y podemos dar por finalizada la partida.
Como ya teníamos estadísticas persistentes de cada jugador, podíamos recuperarlas y mostrarlas mediante vallas publicitarias. En la zona de espera previa a la partida, añadimos dispositivos de valla publicitaria y de referencia de jugador para cada participante y para mostrar tanto sus estadísticas como los trajes que llevaban en ese momento. Ahora bien, seguía siendo un reto ordenar a estos jugadores en función de su rendimiento.
Para lograrlo, implementamos el algoritmo Ordenamiento por mezcla. Se trata de un algoritmo común de divide y vencerás que divide de manera recursiva una matriz en dos, ordena cada submatriz y las vuelve a mezclar. También añadimos la posibilidad de pasar al algoritmo una función de comparación, funciones que creamos para cada una de las diferentes estadísticas de los jugadores.
Cuando necesitábamos ordenar jugadores, podíamos recuperar las estadísticas de cada uno desde su tabla de estadísticas, añadirlas todas a una matriz y pasarlas junto con una función de comparación al algoritmo de ordenamiento por mezcla. Al elegir la función de comparación que pasábamos, podíamos obtener la matriz de jugadores ordenados por cualquier estadística. En la nueva plantilla hemos incluido tanto el algoritmo de ordenamiento por mezcla como un archivo de prueba, para que puedas probar el algoritmo y adaptarlo a tus propias funciones.
Una vez establecidos los mecanismos de ordenación, finalizamos nuestros marcadores. Durante la primera ronda del juego, los jugadores aparecen en una zona de espera previa a la partida con sus referencias de jugador y vallas publicitarias. Ordenamos estas referencias de jugadores por los puntos totales de cada uno de ellos y mostramos cada una de sus estadísticas en la valla publicitaria que ven delante. De este modo, los jugadores tienen la oportunidad de conocer a los rivales antes de la carrera y saber a quién deben vigilar si quieren ganar.
¡Sigue jugando y puede que acabes en lo más alto de la clasificación!

Para ello, creamos una segunda variable de weak map para el jugador llamada CircuitInfo, la cual contiene toda la información que necesitamos restablecer al final de la partida o cuando se vaya un jugador.
Usamos una segunda variable de weak map para dejar claro qué información debe restablecerse (información del circuito) y cuál persistir para siempre (estadísticas del jugador).
Para cada jugador, registramos la siguiente información en la variable CircuitInfo de weak map del jugador:
En la función OnBegin del dispositivo de Verse (que se ejecuta al principio de cada ronda), averiguamos en qué ronda estamos iterando a través de todos los jugadores activos y obteniendo el valor más alto de la última ronda completada a partir de sus datos de persistencia. Solo lo hacemos una vez por ronda y registramos la información de la misma en una variable de weak map de sesión para que todo el código de Verse del proyecto pueda acceder a dicho valor en cualquier momento sin necesidad de volver a calcularlo.
Cuando un jugador completa la carrera, lo que se detecta por la espera en el RaceCompletedEvent del dispositivo gestor de carreras, registramos su orden de llegada y la ronda actual en la variable CircuitInfo de weak map asociada con el jugador. Restablecemos esta información bajo dos condiciones:
En la plantilla actualizada, sustituimos el activador de pulsos por Sequencer para conseguir nuestra cinemática de apertura. Gracias a Sequencer, pudimos añadir distintas cámaras, elementos de visualización frontal (HUD) y una vista de alineación dinámica que se ajusta en función del número de jugadores activos.
De forma similar a cómo configuramos el activador de pulsos, conectamos dispositivos a la secuencia en momentos importantes, lo que nos permitió determinar cuándo mostrar la puntuación del siguiente jugador o cuándo cortar la introducción.

Estamos deseando actualizar esta plantilla a medida que se publiquen nuevas funciones que mejoren su diseño. Mientras tanto, puedes descargarte la plantilla, explorar sus componentes e integrar su funcionalidad en tus proyectos a través de la pestaña de plantillas en UEFN. ¡Estamos deseando ver cómo van tomando forma tus carreras, marcadores y demás!
La plantilla «Crea un juego de carreras en un circuito» se creó inicialmente para utilizar los recursos de circuito que acabábamos de añadir y construir una experiencia de carreras distinta con algunas funciones que mejoran la experiencia del usuario.
Con la última actualización, hemos adoptado un enfoque holístico y hemos sustituido y mejorado muchas funciones por todo el mapa. Echemos un vistazo a algunas de las actualizaciones que hemos realizado y que aprovechan las potentes prestaciones de UEFN.
Diseño visual
El modo Paisaje de UEFN nos permitió volver a las raíces de las carreras y crear un circuito todoterreno utilizando las nuevas herramientas de edición de paisajes. Las montañas del fondo de nuestra isla original, hechas con recursos de rocas, fueron reemplazadas por un diseño paisajístico más natural.El antiguo ciclo de día y noche del proyecto original fue sustituido por la iluminación más avanzada del capítulo 4 del Battle Royale de Fortnite. Este nuevo ciclo nos permitió utilizar Lumen, con el que creamos sombras más suaves y una iluminación global más realista. También añadimos algún detalle como una cascada e hicimos que la pista fuera visualmente más interesante.
Persistencia de Verse: un marcador mejor
En el mapa original, una torre que usaba el dispositivo Referencia de jugador mostraba en primer lugar al jugador y los puntos que tenía. Sin embargo, los datos no permanecían de una sesión a otra.La persistencia de Verse ofreció la solución al permitir el seguimiento de los datos entre sesiones de juego. Esta funcionalidad nos permitió controlar las estadísticas de los jugadores durante toda su vida y crear marcadores locales.
Seguimiento de las estadísticas de los jugadores
Desarrollamos una clase de tabla de estadísticas de jugador de persistencia que registra las victorias de un jugador a lo largo de su vida, junto con su mejor tiempo de vuelta y los puntos obtenidos por cada carrera finalizada. También usamos un weak map de persistencia llamado PlayerStatsMap para asignar jugadores a sus tablas de estadísticas, lo que permite que estas estadísticas persistan a lo largo de rondas y sesiones. Luego, creamos una clase de gestor de estadísticas para gestionar la inicialización, recuperación y actualización de estas estadísticas por jugador.Nuestro objetivo era actualizar el mejor tiempo de vuelta de un jugador al completar una vuelta y también registrar sus puntos y victorias cada vez que terminase una carrera.
Actualizar los tiempos de vuelta fue sencillo, ya que podíamos esperar a que el evento LapCompletedEvent del dispositivo gestor de carreras los detectase cada vez que un jugador completaba una vuelta. Cuando comienza la carrera, ponemos en marcha un dispositivo cronómetro que funciona para cada jugador. La función WaitForPlayerToFinishLap espera a que un jugador complete una vuelta, calcula el tiempo de esta, actualiza la tabla de estadísticas del jugador y, acto seguido, reinicia el cronómetro para registrar la siguiente vuelta.
Registro de victorias y puntos
Más compleja resultó la tarea de registrar victorias y puntos. Sabíamos que podíamos usar la función «WaitForPlayerToFinishRace» similar para esperar el «RaceCompletedEvent» del gestor de carreras y así saber cuándo terminaba cada jugador. Sin embargo, queríamos que la partida concluyera solo cuando hubieran terminado todos los jugadores, y el gestor de carreras no tenía una forma de controlar esto ni de finalizar la partida al llegar estos a la meta.Para solucionarlo, usamos la función ArraySync. ArraySync llama a una función asíncrona en cada elemento de la matriz que se le pasa y espera a que se completen todas esas funciones. Pasando una matriz de jugadores y la función WaitForPlayerToFinishRace, pudimos llamar a la función en cada uno de ellos y esperar hasta que terminase para todos.
Esto nos permitió conceder puntos y una victoria a los jugadores según su posición al finalizar la carrera y, luego, actualizar su tabla de estadísticas en consonancia. Cuando acaba ArraySync, sabemos que todos los jugadores han terminado la carrera y podemos dar por finalizada la partida.
Visualización de resultados
Una cosa era registrar las estadísticas, y otra mostrárselas a los jugadores, cosa que también necesitábamos. Queríamos crear marcadores en niveles que fueran visibles y ordenar a los jugadores por sus puntos totales para destacar a los mejores.Como ya teníamos estadísticas persistentes de cada jugador, podíamos recuperarlas y mostrarlas mediante vallas publicitarias. En la zona de espera previa a la partida, añadimos dispositivos de valla publicitaria y de referencia de jugador para cada participante y para mostrar tanto sus estadísticas como los trajes que llevaban en ese momento. Ahora bien, seguía siendo un reto ordenar a estos jugadores en función de su rendimiento.
Para lograrlo, implementamos el algoritmo Ordenamiento por mezcla. Se trata de un algoritmo común de divide y vencerás que divide de manera recursiva una matriz en dos, ordena cada submatriz y las vuelve a mezclar. También añadimos la posibilidad de pasar al algoritmo una función de comparación, funciones que creamos para cada una de las diferentes estadísticas de los jugadores.
Cuando necesitábamos ordenar jugadores, podíamos recuperar las estadísticas de cada uno desde su tabla de estadísticas, añadirlas todas a una matriz y pasarlas junto con una función de comparación al algoritmo de ordenamiento por mezcla. Al elegir la función de comparación que pasábamos, podíamos obtener la matriz de jugadores ordenados por cualquier estadística. En la nueva plantilla hemos incluido tanto el algoritmo de ordenamiento por mezcla como un archivo de prueba, para que puedas probar el algoritmo y adaptarlo a tus propias funciones.
Una vez establecidos los mecanismos de ordenación, finalizamos nuestros marcadores. Durante la primera ronda del juego, los jugadores aparecen en una zona de espera previa a la partida con sus referencias de jugador y vallas publicitarias. Ordenamos estas referencias de jugadores por los puntos totales de cada uno de ellos y mostramos cada una de sus estadísticas en la valla publicitaria que ven delante. De este modo, los jugadores tienen la oportunidad de conocer a los rivales antes de la carrera y saber a quién deben vigilar si quieren ganar.
¡Sigue jugando y puede que acabes en lo más alto de la clasificación!

Orden de los corredores en la línea de salida
Al comienzo de cada partida, la versión del modo Creativo de Fortnite ordena a los jugadores de forma aleatoria. Para motivar a los jugadores a escalar puestos en la clasificación, establecemos el orden de salida según la posición en que terminaran en la ronda anterior.Seguimiento de la información de las rondas
A diferencia de los datos de persistencia que empleamos para el marcador, necesitábamos que el orden de los corredores y la información del circuito persistieran en todas las rondas, pero no en todas las sesiones de juego. Una variable de weak map de sesión en Verse restablece sus datos cada ronda, por lo que tenemos que almacenar esta información para cada jugador y restablecerla una vez finalizada la partida.Para ello, creamos una segunda variable de weak map para el jugador llamada CircuitInfo, la cual contiene toda la información que necesitamos restablecer al final de la partida o cuando se vaya un jugador.
Usamos una segunda variable de weak map para dejar claro qué información debe restablecerse (información del circuito) y cuál persistir para siempre (estadísticas del jugador).
Para cada jugador, registramos la siguiente información en la variable CircuitInfo de weak map del jugador:
- Orden de llegada
- Última ronda completada
Lógica específica de cada ronda
Necesitamos saber en qué ronda estamos para aplicar la lógica específica de cada ronda (como ordenar a los jugadores por su última posición de llegada si no es la primera ronda) y para saber cuándo restablecer los datos de los jugadores. Actualmente, no existe una API para obtener la ronda actual, por lo que tenemos que registrarla en los datos de persistencia de cada jugador.En la función OnBegin del dispositivo de Verse (que se ejecuta al principio de cada ronda), averiguamos en qué ronda estamos iterando a través de todos los jugadores activos y obteniendo el valor más alto de la última ronda completada a partir de sus datos de persistencia. Solo lo hacemos una vez por ronda y registramos la información de la misma en una variable de weak map de sesión para que todo el código de Verse del proyecto pueda acceder a dicho valor en cualquier momento sin necesidad de volver a calcularlo.
Cuando un jugador completa la carrera, lo que se detecta por la espera en el RaceCompletedEvent del dispositivo gestor de carreras, registramos su orden de llegada y la ronda actual en la variable CircuitInfo de weak map asociada con el jugador. Restablecemos esta información bajo dos condiciones:
- El jugador se marcha durante la partida. Nos suscribimos al PlayerRemovedEvent del espacio de juego para saber cuándo se van y llamamos a nuestra función ResetCircuitInfo.
- Al inicio de cada ronda, calculamos la última ronda completada. En caso de tratarse de la última ronda, invocamos ResetCircuitInfo para actualizar los datos del jugador. Para saber cuántas rondas hay, tenemos una propiedad editable en el dispositivo de Verse que se establece en el número total de rondas para un juego. Esto es en el creador de islas para asegurarnos de que coincida con los ajustes de la ronda.
Cuándo utilizar weak maps de sesión o weak maps de jugador
Esta nueva Speedway Race es un gran ejemplo para mostrar las diferencias y el razonamiento de usar la variable de weak map de sesión y la variable de weak map de jugador en tu código:- Las variables de weak map de sesión son útiles para singletons (instancias únicas) y para almacenar datos de la ronda actual que no quieras volver a calcular cada vez.
- Las variables del weak map del jugador están diseñadas para información que necesite persistir a lo largo de varias rondas y sesiones de juego, pero que deba estar asociada a jugadores individuales.
Activador de pulsos para secuencia de apertura
En la versión original de la plantilla Speedway Race, empleamos un dispositivo llamado activador de pulsos para orquestar la parte «preparados, listos, ya» de la carrera. El activador de pulsos reproducía una secuencia de eventos durante un periodo de tiempo determinado activando disparadores para mostrar texto y habilitar las luces en la línea de salida.En la plantilla actualizada, sustituimos el activador de pulsos por Sequencer para conseguir nuestra cinemática de apertura. Gracias a Sequencer, pudimos añadir distintas cámaras, elementos de visualización frontal (HUD) y una vista de alineación dinámica que se ajusta en función del número de jugadores activos.
De forma similar a cómo configuramos el activador de pulsos, conectamos dispositivos a la secuencia en momentos importantes, lo que nos permitió determinar cuándo mostrar la puntuación del siguiente jugador o cuándo cortar la introducción.

¿Y ahora qué?
A medida que UEFN siga avanzando, esperamos que esta plantilla también evolucione. Nuestro objetivo es que puedas seguir creando contenidos más sofisticados con UEFN, aprovechando las últimas funciones para crear islas interesantes y divertidas.Estamos deseando actualizar esta plantilla a medida que se publiquen nuevas funciones que mejoren su diseño. Mientras tanto, puedes descargarte la plantilla, explorar sus componentes e integrar su funcionalidad en tus proyectos a través de la pestaña de plantillas en UEFN. ¡Estamos deseando ver cómo van tomando forma tus carreras, marcadores y demás!