Un loop di gioco è un codice eseguito ripetutamente (loop) per rispondere a un input (di solito il giocatore che interagisce con il controller o il mouse), aggiornare lo stato del gioco e fornire un output che mostri al giocatore che ha influenzato lo stato del gioco, ad esempio quando si accende una luce premendo un pulsante. Il loop di solito termina quando il gioco raggiunge uno stato di completamento, come ad esempio il raggiungimento di un obiettivo da parte del giocatore o uno stato di errore come l'esaurimento del tempo prima di raggiungere l'obiettivo.
Completando questo passaggio del tutorial Prova a tempo: A caccia di pizza, imparerai a creare il loop di gioco e a definire gli stati di completamento e di errore del gioco.
Di seguito è riportato lo pseudocodice del loop di gioco in Prova a tempo: A caccia di pizza:
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
Questo loop deve terminare quando il timer del conto alla rovescia termina o se si verifica un errore imprevisto nel gioco.
Creazione del loop di gioco di base
Per aggiornare il file game_coordinator_device.verse, segui questi passaggi:
Crea un nuovo metodo denominato
PickupDeliveryLoop()che abbia gli specificatoriprivateesuspends. Sposta il loop creato in precedenza inOnBegin()in questo nuovo metodo.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):Determina il numero massimo di livelli di raccolta dalla lunghezza dell'array dei tag e aumenta il
PickupLevelogni volta che il giocatore completa una zona di raccolta, purché il livello di raccolta non sia superiore al numero massimo di livelli di raccolta.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 = 0La zona di consegna deve attivarsi dopo che il giocatore ha completato il suo primo ritiro, ma il giocatore deve comunque essere in grado di raccogliere gli oggetti se lo desidera prima di recarsi nella zona di consegna. A tal fine, il codice della zona di raccolta e quello della zona di consegna devono coincidere. Questo esempio utilizza l'espressione di concorrenza race perché:
Il blocco di consegna deve annullare il loop della zona di raccolta quando il giocatore termina una consegna.
Il loop della zona di raccolta deve annullare il blocco di consegna se si verifica un problema con il loop di raccolta.
Devi anche eseguire una leggera modifica alla disattivazione della zona. Quando il loop o il blocco di consegna viene annullato,
DeactivateZone()non deve essere chiamata se lo script stava aspettando il completamento della zona.Poiché la riga di disattivazione della zona non viene mai eseguita, la zona rimane attiva, creando un bug.
Per risolvere questo problema, puoi utilizzare l'espressione defer. Un
deferritarda l'esecuzione delle espressioni che contiene fino a quando l'ambito in cui appare ildefernon termina. Undeferviene eseguito una volta che il controllo del programma viene trasferito fuori dall'ambito, inclusa l'uscita normale da un ambito (fine di una funzione), le uscite anticipate (come return o break) o a causa di qualsiasi attività concomitante annullata/espressione asincrona (come unarace). È come mettere in coda operazioni che verranno eseguite alla fine, qualunque cosa accada. Racchiudi ogni chiamataDeactivateZonein undefere spostala prima del rispettivoZoneCompletedEvent.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()L'esempio precedente attiva la zona di consegna nello stesso momento in cui viene attivata la zona di raccolta, ma l'attivazione della zona di consegna deve attendere il completamento della prima raccolta. A tal fine, aggiungi un evento e fai in modo che la zona di consegna attenda l'evento prima di attivarsi.
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(){}Esegui il loop di questa espressione race della zona di raccolta/zona di consegna fino al termine del gioco in modo che il giocatore possa continuare a raccogliere e consegnare oggetti.
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(){}Il file game_coordinator_device.verse dovrebbe ora essere simile al seguente:
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 }
Salva i file di Verse, compila il codice ed esegui il playtest del livello.
Durante il playtest del livello, uno dei dispositivi Generatore oggetti si attiverà all'inizio del gioco e dopo che il giocatore raccoglie un oggetto. Dopo che il giocatore ha raccolto il suo primo oggetto, il dispositivo Generatore oggetti si disattiva e si attiva un dispositivo Area di cattura. Ciò continua fino a quando non termini manualmente il gioco.
Definizione degli stati di completamento ed errore per il loop di gioco
Ora che hai creato la base del loop di gioco, devi definire lo stato di completamento e di errore del loop di gioco. Questo gioco deve terminare quando:
Termina un conto alla rovescia oppure
C'è un problema con il loop di gioco.
Per impostare gli stati di completamento ed errore del gioco, attieniti ai passaggi seguenti:
Crea un'istanza della classe countdown_timer in
game_coordinator_deviceche abbia lo specificatoreprivate.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{}Poiché il costruttore di
countdown_timerrichiede un riferimento al giocatore, aggiungi una variabile opzionale giocatore per memorizzare un riferimento al giocatore in questo gioco a giocatore singolo e crea una funzione denominataFindPlayer()per ottenere il riferimento al giocatore. ChiamaFindPlayer()inOnBegin()prima di configurare le zone.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)Crea una funzione denominata
HandleCountdownEnd()che attenda la fine del timer del conto alla rovescia e attivi il dispositivo Fine gioco.VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)Crea una funzione denominata StartGame() e chiama questa funzione dopo SetupZones() in OnBegin() .Questa funzione deve:Inizializzare il timer del conto alla rovescia.
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)Usa l'espressione
raceper chiamare siaHandleCountdownEnd(ValidPlayer)chePickupDeliveryLoop(), in modo che:Quando il conto alla rovescia termina, il loop di gioco si arresta oppure
Se il loop di gioco si arresta, il conto alla rovescia viene annullato.
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)
Il file game_coordinate_device.verse deve essere simile a:
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 }Salva i file di Verse, compila il codice ed esegui il playtest del livello.
Quando esegui il playtest del livello, il gioco funziona come nella sezione precedente, ma ora è presente un timer che termina la gioco quando il conto alla rovescia finisce o si verifica un problema nel loop di gioco.