Um loop de jogo é um código que é executado repetidamente (em loops) para responder à entrada (geralmente o jogador interagindo com o controle ou o mouse), atualizar o estado do jogo e fornecer saída que mostra ao jogador que ele afetou o estado do jogo, como quando pressiona um botão e acende uma luz. O loop geralmente termina quando o jogo atinge um estado de conclusão, como o jogador atingindo uma meta, ou um estado de falha, como ficar sem tempo antes de atingir a meta.
Ao concluir esta etapa no tutorial Prova de tempo: Em busca da pizza, você aprenderá a criar o loop do jogo e definir os estados de conclusão e falha do jogo.
A seguir está o pseudocódigo do loop do jogo em Prova de tempo: Em busca da pizza:
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
Esse loop deve terminar quando o cronômetro de contagem regressiva terminar ou se houver um erro inesperado no jogo.
Como criar o loop principal do jogo
Siga estas etapas para atualizar o arquivo game_coordinator_device.verse:
Crie um novo método chamado
PickupDeliveryLoop()que tenha os especificadoresprivateesuspends. Mova o loop que você criou anteriormente emOnBegin()para esse novo método.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):Determine o número máximo de níveis de retirada a partir do comprimento das tags array e aumente o
PickupLeveltodas as vezes que o jogador completar uma zona de retirada, desde que o nível de retirada não seja maior que o número máximo de níveis de retirada.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 var PickupLevel : int = 0A zona de entrega deve ser ativada após o jogador concluir sua primeira retirada, mas o jogador ainda deve poder pegar itens se quiser, antes de ir para a zona de entrega. Para fazer isso, os código das zonas de retirada e entrega precisam ocorrer ao mesmo tempo. Este exemplo usa a expressão de simultaneidade race porque:
O bloco de entrega deve cancelar o ciclo da zona de retirada quando o jogador terminar uma entrega.
O loop da zona de retirada deve cancelar o bloqueio de entrega se houver um problema com o circuito de retirada.
Você também precisa de uma pequena modificação na desativação das zonas. Quando o loop ou o bloco de entrega são cancelados,
DeactivateZone()não deveria ser chamado se o script estivesse aguardando a conclusão da zona.Como a linha de desativação da zona nunca seria executada, a zona permaneceria ativa, criando um bug.
Para corrigir isso, você pode usar a expressão defer. A expressão
deferatrasa a execução das expressões que ele contém até que o escopo no qual odeferaparece seja encerrado. Uma expressãodeferé executada quando o controle do programa é transferido para fora do escopo, incluindo deixar um escopo normalmente (fim de uma função), saídas antecipadas (como retorno ou interrupção) ou devido a qualquer expressão assíncrona ou tarefa simultânea cancelada (comorace). É como enfileirar operações que serão executadas logo no final, não importa o que aconteça. Embrulhe cada chamadaDeactivateZoneem uma expressãodefere mova-a antes da respectivaZoneCompletedEvent.Await().VersePickupDeliveryLoop<private>()<suspends>; : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 var PickupLevel : int = 0 race: loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone()O exemplo anterior ativa a zona de entrega ao mesmo tempo em que a zona de retirada é ativada, mas a ativação da zona de entrega deve esperar até que a primeira retirada seja concluída. Para fazer isso, adicione um evento e faça com que a zona de entrega aguarde o evento antes de ativá-lo.
VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 FirstPickupZoneCompletedEvent := event(){}Repita essa expressão de corrida de zona de retirada/zona de entrega até que o jogo termine, para que o jogador possa continuar retirando e entregando itens.
VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 FirstPickupZoneCompletedEvent := event(){}Seu arquivo game_coordinator_device.verse deve ficar assim:
Verseusing { /Verse.org/Simulation } using { /Fortnite.com/Devices } using { /Fortnite.com/Vehicles } using { /Fortnite.com/Characters } using { /Fortnite.com/Playspaces } using { /Verse.org/Random } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath } using { /UnrealEngine.com/Temporary/Curves } using { /Verse.org/Simulation/Tags }
Salve seus arquivos do Verse, compile seu código e teste seu nível.
Quando você testa seu nível, um dos dispositivos Gerador de Itens é ativado no início do jogo e depois que o jogador retira um item. Depois que o jogador retirar seu primeiro item, esse dispositivo Gerador de Itens será desativado, e um dispositivo Área de Captura será ativado. Isso continuará até você terminar o jogo manualmente.
Como definir estados de conclusão e falha para loop de jogo
Agora que você criou o loop principal do jogo, defina o estado de conclusão e falha desse loop. Esse jogo deve terminar quando:
Uma contagem regressiva terminar, ou
Há um problema com o loop do jogo.
Siga estas etapas para configurar os estados de conclusão e falha do jogo:
Crie uma instância da classe countdown_timer em
game_coordinator_deviceque tenha o especificadorprivate.game_coordinator_device<public> := class(creative_device): @editable EndGame<public> : end_game_device = end_game_device{} var CountdownTimer<private> : countdown_timer = countdown_timer{}Versegame_coordinator_device<public> := class(creative_device): @editable EndGame<public> : end_game_device = end_game_device{} var CountdownTimer<private> : countdown_timer = countdown_timer{}Como o construtor para
countdown_timerrequer uma referência de jogador, adicione uma variável de jogador opcional para armazenar uma referência ao jogador nesse jogo para um jogador e crie uma função chamadaFindPlayer()para obter a referência do jogador. ChameFindPlayer()emOnBegin()antes de configurar as zonas.Versegame_coordinator_device<public> := class(creative_device): @editable EndGame<public> : end_game_device = end_game_device{} var CountdownTimer<private> : countdown_timer = countdown_timer{} var MaybePlayer<private> : ?player = false OnBegin<override>()<suspends> : void = FindPlayer() SetupZones() FindPlayer<private>() : void = # Since this is a single player experience, the first player (at index 0) # should be the only one available. if (FirstPlayer := GetPlayspace().GetPlayers()[0]): set MaybePlayer = option{FirstPlayer} Logger.Print("Player found") else: # Log an error if we can't find a player. # This shouldn't happen because at least one player is always present. Logger.Print("Can't find valid player", ?Level := log_level.Error)Crie uma função chamada
HandleCountdownEnd()que espera o fim do cronômetro de contagem regressiva e ativa o dispositivo Fim de Jogo.VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)Crie uma função chamada
StartGame()e chame-a depois deSetupZones()emOnBegin(). Essa função deve:Inicializar o temporizador de contagem regressiva.
Versegame_coordinator_device<public> := class(creative_device): # How long the countdown timer will start counting down from. @editable InitialCountdownTime<public> : float = 30.0 @editable EndGame<public> : end_game_device = end_game_device{} OnBegin<override>()<suspends> : void = FindPlayer() SetupZones() StartGame() StartGame<private>()<suspends> : void = Logger.Print("Trying to start the game...") <# We construct a new countdown_timer that'll countdown from InitialCountdownTime once started. The countdown_timer requires a player to show their UI to. We should have a valid player by now. #> if (ValidPlayer := MaybePlayer?): Logger.Print("Valid player, starting game...") set CountdownTimer = MakeCountdownTimer(InitialCountdownTime, ValidPlayer) CountdownTimer.StartCountdown() else: Logger.Print("Can't find valid player. Aborting game start", ?Level := log_level.Error)Use a expressão
racepara chamarHandleCountdownEnd(ValidPlayer)ePickupDeliveryLoop(), de forma que:Quando a contagem regressiva terminar, o loop do jogo seja interrompido, ou
Se o loop do jogo parar, a contagem regressiva seja cancelada.
VerseStartGame<private>()<suspends> : void = Logger.Print("Trying to start the game...") <# We construct a new countdown_timer that'll countdown from InitialCountdownTime once started. The countdown_timer requires a player to show their UI to. We should have a valid player by now. #> if (ValidPlayer := MaybePlayer?): Logger.Print("Valid player, starting game...") set CountdownTimer = MakeCountdownTimer(InitialCountdownTime, ValidPlayer) CountdownTimer.StartCountdown() # We wait for the countdown to end. # At the same time, we also run the Pickup and Delivery game loop that constitutes the core gameplay. race: HandleCountdownEnd(ValidPlayer) PickupDeliveryLoop() else: Logger.Print("Can't find valid player. Aborting game start", ?Level := log_level.Error)
Seu arquivo game_coordinate_device.verse agora deve ter a seguinte aparência:
Verseusing { /Verse.org/Simulation } using { /Fortnite.com/Devices } using { /Fortnite.com/Vehicles } using { /Fortnite.com/Characters } using { /Fortnite.com/Playspaces } using { /Verse.org/Random } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath } using { /UnrealEngine.com/Temporary/Curves } using { /Verse.org/Simulation/Tags }Salve seus arquivos do Verse, compile seu código e teste seu nível.
Quando você testar seu nível, o jogo funcionará da mesma forma que na seção anterior, mas agora há um cronômetro que encerrará o jogo quando a contagem regressiva terminar, ou há um problema no loop do jogo.