Voltar
Como melhoramos o modelo Speedway Race no Fortnite com persistência em Verse e mais
Equipe Fortnite
A introdução de persistência em Verse permitiu o aprimoramento da ilha modelo "Design a Speedway Race" (Projete uma pista de corrida) do Modo Criativo do Fortnite para o UEFN. Desde atualizações como a adição de cinematografia e paisagismo até a mudança dos gatilhos para Verse, estamos entusiasmados em compartilhar a jornada de desenvolvimento deste modelo com vocês. Boas-vindas ao Speedway Race com persistência em Verse!
O modelo "Design a Speedway Race" (Projete uma pista de corrida) foi inicialmente criado para usar os recém-criados ativos de pista de corrida e criar uma experiência de corrida única com funcionalidades que melhoram a qualidade de vida.
Com a última atualização, adotamos uma abordagem holística, substituímos e aprimoramos muitas funcionalidades pelo mapa. Vamos dar uma olhada em algumas das mudanças feitas que aproveitam as funcionalidades avançadas do UEFN.
O ciclo legado de dia/noite do projeto original foi substituído por uma iluminação mais avançada do Capítulo 4 do Fortnite Battle Royale. Este novo ciclo nos permitiu usar o Lumen, criando sombras mais suaves e iluminação global realista. Também adicionamos funcionalidades como uma cachoeira, e tornamos a pista em si visualmente mais interessante.
A persistência em Verse forneceu a solução permitindo o rastreamento dos dados entre as sessões de jogo. Esta funcionalidade nos permitiu monitorar os atributos acumulados do jogador e criar tabelas de liderança locais.
Nosso objetivo era atualizar o melhor tempo de um jogador ao finalizar uma volta e também registrar seus pontos e vitórias sempre que finalizasse uma corrida.
Atualizar tempos de volta foi algo bem objetivo, já que poderíamos aguardar o LapCompletedEvent do dispositivo Gerenciador de Corrida detectar sempre que um jogador finalizasse uma corrida. Quando uma corrida começa, iniciamos um dispositivo Temporizador que é executado para cada jogador. A função WaitForPlayerToFinishLap aguarda o jogador finalizar uma volta, calcula o tempo, atualiza a tabela de atributos e reinicia o temporizador para registrar a próxima volta.
Como solução usamos a função ArraySync. ArraySync chama uma função assíncrona para cada elemento na matriz passada e aguarda a conclusão de todas essas funções. Ao passar uma matriz de jogadores e a função WaitForPlayerToFinishRace, pudemos chamar a função para cada um deles e esperar até que ela finalizasse para todos.
Assim, pudemos conceder pontos e uma vitória aos jogadores com base na sua colocação quando terminaram a corrida, e então atualizar sua tabela de atributos adequadamente. Quando a ArraySync finaliza, sabemos que todos os jogadores terminaram a corrida e podemos encerrar o jogo.
Como já tínhamos dados persistentes para cada jogador, poderíamos recuperá-los e exibi-los usando cartazes. Na área de espera pré-jogo, adicionamos os dispositivos Cartaz e Referência de Jogador para cada jogador, exibindo seus atributos e trajes usados na ocasião. Entretanto, classificar esses jogadores com base no desempenho ainda era um desafio.
Para isso, implementamos o algoritmo Merge Sort. Merge Sort é um algoritmo de classificação comum do tipo "dividir e conquistar" que divide recursivamente uma matriz em duas, classifica cada submatriz e depois as mescla novamente. Também adicionamos a habilidade de passar uma função de comparação para o algoritmo, e criamos funções de comparação para cada um dos diferentes atributos de jogador.
Sempre que precisávamos classificar jogadores, podíamos recuperar os atributos de cada um a partir da tabela, adicionar todos a uma matriz e passá-la, junto da função de comparação, para o algoritmo Merge Sort. Ao escolher qual função de comparação passamos, poderíamos recuperar a matriz de jogadores ordenados por qualquer atributo. Incluímos tanto o Merge Sort e um arquivo de teste no modelo novo para você poder testar o algoritmo e ajustá-lo às suas próprias funções!
Com os mecanismos de classificação prontos, finalizamos as tabelas de liderança. Durante a primeira rodada do jogo, os jogadores aparecem em uma área de espera pré-jogo com suas referências de jogador e cartazes. Classificamos essas referências de jogador pelos pontos acumulados de cada jogador e exibimos cada um dos seus atributos no cartaz em frente deles. Isso permite que os jogadores visualizem seus adversários antes da corrida e saber em quem ficar de olho para vencer.
Continue jogando e talvez você alcance o topo do placar!
Para isso, criamos uma segunda variável de weakmap de jogador chamada CircuitInfo. A variável CircuitInfo armazena todas as informações que precisamos restaurar ao final do jogo ou quando um jogador sai.
Usamos uma segunda variável de weakmap para clarificar quais informações devem ser restauradas (as informações do circuito) e quais devem persistir para sempre (os atributos do jogador).
Para cada jogador, registramos as seguintes informações na variável de weakmap de jogador de CircuitInfo:
Na função OnBegin do dispositivo Verse (que roda no início de cada rodada), descobrimos em qual rodada estamos iterando por todos os jogadores ativos e obtendo o valor mais alto da rodada concluída a partir dos dados persistentes. Só fazemos isso uma vez por rodada e registramos as informações da rodada em uma variável de weakmap de sessão para que todo o código Verse no projeto possa acessar esse valor a qualquer momento sem precisar calculá-lo novamente.
Quando um jogador conclui a corrida, momento detectado ao aguardar o RaceCompletedEvent do dispositivo Gerenciador de Corrida, registramos sua ordem de chegada e a rodada atual na variável de weakmap CircuitInfo associada ao jogador. Restauramos essas informações mediante duas condições:
No modelo atualizado, substituímos o Gatilho de Pulso pelo Sequencer para realizar nossa cinematografia de abertura. Com o Sequencer, pudemos adicionar diferentes câmeras, elementos de HUD e uma visualização dinâmica que se ajusta com base na quantidade de jogadores ativos.
Semelhantemente à forma como configuramos o Gatilho de Pulso, conectamos dispositivos à sequência em momentos importantes, permitindo-nos determinar quando exibir a pontuação do próximo jogador ou quando cortar a introdução.
Estamos ansiosos para atualizar este modelo conforme novas funcionalidades que aprimoram seu projeto são lançadas. Enquanto isso, você pode baixar o modelo, explorar seus componentes e integrar sua funcionalidade aos seus projetos por meio da aba de modelos no UEFN. Estamos ansiosos para ver suas corridas, tabelas de líderes e tudo mais ganharem forma!
O modelo "Design a Speedway Race" (Projete uma pista de corrida) foi inicialmente criado para usar os recém-criados ativos de pista de corrida e criar uma experiência de corrida única com funcionalidades que melhoram a qualidade de vida.
Com a última atualização, adotamos uma abordagem holística, substituímos e aprimoramos muitas funcionalidades pelo mapa. Vamos dar uma olhada em algumas das mudanças feitas que aproveitam as funcionalidades avançadas do UEFN.
Design visual
O Modo Paisagem do UEFN nos permitiu voltar às origens das corridas e criar uma pista off-road usando as novas ferramentas de edição de paisagens. As montanhas criadas com ativos de rochas no plano de fundo da nossa ilha original foram substituídas por um design de paisagem mais natural.O ciclo legado de dia/noite do projeto original foi substituído por uma iluminação mais avançada do Capítulo 4 do Fortnite Battle Royale. Este novo ciclo nos permitiu usar o Lumen, criando sombras mais suaves e iluminação global realista. Também adicionamos funcionalidades como uma cachoeira, e tornamos a pista em si visualmente mais interessante.
Persistência em Verse: uma Tabela de Liderança melhor
No mapa original, uma torre que usasse o dispositivo Referência de Jogador exibia o jogador em primeiro lugar e sua pontuação. Entretanto, os dados não persistiam entre sessões.A persistência em Verse forneceu a solução permitindo o rastreamento dos dados entre as sessões de jogo. Esta funcionalidade nos permitiu monitorar os atributos acumulados do jogador e criar tabelas de liderança locais.
Rastreamento de atributos do jogador
Desenvolvemos uma classe de tabela de atributos de jogador persistente que monitora as vitórias, o melhor tempo de volta e os pontos ganhos por corrida finalizada de um jogador. Também usamos um weakmap persistente chamado PlayerStatsMap para mapear as tabelas de atributos dos jogadores, permitindo que persistissem entre as rodadas e as sessões. Posteriormente, criamos uma classe de gerenciamento de atributos para lidar com a inicialização, a recuperação e a atualização destes atributos por jogador.Nosso objetivo era atualizar o melhor tempo de um jogador ao finalizar uma volta e também registrar seus pontos e vitórias sempre que finalizasse uma corrida.
Atualizar tempos de volta foi algo bem objetivo, já que poderíamos aguardar o LapCompletedEvent do dispositivo Gerenciador de Corrida detectar sempre que um jogador finalizasse uma corrida. Quando uma corrida começa, iniciamos um dispositivo Temporizador que é executado para cada jogador. A função WaitForPlayerToFinishLap aguarda o jogador finalizar uma volta, calcula o tempo, atualiza a tabela de atributos e reinicia o temporizador para registrar a próxima volta.
Registro de vitórias e pontos
Registrar vitórias e pontos se mostrou mais complexo. Sabíamos que poderíamos usar a função semelhante WaitForPlayerToFinishRace para aguardar o RaceCompletedEvent do gerenciador de corrida e saber quando um jogador finalizasse. Porém, queríamos que o jogo terminasse somente quando todos os jogadores finalizassem, e o gerenciador de corrida não tinha uma forma de rastrear isso nem de encerrar o jogo.Como solução usamos a função ArraySync. ArraySync chama uma função assíncrona para cada elemento na matriz passada e aguarda a conclusão de todas essas funções. Ao passar uma matriz de jogadores e a função WaitForPlayerToFinishRace, pudemos chamar a função para cada um deles e esperar até que ela finalizasse para todos.
Assim, pudemos conceder pontos e uma vitória aos jogadores com base na sua colocação quando terminaram a corrida, e então atualizar sua tabela de atributos adequadamente. Quando a ArraySync finaliza, sabemos que todos os jogadores terminaram a corrida e podemos encerrar o jogo.
Exibição de resultados
Não era suficiente registrar atributos, também precisávamos uma forma de exibir esses atributos aos jogadores. Queríamos criar tabelas de liderança internas que fossem visíveis e ordenassem os jogadores pelos pontos acumulados para destacar os melhores.Como já tínhamos dados persistentes para cada jogador, poderíamos recuperá-los e exibi-los usando cartazes. Na área de espera pré-jogo, adicionamos os dispositivos Cartaz e Referência de Jogador para cada jogador, exibindo seus atributos e trajes usados na ocasião. Entretanto, classificar esses jogadores com base no desempenho ainda era um desafio.
Para isso, implementamos o algoritmo Merge Sort. Merge Sort é um algoritmo de classificação comum do tipo "dividir e conquistar" que divide recursivamente uma matriz em duas, classifica cada submatriz e depois as mescla novamente. Também adicionamos a habilidade de passar uma função de comparação para o algoritmo, e criamos funções de comparação para cada um dos diferentes atributos de jogador.
Sempre que precisávamos classificar jogadores, podíamos recuperar os atributos de cada um a partir da tabela, adicionar todos a uma matriz e passá-la, junto da função de comparação, para o algoritmo Merge Sort. Ao escolher qual função de comparação passamos, poderíamos recuperar a matriz de jogadores ordenados por qualquer atributo. Incluímos tanto o Merge Sort e um arquivo de teste no modelo novo para você poder testar o algoritmo e ajustá-lo às suas próprias funções!
Com os mecanismos de classificação prontos, finalizamos as tabelas de liderança. Durante a primeira rodada do jogo, os jogadores aparecem em uma área de espera pré-jogo com suas referências de jogador e cartazes. Classificamos essas referências de jogador pelos pontos acumulados de cada jogador e exibimos cada um dos seus atributos no cartaz em frente deles. Isso permite que os jogadores visualizem seus adversários antes da corrida e saber em quem ficar de olho para vencer.
Continue jogando e talvez você alcance o topo do placar!
Ordem da corrida na linha de partida
A versão do Modo Criativo do Fortnite do mapa coloca os jogadores em uma ordem aleatória no início de cada jogo. Para motivar os jogadores a alcançar posições mais altas na tabela de liderança, estabelecemos a linha de partida com base na sua posição de chegada na rodada anterior.Rastreamento de informações da rodada
Diferentemente dos dados persistentes que usamos para a tabela de liderança, precisamos que a ordem de corredores e as informações do circuito persistam entre todas as rodadas, mas não entre todas as sessões do jogo. Uma variável de weakmap de sessão em Verse restaura seus dados a cada rodada, então temos que armazenar essas informações para cada jogador e restaurá-las depois que o jogo termina.Para isso, criamos uma segunda variável de weakmap de jogador chamada CircuitInfo. A variável CircuitInfo armazena todas as informações que precisamos restaurar ao final do jogo ou quando um jogador sai.
Usamos uma segunda variável de weakmap para clarificar quais informações devem ser restauradas (as informações do circuito) e quais devem persistir para sempre (os atributos do jogador).
Para cada jogador, registramos as seguintes informações na variável de weakmap de jogador de CircuitInfo:
- A ordem de chegada
- A última rodada concluída
A lógica específica da rodada
Precisamos saber em qual rodada estamos para aplicar a lógica específica (como ordenar jogadores de acordo com a última posição de chegada, se não for a primeira rodada) e saber quando restaurar os dados do jogador. No momento, não há uma API para obter a rodada atual, motivo pelo qual precisamos registrá-la nos dados persistentes para cada jogador.Na função OnBegin do dispositivo Verse (que roda no início de cada rodada), descobrimos em qual rodada estamos iterando por todos os jogadores ativos e obtendo o valor mais alto da rodada concluída a partir dos dados persistentes. Só fazemos isso uma vez por rodada e registramos as informações da rodada em uma variável de weakmap de sessão para que todo o código Verse no projeto possa acessar esse valor a qualquer momento sem precisar calculá-lo novamente.
Quando um jogador conclui a corrida, momento detectado ao aguardar o RaceCompletedEvent do dispositivo Gerenciador de Corrida, registramos sua ordem de chegada e a rodada atual na variável de weakmap CircuitInfo associada ao jogador. Restauramos essas informações mediante duas condições:
- O jogador sai durante o jogo. Inscrevemos o PlayerRemovedEvent do espaço de jogo para saber quando o jogador sai e chamamos a função ResetCircuitInfo.
- No começo de cada rodada, calculamos a última rodada concluída. Se for a última rodada, chamamos ResetCircuitInfo para atualizar os dados do jogador. Sabemos quantas rodadas há colocando uma propriedade editável no dispositivo Verse definida para o número total de rodadas para um jogo. Isso está no criador de ilhas para garantir que a informação corresponda às configurações da rodada.
Quando usar weakmaps de sessão e de jogador
Este novo Speedway Race é um ótimo exemplo para mostrar as diferenças e motivos para usar a variável de weakmap de sessão e de jogador no seu código:- As variáveis de weakmap de sessão são úteis para singletons e para armazenar dados para a rodada atual que você não quer recalcular sempre.
- As variáveis de weakmap de jogador destinam-se a informações que precisam persistir por várias rodadas e sessões de jogo, mas precisam estar associadas a jogadores individuais.
Gatilho de Pulso para a sequência de abertura
Na versão original do modelo Speedway Race, utilizamos um dispositivo chamado Gatilho de Pulso para orquestrar a parte "preparar, apontar, já" da corrida. O Gatilho de Pulso executava uma sequência de eventos ao longo de um período de tempo ativando gatilhos para exibir texto e ativar luzes na linha de partida.No modelo atualizado, substituímos o Gatilho de Pulso pelo Sequencer para realizar nossa cinematografia de abertura. Com o Sequencer, pudemos adicionar diferentes câmeras, elementos de HUD e uma visualização dinâmica que se ajusta com base na quantidade de jogadores ativos.
Semelhantemente à forma como configuramos o Gatilho de Pulso, conectamos dispositivos à sequência em momentos importantes, permitindo-nos determinar quando exibir a pontuação do próximo jogador ou quando cortar a introdução.
O que vem por aí?
Conforme o UEFN continua avançando, esperamos também aprimorar este modelo. Nosso objetivo é dar a vocês autonomia para continuar a criar conteúdos mais sofisticados com o UEFN, aproveitando as funcionalidades mais recentes para criar ilhas divertidas e envolventes.Estamos ansiosos para atualizar este modelo conforme novas funcionalidades que aprimoram seu projeto são lançadas. Enquanto isso, você pode baixar o modelo, explorar seus componentes e integrar sua funcionalidade aos seus projetos por meio da aba de modelos no UEFN. Estamos ansiosos para ver suas corridas, tabelas de líderes e tudo mais ganharem forma!