Un bucle de juego es un código que se ejecuta repetidamente (bucles) para responder a una entrada (normalmente el jugador interactuando con su mando o ratón), actualizar el estado del juego y proporcionar una salida (que muestra al jugador que ha afectado al estado del juego, como cuando al pulsar un botón se enciende una luz). Normalmente, el bucle finaliza cuando la partida llega al estado finalizado, como cuando el jugador alcanza una meta, o al estado fallido, como cuando se queda sin tiempo antes de alcanzar la meta.
Al completar este paso del tutorial Prueba contrarreloj: Reparto de pizzas, tendrás más información sobre cómo crear el bucle de juego y definir el estado de finalización y fallo del juego.
A continuación se muestra el seudocódigo del bucle de juego Prueba contrarreloj: Reparto de pizzas.
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
Este bucle debe terminar cuando finalice el cronómetro de cuenta atrás, o si se produce un error inesperado en el juego.
Cómo crear el bucle central de juego
Sigue estos pasos para actualizar el archivo game_coordinator_device.verse:
Crea un nuevo método nombrado
PickupDeliveryLoop()que tenga los especificadoresprivateysuspends. Mueve el bucle que creaste anteriormente enOnBegin()a este nuevo método.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):Determina el número máximo de niveles de recogida a partir de la longitud de la matriz de etiquetas, e incrementa el
PickupLevelcada vez que el jugador complete una zona de recogida, siempre que el nivel de recogida no sea mayor que el número máximo de niveles de recogida.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 de entrega debe activarse después de que el jugador complete su primera recogida, pero el jugador debe poder seguir recogiendo objetos si quiere antes de ir a la zona de entrega. Para ello, el código de zona de recogida y el código de zona de entrega deben coincidir. Este ejemplo utiliza la expresión de simultaneidad race porque:
El bloque de entrega debe cancelar el bucle de la zona de recogida cuando el jugador finalice una entrega.
El bucle de zona de recogida debe cancelar el bloque de entrega si hay un problema con el bucle de recogida.
También necesitas una ligera modificación en la desactivación de zonas. Cuando se cancela el bucle o el bloque de entrega, no se debería llamar a
DeactivateZone()si la secuencia de comandos estuviera esperando a que se completara la zona.Como la línea de desactivación de la zona nunca se ejecutaba, la zona permanecía activa, lo que creaba un error.
Para solucionarlo, puedes utilizar la expresión defer. Un
deferretrasa la ejecución de las expresiones que contiene hasta que finalice el ámbito en el que aparecedefer. Undeferse ejecuta siempre que el control del programa se transfiere fuera del ámbito, incluyendo la salida normal de un ámbito (fin de una función), salidas anticipadas (como return o break) o debido a cualquier tarea simultánea/expresión asíncrona cancelada (como unrace). Es como poner en cola operaciones que se ejecutarán al final, pase lo que pase. Envuelve cada llamada aDeactivateZoneen undefer, y muévelas antes de sus respectivosZoneCompletedEvent.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()En el ejemplo anterior, la zona de entrega se activa al mismo tiempo que la zona de recogida, pero la activación de la zona de entrega debe esperar a que se complete la primera recogida. Para ello, añade un evento y haz que la zona de entrega espere al evento antes de activarse.
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(){}Haz un bucle con esta expresión `race` de zona de recogida/zona de entrega hasta que termine la partida para que el jugador pueda seguir recogiendo y entregando objetos.
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(){}Tu archivo game_coordinator_device.verse debería tener ahora el siguiente aspecto:
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 }
Guarda tus archivos de Verse, compila el código y prueba tu nivel.
Cuando pruebes tu nivel, uno de los dispositivos Generador de objetos se activará al principio de la partida y después de que el jugador recoja un objeto. Después de que el jugador recoja su primer objeto, ese dispositivo Generador de objetos se desactivará y entonces se activará un dispositivo Zona de captura. Esto continúa hasta que termines manualmente el juego.
Cómo definir los estados de finalización y fallo del bucle de juego
Ahora que ya tienes creado el bucle central de juego, define el estado de finalización y de fallo del bucle de juego. Se supone que esta partida termina cuando:
Termina una cuenta atrás, o bien
Hay un problema con el bucle del juego.
Sigue estos pasos para configurar los estados de finalización y fallo del juego:
Crea una instancia de la clase countdown_timer en
game_coordinator_deviceque tenga el 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 el constructor de
countdown_timernecesita una referencia al jugador, añade una variable opcional `player` para almacenar una referencia al jugador en este juego de un solo jugador y crea una función llamadaFindPlayer()para obtener la referencia al jugador. Llama aFindPlayer()enOnBegin()antes de configurar las 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)Crea una función nombrada
HandleCountdownEnd()que espere a que termine el cronómetro de la cuenta atrás y active el dispositivo Fin de partida.VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)Crea una función nombrada
StartGame()y nómbrala después deSetupZones()enOnBegin(). Esta función debería hacer lo siguiente:Inicializa el cronómetro de cuenta atrás.
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)Utiliza la expresión
racepara llamar tanto aHandleCountdownEnd(ValidPlayer)como aPickupDeliveryLoop(), de forma que:Cuando termina la cuenta atrás, se detiene el bucle de juego, o bien
Si el bucle del juego se detiene, se cancela la cuenta atrás.
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)
Tu archivo game_coordinate_device.verse debería tener ahora el siguiente aspecto:
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 }Guarda tus archivos de Verse, compila el código y prueba tu nivel.
Cuando pruebes tu nivel, el juego funcionará igual que en la sección anterior, pero ahora hay un cronómetro que terminará el juego cuando termine la cuenta atrás o haya un problema en el bucle de juego.