Pętla gry to kod uruchamiany w sposób powtarzalny (zapętlony), aby reagować na dane wejściowe (zwykle interakcje gracza za pomocą kontrolera lub myszy) w celu aktualizacji stanu gry i dostarczenia danych wyjściowych, które pokazują graczowi, że wpłynął na stan gry, na przykład po naciśnięciu przycisku, który włącza światło. Pętla zwykle kończy się, gdy gra zostanie ukończona, na przykład po osiągnięciu celu przez gracza, lub zakończy się niepowodzeniem, na przykład po upłynięciu limitu czasu przed osiągnięciem celu.
Wykonując ten krok w samouczku Próba czasu: Pogoń za pizzą (Time Trial: Pizza Pursuit), nauczysz się tworzyć pętlę gry i zdefiniujesz stany ukończenia i niepowodzenia gry.
Poniżej przedstawiono pseudokod pętli gry w grze Próba czasu: Pogoń za pizzą (Time Trial: Pizza Pursuit):
loop:
race:
loop:
SelectNextPickupZone
WaitForPlayerToCompletePickupZone
block:
WaitForFirstPickup
SelectNextDeliveryZone
WaitForPlayerToCompleteDeliveryZone
Ta pętla powinna się kończyć, gdy licznik czasu odliczania zakończy odliczanie lub gdy w grze wystąpi niespodziewany błąd.
Tworzenie głównej pętli gry
Aby zaktualizować plik game_coordinator_device.verse, wykonaj następujące instrukcje:
Utwórz metodę o nazwie
PickupDeliveryLoop()zawierającą specyfikatoryprivateisuspends. Przenieś pętlę utworzoną wcześniej w metodzieOnBegin()do tej nowej metody.VerseOnBegin<override>;()<suspends> : void = SetupZones() PickupDeliveryLoop() PickupDeliveryLoop<private>()<suspends> : void = var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]):Określ maksymalną liczbę poziomów odbioru na podstawie długości tablicy tagów i zwiększaj wartość
PickupLevelza każdym razem, gdy gracz ukończy strefę odbioru, dopóki poziom odbioru nie będzie większy niż maksymalna liczba poziomów odbioru.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 = 0Aktywacja strefy dostarczania powinna nastąpić, po ukończeniu przez gracza pierwszego odbioru, jednak mimo to, gracz powinien być w stanie podnosić przedmioty, jeśli zechce, zanim uda się do strefy dostarczania. W związku z tym kody stref odbioru i dostarczania muszą występować równocześnie. W tym przykładzie użyto wyrażenia współbieżności race, ponieważ:
Blok dostarczania powinien anulować pętlę strefy odbioru, gdy gracz zakończy dostarczanie.
Pętla strefy dostarczania powinna anulować blok dostarczania w razie wystąpienia problemu z pętlą odbioru.
Konieczna jest również nieznaczna modyfikacja dezaktywacji strefy. W przypadku anulowania pętli lub bloku dostarczania funkcja
DeactivateZone()nie powinna być wywoływana, jeśli skrypt oczekiwał na ukończenie strefy.Wiersz dezaktywacji strefy nie zostałby nigdy wykonany, więc strefa pozostałaby aktywna, co prowadziłoby do błędu.
Aby naprawić ten problem, można użyć wyrażenia defer. Wyrażenie
deferopóźnia wykonanie zawartych w nim wyrażeń, dopóki zakres, w którym występuje wyrażeniedefer, nie dobiegnie końca. Wyrażeniedeferzostanie uruchomione po przekazaniu sterowania programem poza zakres poprzez typowe opuszczenie zakresu (zakończenie funkcji), wczesne zakończenie (na przykład za pomocą wyrażeń return lub break) albo w wyniku dowolnego anulowanego zadania współbieżnego / wyrażenia asynchronicznego (takiego jakrace). Przypomina to kolejkowanie operacji, które zostaną wykonane na samym końcu, bez względu na to, co się stanie. Zawrzyj każde wywołanie funkcjiDeactivateZonew wyrażeniudeferi przenieś je przed odpowiednią funkcjęZoneCompletedEvent.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()Kod z poprzedniego przykładu aktywuje strefę dostarczania równocześnie z aktywacją strefy odbioru, jednak aktywacja strefy dostarczania będzie oczekiwać do momentu ukończenia pierwszego odbioru. W tym celu dodaj zdarzenie i skonfiguruj aktywację strefy dostarczania tak, aby następowała dopiero po wystąpieniu zdarzenia.
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(){}Zapętl to wyrażenie race strefy odbioru/dostarczania do czasu ukończenia gry, aby gracz mógł odbierać i dostarczać przedmioty.
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(){}Otrzymany plik game_coordinator_device.verse powinien wyglądać następująco:
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 }
Zapisz swoje pliki Verse, skompiluj kod i przeprowadź test gry na swoim poziomie.
W trakcie przeprowadzania testu gry na twoim poziomie jedno z urządzeń generatora przedmiotów zostanie aktywowane na początku gry oraz po tym, jak gracz odbierze przedmiot. Gdy gracz odbierze pierwszy przedmiot, urządzenie generatora przedmiotów zostanie dezaktywowane i nastąpi aktywacja urządzenia strefy punktowanej. Cykl ten będzie kontynuowany, dopóki ręcznie nie zakończysz gry.
Definiowanie stanów ukończenia i niepowodzenia dla pętli gry
Masz już utworzoną główną pętlę gry. Teraz zdefiniuj stan ukończenia i niepowodzenia pętli gry. Ta gra ma się zakończyć, gdy:
Odliczanie dobiegnie końca, lub
Wystąpi problem z pętlą gry.
Aby skonfigurować stany ukończenia i niepowodzenia dla gry, wykonaj następujące instrukcje:
Utwórz instancję klasy countdown_timer w urządzeniu
game_coordinator_device, stosując specyfikatorprivate.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{}Konstruktor
countdown_timerwymaga odwołania do gracza, więc dodaj opcjonalną zmienną gracza do przechowywania odwołania do gracza w trybie jednego gracza i utwórz funkcję o nazwieFindPlayer(), która będzie pobierać odwołanie do gracza. Przed skonfigurowaniem stref wywołaj funkcjęFindPlayer()w metodzieOnBegin().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)Utwórz funkcję o nazwie
HandleCountdownEnd(), a następnie odczekaj, aż licznik czasu odliczania zakończy odliczanie i aktywuje urządzenie końca gry.VerseHandleCountdownEnd<private>(InPlayer : agent)<suspends> : void = CountdownTimer.CountdownEndedEvent.Await() EndGame.Activate(InPlayer)Utwórz funkcję o nazwie
StartGame(), a następnie wywołaj tę funkcję po funkcjiSetupZones()w metodzieOnBegin(). Ta funkcja powinna:Zainicjować licznik czasu odliczania.
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)Za pomocą wyrażenia
racewywołaj funkcjeHandleCountdownEnd(ValidPlayer)iPickupDeliveryLoop(), aby:Pętla gry została zatrzymana, gdy odliczanie dobiegnie końca, lub
Odliczanie zostało anulowane, jeśli pętla gry zostanie zatrzymana.
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)
Plik game_coordinate_device.verse powinien teraz wyglądać następująco:
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 }Zapisz swoje pliki Verse, skompiluj kod i przeprowadź test gry na swoim poziomie.
Podczas testu gry utworzonego poziomu gra działa tak samo, jak w poprzedniej sekcji, jednak teraz dostępny jest licznik czasu odliczania, który zakończy grę po zakończeniu odliczania lub w razie wystąpienia problemu w pętli gry.