Une boucle de jeu est un code qui s'exécute de manière répétée (en boucle) pour répondre à des commandes (généralement, les interactions du joueur avec sa manette ou sa souris), pour actualiser l'état du jeu et pour fournir un résultat (montrer au joueur qu'il a modifié l'état du jeu, par exemple lorsqu'il appuie sur un bouton qui allume une lumière). En général, la boucle se termine lorsque le jeu atteint un état d’achèvement, par exemple lorsque le joueur atteint un objectif, ou un état d’échec si le temps imparti s’écoule avant que le joueur ait atteint l’objectif.
Dans cette étape du tutoriel
Voici le pseudo-code de la boucle de jeu dans Contre-la-montre : Pizza Pursuit :
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
Cette boucle doit s’achever lorsque le compte à rebours se termine, ou si une erreur inattendue survient pendant le jeu.
Créer la boucle de jeu de base
Procédez comme suit pour mettre à jour le fichier game_coordinator_device.verse :
Créez une nouvelle méthode nommée
PickupDeliveryLoop()avec les spécificateursprivateetsuspends. Déplacez la boucle que vous avez précédemment créée dansOnBegin()vers cette nouvelle méthode.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):Déterminez le nombre maximum de niveaux de collecte selon la longueur de la matrice de balises, et augmentez le niveau de collecte (
PickupLevel) chaque fois que le joueur termine une zone de collecte, à condition que le niveau de collecte ne soit pas supérieur au nombre maximum de niveaux de collecte.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 zone de livraison doit s’activer après que le joueur a effectué sa première collecte, mais le joueur doit toujours pouvoir collecter des objets s'il le souhaite avant d’accéder à la zone de livraison. Pour ce faire, le code de la zone de collecte et le code de la zone de livraison doivent s’exécuter en même temps. Dans cet exemple, nous utilisons l'expression de simultanéité race, car :
le bloc de livraison doit annuler la boucle de la zone de collecte lorsque le joueur termine une livraison ;
la boucle de zone de collecte doit annuler le bloc de livraison si un problème se produit avec la boucle de livraison.
Vous devez également apporter une légère modification à la désactivation de la zone. Lorsque la boucle (ou le bloc) de livraison est annulée,
DeactivateZone()n'est pas appelé si le script attend que la zone soit terminée.Étant donné que la ligne de désactivation de zones ne serait jamais exécutée, la zone resterait active, ce qui entraînerait un bogue.
Pour résoudre ce problème, vous pouvez utiliser l'expression defer. Une expression
deferretarde l'exécution des expressions qu'elle contient jusqu'à ce que l'étendue dans laquelledeferapparaît se termine. Une expressiondefers'exécute dès que le contrôle du programme est transféré hors de l'étendue, notamment lorsque vous quittez une étendue normalement (fin d'une fonction), lors de sorties anticipées (entrée ou retour chariot) ou en raison de l'annulation d'une tâche simultanée/expression asynchrone (commerace). C'est un peu comme si l’on mettait en file d'attente des opérations qui seraient exécutées à la toute fin, quoi qu'il arrive. Englobez chaque appelDeactivateZonedans une expressiondeferet déplacez-le avant son événementZoneCompletedEvent.Await()respectif.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()Dans l'exemple précédent, la zone de livraison est activée en même temps que la zone de collecte, mais l'activation de la zone de livraison doit attendre la fin de la première collecte. Pour ce faire, ajoutez un événement et faites en sorte que la zone de livraison attende l'événement avant de s'activer.
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(){}Lisez en boucle l’expression race de cette zone de collecte/zone de livraison jusqu'à la fin du jeu, afin que le joueur puisse continuer à collecter et à livrer des objets.
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(){}Votre fichier game_coordinator_device.verse doit maintenant être similaire à ce qui suit :
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 }
Enregistrez vos fichiers Verse, compilez votre code et testez votre niveau.
Lorsque vous testez votre niveau, l'un des générateurs d'objets s'active au début du jeu et après que le joueur a collecté un objet. Une fois que le joueur a collecté son premier objet, le générateur d'objets se désactive et un appareil de capture de zone s'active. Cette séquence se poursuit jusqu'à ce que vous mettiez fin manuellement à la partie.
Définir les états d'achèvement et d'échec de la boucle de jeu
Maintenant que vous avez créé la boucle de jeu principale, définissez l'état d'achèvement et d'échec de la boucle de jeu. Ce jeu est censé se terminer :
À la fin d'un compte à rebours ou
Si un problème s'est produit dans la boucle de jeu.
Procédez comme suit pour configurer les états d'achèvement et d'échec du jeu :
Créez une instance de la classe countdown_timer dans l'appareil
game_coordinator_deviceavec le spécificateurprivate.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{}Étant donné que le constructeur de
countdown_timernécessite une référence au joueur, ajoutez une variable de joueur facultative pour stocker une référence au joueur dans ce jeu solo et créez une fonction nomméeFindPlayer()pour obtenir la référence du joueur. AppelezFindPlayer()dansOnBegin()avant de configurer les zones.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)Créez une fonction nommée
HandleCountdownEnd()qui attend la fin du compte à rebours et active l'appareil de fin de partie.VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)Créez une fonction nommée
StartGame()et appelez cette fonction aprèsSetupZones()dansOnBegin(). Cette fonction doit :Initialisez le compte à rebours.
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)Utilisez l'expression
racepour appeler à la foisHandleCountdownEnd(ValidPlayer)etPickupDeliveryLoop(), de sorte que :lorsque le compte à rebours se termine, la boucle de jeu s'arrête ; ou
lorsque le jeu s'arrête, le compte à rebours est annulé.
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)
Votre fichier game_coordinate_device.verse doit maintenant être similaire à ce qui suit :
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 }Enregistrez vos fichiers Verse, compilez votre code et testez votre niveau.
Lorsque vous testez votre niveau, le jeu fonctionne de la même manière que dans la section précédente, mais il dispose désormais d’un chronomètre qui met fin au jeu au terme du compte à rebours, ou si un problème s’est produit dans la boucle de jeu.