Eine Spielschleife ist Code, der wiederholt ausgeführt wird (Schleifen), um auf Input zu reagieren (in der Regel die Interaktion des Spielers mit dem Controller oder der Maus), den Spielstatus zu aktualisieren und Output zu liefern, der dem Spieler zeigt, dass er den Spielstatus beeinflusst hat, z. B. das Drücken einer Taste schaltet ein Licht ein). Die Schleife endet normalerweise, wenn das Spiel einen Abschlussstatus erreicht, indem der Spieler zum Beispiel ein Ziel erreicht, oder einen Fehlschlagstatus, wenn die Zeit ausgeht, bevor er das Ziel erreicht.
Wenn du diesen Schritt im Tutorial Time Trial: Pizza Pursuit abgeschlossen hast, weißt du, wie du die Spielschleife erstellst und den Abschluss- und Fehlschlagstatus des Spiels definierst.
Im Folgenden findest du den pseudocode für die Spielschleife in Time Trial: Pizza Pursuit:
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
Diese Schleife sollte enden, wenn der Countdown-Timer abgelaufen ist oder wenn ein unerwarteter Fehler im Spiel auftritt.
Erstellen der Kernspielschleife
Befolge diese Schritte, um die Datei game_coordinator_device.verse zu aktualisieren:
Erstelle eine neue Methode mit dem Namen
PickupDeliveryLoop(), die die Bezeichnerprivateundsuspendshat. Verschiebe die Schleife, die du zuvor inOnBegin()erstellt hast, in diese neue Methode.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):Bestimme die maximale Anzahl von Abholpunkt-Leveln aus der Länge des Tags-array und erhöhe das
PickupLeveljedes Mal, wenn der Spieler eine Abholzone abschließt, solange das Abholpunkt-Level nicht größer als die maximale Anzahl von Abholpunkt-Leveln ist.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 = 0Die Lieferzone sollte aktiviert werden, nachdem der Spieler seine erste Abholung abgeschlossen hat, aber der Spieler sollte immer noch in der Lage sein, Gegenstände abzuholen, wenn er dies möchte, bevor er in die Lieferzone geht. Dazu müssen der Code für die Abholzone und der Code für die Lieferzone zur gleichen Zeit eingegeben werden. Dieses Beispiel verwendet den race-Gleichzeitigkeitsausdruck, weil:
Der Lieferblock sollte die Abhol-Zonen-Schleife abbrechen, wenn der Spieler eine Lieferung abschließt.
Die Abhol-Zonen-Schleife sollte den Lieferblock abbrechen, wenn es ein Problem mit der Abholzonenschleife gibt.
Außerdem musst du die Deaktivierung der Zonen leicht ändern. Wenn entweder die Schleife oder der Lieferblock abgebrochen wird, sollte
DeactivateZone()nicht aufgerufen werden, wenn das Script auf die Fertigstellung der Zone wartet.Da die Zeile zur Deaktivierung der Zone nie ausgeführt würde, bliebe die Zone aktiv, was einen Fehler verursacht.
Um dies zu beheben, kannst du den Ausdruck defer verwenden. Ein
deferverzögert die Ausführung der darin enthaltenen Ausdrücke, bis der Bereich, in demdefererscheint, endet. Eindeferwird ausgeführt, wie auch immer die Programmkontrolle aus dem Bereich heraus übertragen wird, einschließlich des normalen Verlassens eines Bereichs (Ende einer Funktion), frühzeitige Beendigungen (wie return oder break) oder aufgrund eines abgebrochenen gleichzeitigen Tasks / asynchronen Ausdrucks (wie einrace). Es ist, als ob man Vorgänge in eine Warteschlange stellt, die erst ganz zum Schluss ausgeführt werden, egal, was passiert. Verpacke jedenDeactivateZone-Aufruf in einendefer, und verschiebe es vor sein jeweiligesZoneCompletedEvent.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()Im vorherigen Beispiel wird die Lieferzone zur gleichen Zeit aktiviert wie die Abholzone, aber die Aktivierung der Lieferzone sollte warten, bis die erste Abholung abgeschlossen ist. Hierfür kann ein Event hinzugefügt werden, auf das die Lieferzone wartet, bevor sie aktiviert wird.
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(){}Führe eine Schleife mit dem `race`-Ausdruck "Abholzone/Lieferzone" durch, bis das Spiel beendet ist, damit der Spieler weiterhin Gegenstände abholen und liefern kann.
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(){}Deine Datei game_coordinator_device.verse sollte nun wie folgt aussehen:
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 }
Speichere deine Verse-Dateien, kompiliere deinen Code und teste dein Level.
Wenn du deinen Level testest, wird eines der Gegenstands-Spawnpunkt-Geräte zu Beginn des Spiels aktiviert und nachdem der Spieler einen Gegenstand einsammelt. Nachdem der Spieler seinen ersten Gegenstand aufgesammelt hat, wird das Gegenstands-Spawnpunkt-Gerät deaktiviert und ein Aufnahmebereich-Gerät wird aktiviert. Das geht so lange, bis du das Spiel manuell beendest.
Definieren von Abschluss- und Fehlerzuständen für die Spielschleife
Nachdem du nun den Kern der Spielschleife erstellt hast, definiere den Abschluss- und Fehlerstatus der Spielschleife. Dieses Spiel soll enden, wenn:
Ein Countdown endet, oder
Ein Problem mit der Spielschleife auftritt.
Befolge diese Schritte, um den Abschluss- und Fehlerstatus für das Spiel einzurichten:
Erstelle eine Instanz der countdown_timer-Klasse in
game_coordinator_device, die den Bezeichnerprivatehat.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{}Da der Konstruktor für
countdown_timereine Spielerreferenz benötigt, füge eine optionale Spielervariable hinzu, um eine Referenz auf den Spieler in diesem Einzelspieler-Spiel zu speichern und erstelle eine Funktion namensFindPlayer(), um die Spielerreferenz zu erhalten. RufeFindPlayer()inOnBegin()auf, bevor du die Zonen einrichtest.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)Erstelle eine Funktion namens
HandleCountdownEnd(), die auf das Ende des Countdown-Timers wartet und das Spielbeender-Gerät aktiviert.VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)Erstelle eine Funktion mit dem Namen
StartGame()und rufe diese Funktion nachSetupZones()inOnBegin()auf. Diese Funktion sollte:Initialisiere den Countdown-Timer.
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)Verwende den
race-Ausdruck, um sowohlHandleCountdownEnd(ValidPlayer)als auchPickupDeliveryLoop()aufzurufen, sodass:Wenn der Countdown endet, die Spielschleife angehalten wird, oder
Wenn die Spielschleife anhält, wird der Countdown abgebrochen.
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)
Deine Datei game_coordinate_device.verse sollte nun wie folgt aussehen:
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 }Speichere deine Verse-Dateien, kompiliere deinen Code und teste dein Level.
Wenn du deinen Level testest, funktioniert das Spiel genauso wie im vorherigen Abschnitt, aber jetzt gibt es einen Timer, der das Spiel beendet, wenn der Countdown abläuft oder es ein Problem in der Spielschleife gibt.