22 cze 2024
Jak ulepszyliśmy szablon wyścigu szosowego w Fortnite dzięki persystencji Verse i nie tylko

Wprowadzenie utrwalonych danych (persystencji) do Verse umożliwiło ulepszenie szablonu wyspy trybu kreatywnego Fortnite „Zaprojektuj wyścig szosowy” do UEFN. Od aktualizacji, takich jak dodanie wstawek filmowych i krajobrazów, po odejście od aktywatorów i przejście do Verse, cieszymy się, że możemy podzielić się z wami przebiegiem rozwoju tego szablonu. Witajcie w wyścigu szosowym z persystencją Verse!
Szablon „Zaprojektuj wyścig szosowy” został pierwotnie stworzony w celu wykorzystania nowo utworzonych zasobów toru wyścigowego i zbudowania odrębnej rajdowej rozgrywki z kilkoma fajnymi funkcjami zwiększającymi komfort gry.
W najnowszej aktualizacji przyjęliśmy podejście holistyczne i wymieniliśmy oraz ulepszyliśmy wiele funkcji na całej mapie. Przyjrzyjmy się niektórym z wprowadzonych przez nas aktualizacji, które naprawdę wykorzystują potężne możliwości UEFN.
Dotychczasowy cykl dobowy z oryginalnego projektu został zastąpiony bardziej zaawansowanym oświetleniem z Rozdziału 4 Fortnite: Battle Royale. Ten nowy cykl umożliwił nam użycie Lumen, tworząc bardziej miękkie cienie i realistyczne globalne oświetlenie. Dodaliśmy też takie elementy jak wodospad i uczyniliśmy sam tor bardziej interesującym wizualnie.
Zastosowanie persystencji Verse umożliwiło śledzenie danych podczas sesji gry. Funkcjonalność ta pozwoliła nam na monitorowanie wszystkich statystyk graczy i tworzenie lokalnych rankingów.
Naszym celem było aktualizowanie najlepszego czasu okrążenia gracza po ukończeniu okrążenia, a także rejestrowanie jego punktów i zwycięstw za każdym razem, gdy ukończył wyścig.
Aktualizacja czasów okrążeń była prosta, ponieważ mogliśmy czekać na zdarzenie LapCompletedEvent urządzenia menedżera wyścigu, aby wykryć, kiedy gracz zakończył okrążenie. Po rozpoczęciu wyścigu uruchamiamy licznik czasu, który działa dla każdego gracza. Funkcja WaitForPlayerToFinishLap czeka, aż gracz ukończy okrążenie, oblicza jego czas okrążenia, aktualizuje jego tabelę statystyk, a następnie resetuje licznik czasu, by rejestrować kolejne okrążenia.
Żeby to rozwiązać, użyliśmy funkcji ArraySync. ArraySync wywołuje funkcję asynchroniczną na każdym elemencie przekazanej do niej tablicy i czeka na zakończenie wszystkich tych funkcji. Przekazując tablicę graczy i funkcję WaitForPlayerToFinishRace, mogliśmy wywołać funkcję dla każdego z nich i poczekać, aż wyścig zakończy się dla wszystkich.
Umożliwiło nam to przyznanie graczom punktów i zwycięstwa w oparciu o ich pozycję po ukończeniu wyścigu, a następnie odpowiednie zaktualizowanie ich tabeli statystyk. Po zakończeniu ArraySync wiemy, że wszyscy gracze ukończyli wyścig i możemy zakończyć grę.
Ponieważ mieliśmy już stałe statystyki dla każdego gracza, mogliśmy je pobrać i wyświetlić za pomocą billboardów. W obszarze oczekiwania przed meczem dodaliśmy urządzenia billboardu i odwołania do gracza dla każdego zawodnika, aby pokazać zarówno statystyki, jak i stroje używane w danym momencie. Jednak sortowanie tych graczy na podstawie wyników pozostało wyzwaniem.
Aby to osiągnąć, zaimplementowaliśmy algorytm Merge Sort (sortowanie ze scaleniem). Merge Sort to popularny algorytm typu „dziel i zwyciężaj”, który rekurencyjnie dzieli tablicę na dwie części, sortuje każdą z nich i łączy je ze sobą. Dodaliśmy również możliwość przekazywania funkcji porównania do algorytmu i stworzyliśmy funkcje porównania dla każdej z różnych statystyk gracza.
Za każdym razem, gdy potrzebowaliśmy posortować graczy, mogliśmy pobrać statystyki każdego gracza z jego tabeli statystyk, dodać je wszystkie do tablicy i przekazać zarówno ją, jak i funkcję porównania do algorytmu Merge Sort. Wybierając funkcję porównania, którą przekazujemy, możemy uzyskać tablicę graczy posortowaną według dowolnej statystyki. Do nowego szablonu dołączyliśmy zarówno algorytm Merge Sort, jak i plik testowy, dzięki czemu można samodzielnie przetestować algorytm i dostosować go do własnych funkcji!
Po wprowadzeniu mechanizmów sortowania, sfinalizowaliśmy nasze tabele rankingowe. Podczas pierwszej rundy gry gracze spawnują się w obszarze oczekiwania przed meczem z własnym odwołaniem do gracza i billboardem. Sortujemy te odwołania do graczy według łącznej sumy punktów każdego z nich i wyświetlamy każdą z ich statystyk na billboardzie przed nimi. Dzięki temu gracze mogą sprawdzić konkurencję przed wyścigiem i zorientować się, na kogo uważać, jeśli chcą wygrać.
Grajcie, a być może znajdziecie się na szczycie rankingu!

Aby to zrobić, stworzyliśmy drugą zmienną słabej mapy o nazwie CircuitInfo. Ta zmienna CircuitInfo przechowuje wszystkie informacje, które musimy zresetować na koniec gry lub gdy gracz ją opuści.
Użyliśmy drugiej zmiennej słabej mapy, aby jasne było, które informacje powinny zostać zresetowane (informacje o torze), a które powinny pozostać na stałe (statystyki gracza).
Dla każdego gracza zapisujemy następujące informacje w zmiennej słabej mapy gracza CircuitInfo:
W funkcji OnBegin urządzenia Verse (uruchamianej na początku każdej rundy) ustalamy, w której rundzie się znajdujemy, iterując przez wszystkich aktywnych graczy i uzyskując najwyższą wartość ostatniej zakończonej rundy z ich trwałych danych. Robimy to tylko raz na rundę i zapisujemy informacje o rundzie w zmiennej słabej mapy sesji, dzięki czemu cały kod Verse w projekcie może uzyskać dostęp do tej wartości w dowolnym momencie bez konieczności jej ponownego obliczania.
Gdy gracz ukończy wyścig, wykryty przez oczekujące zdarzenie RaceCompletedEvent urządzenia menedżera wyścigu, zapisujemy jego pozycję na mecie i bieżącą rundę w zmiennej CircuitInfo słabej mapy powiązanej z graczem. Resetujemy te informacje pod dwoma warunkami:
W zaktualizowanym szablonie zastępujemy aktywator impulsu Sekwencerem (Sequencer), aby uzyskać początkową sekwencję filmową. Korzystając z Sequencera, byliśmy w stanie dodać różne kamery, elementy wyświetlacza head-up (HUD) i dynamiczny widok listy, który dostosowuje się w zależności od liczby aktywnych graczy.
Podobnie do tego, jak skonfigurowaliśmy aktywator impulsu, podłączyliśmy urządzenia do sekwencji w ważnych momentach, co pozwoliło nam określić, kiedy wyświetlić wynik następnego gracza lub kiedy przerwać wstęp.

Mamy nadzieję na aktualizację tego szablonu w miarę pojawiania się nowych funkcji, które poprawią jego wygląd. W międzyczasie można pobrać szablon, zapoznać się z jego komponentami i zintegrować jego funkcjonalność ze swoimi projektami za pośrednictwem karty szablonu w UEFN. Nie możemy się doczekać, aby zobaczyć, jak wyścigi, rankingi i nie tylko nabierają kształtu!
Szablon „Zaprojektuj wyścig szosowy” został pierwotnie stworzony w celu wykorzystania nowo utworzonych zasobów toru wyścigowego i zbudowania odrębnej rajdowej rozgrywki z kilkoma fajnymi funkcjami zwiększającymi komfort gry.
W najnowszej aktualizacji przyjęliśmy podejście holistyczne i wymieniliśmy oraz ulepszyliśmy wiele funkcji na całej mapie. Przyjrzyjmy się niektórym z wprowadzonych przez nas aktualizacji, które naprawdę wykorzystują potężne możliwości UEFN.
Projekt wizualny
Tryb krajobrazu w UEFN umożliwił nam powrót do korzeni wyścigów i stworzenie toru off-roadowego przy użyciu nowych narzędzi do edycji krajobrazu. Góry wykonane z zasobów skał w tle naszej oryginalnej wyspy zostały zastąpione projektem bardziej naturalnego krajobrazu.Dotychczasowy cykl dobowy z oryginalnego projektu został zastąpiony bardziej zaawansowanym oświetleniem z Rozdziału 4 Fortnite: Battle Royale. Ten nowy cykl umożliwił nam użycie Lumen, tworząc bardziej miękkie cienie i realistyczne globalne oświetlenie. Dodaliśmy też takie elementy jak wodospad i uczyniliśmy sam tor bardziej interesującym wizualnie.
Persystencja Verse: lepszy ranking
Na oryginalnej mapie wieża korzystająca z urządzenia odwołania do gracza wyświetlała gracza na pierwszym miejscu i liczbę posiadanych przez niego punktów. Dane te nie były jednak zachowywane z sesji na sesję.Zastosowanie persystencji Verse umożliwiło śledzenie danych podczas sesji gry. Funkcjonalność ta pozwoliła nam na monitorowanie wszystkich statystyk graczy i tworzenie lokalnych rankingów.
Śledzenie statystyk gracza
Opracowaliśmy trwałą klasę tabeli statystyk gracza, która śledzi jego dotychczasowe zwycięstwa, najlepszy czas okrążenia i punkty zdobyte za ukończenie wyścigu. Użyliśmy również trwałej słabej mapy (weak map) o nazwie PlayerStatsMap, aby zmapować graczy do ich tabel statystyk graczy, umożliwiając zachowanie tych statystyk w rundach i sesjach. Następnie utworzyliśmy klasę menedżera statystyk, która obsługuje inicjowanie, pobieranie i aktualizowanie tych statystyk dla każdego gracza.Naszym celem było aktualizowanie najlepszego czasu okrążenia gracza po ukończeniu okrążenia, a także rejestrowanie jego punktów i zwycięstw za każdym razem, gdy ukończył wyścig.
Aktualizacja czasów okrążeń była prosta, ponieważ mogliśmy czekać na zdarzenie LapCompletedEvent urządzenia menedżera wyścigu, aby wykryć, kiedy gracz zakończył okrążenie. Po rozpoczęciu wyścigu uruchamiamy licznik czasu, który działa dla każdego gracza. Funkcja WaitForPlayerToFinishLap czeka, aż gracz ukończy okrążenie, oblicza jego czas okrążenia, aktualizuje jego tabelę statystyk, a następnie resetuje licznik czasu, by rejestrować kolejne okrążenia.
Rejestrowanie zwycięstw i punktów
Rejestrowanie zwycięstw i punktów okazało się bardziej skomplikowane. Wiedzieliśmy, że możemy użyć podobnej funkcji “WaitForPlayerToFinishRace”, aby czekać na “RaceCompletedEvent” menedżera wyścigu i wiedzieć, kiedy któryś z graczy ukończy wyścig. Chcieliśmy jednak, aby gra zakończyła się dopiero wtedy, gdy wszyscy gracze ją ukończą, a menedżer wyścigu nie miał możliwości śledzenia tego ani zakończenia gry, gdy wszyscy ją ukończą.Żeby to rozwiązać, użyliśmy funkcji ArraySync. ArraySync wywołuje funkcję asynchroniczną na każdym elemencie przekazanej do niej tablicy i czeka na zakończenie wszystkich tych funkcji. Przekazując tablicę graczy i funkcję WaitForPlayerToFinishRace, mogliśmy wywołać funkcję dla każdego z nich i poczekać, aż wyścig zakończy się dla wszystkich.
Umożliwiło nam to przyznanie graczom punktów i zwycięstwa w oparciu o ich pozycję po ukończeniu wyścigu, a następnie odpowiednie zaktualizowanie ich tabeli statystyk. Po zakończeniu ArraySync wiemy, że wszyscy gracze ukończyli wyścig i możemy zakończyć grę.
Wyświetlanie wyników
Rejestrowanie statystyk to jedno, ale potrzebowaliśmy też sposobu na wyświetlanie ich graczom. Chcieliśmy stworzyć rankingi widoczne na poziomie i posortować graczy według ich łącznej sumy punktów, aby wyróżnić najlepszych graczy.Ponieważ mieliśmy już stałe statystyki dla każdego gracza, mogliśmy je pobrać i wyświetlić za pomocą billboardów. W obszarze oczekiwania przed meczem dodaliśmy urządzenia billboardu i odwołania do gracza dla każdego zawodnika, aby pokazać zarówno statystyki, jak i stroje używane w danym momencie. Jednak sortowanie tych graczy na podstawie wyników pozostało wyzwaniem.
Aby to osiągnąć, zaimplementowaliśmy algorytm Merge Sort (sortowanie ze scaleniem). Merge Sort to popularny algorytm typu „dziel i zwyciężaj”, który rekurencyjnie dzieli tablicę na dwie części, sortuje każdą z nich i łączy je ze sobą. Dodaliśmy również możliwość przekazywania funkcji porównania do algorytmu i stworzyliśmy funkcje porównania dla każdej z różnych statystyk gracza.
Za każdym razem, gdy potrzebowaliśmy posortować graczy, mogliśmy pobrać statystyki każdego gracza z jego tabeli statystyk, dodać je wszystkie do tablicy i przekazać zarówno ją, jak i funkcję porównania do algorytmu Merge Sort. Wybierając funkcję porównania, którą przekazujemy, możemy uzyskać tablicę graczy posortowaną według dowolnej statystyki. Do nowego szablonu dołączyliśmy zarówno algorytm Merge Sort, jak i plik testowy, dzięki czemu można samodzielnie przetestować algorytm i dostosować go do własnych funkcji!
Po wprowadzeniu mechanizmów sortowania, sfinalizowaliśmy nasze tabele rankingowe. Podczas pierwszej rundy gry gracze spawnują się w obszarze oczekiwania przed meczem z własnym odwołaniem do gracza i billboardem. Sortujemy te odwołania do graczy według łącznej sumy punktów każdego z nich i wyświetlamy każdą z ich statystyk na billboardzie przed nimi. Dzięki temu gracze mogą sprawdzić konkurencję przed wyścigiem i zorientować się, na kogo uważać, jeśli chcą wygrać.
Grajcie, a być może znajdziecie się na szczycie rankingu!

Kolejność zawodników na linii startu
Wersja mapy trybu kreatywnego Fortnite ustawia graczy w losowej kolejności na początku każdej rozgrywki. Aby zmotywować graczy do osiągania wyższych pozycji w rankingu, ustaliliśmy kolejność startową w oparciu o ich końcową pozycję z poprzedniej rundy.Śledzenie informacji o rundzie
W przeciwieństwie do trwałych danych, których używaliśmy w rankingu, musieliśmy zachować kolejność zawodników i informacje o torze we wszystkich rundach, ale nie we wszystkich sesjach gry. Zmienna słabej mapy (weak map) sesji w Verse resetuje swoje dane co rundę, więc musimy przechowywać te informacje dla każdego gracza i resetować je po zakończeniu gry.Aby to zrobić, stworzyliśmy drugą zmienną słabej mapy o nazwie CircuitInfo. Ta zmienna CircuitInfo przechowuje wszystkie informacje, które musimy zresetować na koniec gry lub gdy gracz ją opuści.
Użyliśmy drugiej zmiennej słabej mapy, aby jasne było, które informacje powinny zostać zresetowane (informacje o torze), a które powinny pozostać na stałe (statystyki gracza).
Dla każdego gracza zapisujemy następujące informacje w zmiennej słabej mapy gracza CircuitInfo:
- Pozycja na mecie
- Ostatnia ukończona runda
Logika specyficzna dla rundy
Musimy wiedzieć, w której rundzie jesteśmy, aby zastosować logikę specyficzną dla rundy (taką jak porządkowanie graczy według ich ostatniej pozycji na mecie, jeśli nie jest to pierwsza runda) i wiedzieć, kiedy zresetować dane gracza. Obecnie nie ma interfejsu API do pobierania bieżącej rundy, dlatego musimy zapisać ją w trwałych danych dla każdego gracza.W funkcji OnBegin urządzenia Verse (uruchamianej na początku każdej rundy) ustalamy, w której rundzie się znajdujemy, iterując przez wszystkich aktywnych graczy i uzyskując najwyższą wartość ostatniej zakończonej rundy z ich trwałych danych. Robimy to tylko raz na rundę i zapisujemy informacje o rundzie w zmiennej słabej mapy sesji, dzięki czemu cały kod Verse w projekcie może uzyskać dostęp do tej wartości w dowolnym momencie bez konieczności jej ponownego obliczania.
Gdy gracz ukończy wyścig, wykryty przez oczekujące zdarzenie RaceCompletedEvent urządzenia menedżera wyścigu, zapisujemy jego pozycję na mecie i bieżącą rundę w zmiennej CircuitInfo słabej mapy powiązanej z graczem. Resetujemy te informacje pod dwoma warunkami:
- Gracz opuszcza grę w trakcie jej trwania. Subskrybujemy zdarzenie PlayerRemovedEvent obszaru rozgrywki, aby wiedzieć, kiedy gracz ją opuszcza i wywołujemy funkcję ResetCircuitInfo.
- Na początku każdej rundy obliczamy ostatnią ukończoną rundę. Jeśli była to ostatnia runda, wywołujemy ResetCircuitInfo, aby odświeżyć dane gracza. Wiemy, ile jest rund, mając edytowalną właściwość na urządzeniu Verse, która jest ustawiona na całkowitą liczbę rund w grze. Twórca wyspy powinien upewnić się, że jest to zgodne z ustawieniami rundy.
Kiedy używać słabych map sesji, a kiedy słabych map gracza
Ten nowy wyścig szosowy jest świetnym przykładem do pokazania różnic i uzasadnienia użycia zmiennej słabej mapy sesji i zmiennej słabej mapy gracza w kodzie:- Zmienne słabej mapy sesji są przydatne dla singletonów i przechowywania danych dla bieżącej rundy, których nie chcemy za każdym razem ponownie obliczać.
- Zmienne słabej mapy gracza są przeznaczone dla informacji, które muszą być przechowywane przez wiele rund i sesji gry, ale muszą być powiązane z poszczególnymi graczami.
Od aktywatora impulsu do sekwencji początkowej
W oryginalnej wersji szablonu wyścigu szosowego wykorzystaliśmy urządzenie o nazwie „Aktywator impulsu” do zaaranżowania części wyścigu „gotowy – do startu – start ”. Aktywator impulsu odtwarzał sekwencję zdarzeń w zadanym okresie czasu, wyzwalając aktywatory wyświetlające tekst i włączające światła na linii startu.W zaktualizowanym szablonie zastępujemy aktywator impulsu Sekwencerem (Sequencer), aby uzyskać początkową sekwencję filmową. Korzystając z Sequencera, byliśmy w stanie dodać różne kamery, elementy wyświetlacza head-up (HUD) i dynamiczny widok listy, który dostosowuje się w zależności od liczby aktywnych graczy.
Podobnie do tego, jak skonfigurowaliśmy aktywator impulsu, podłączyliśmy urządzenia do sekwencji w ważnych momentach, co pozwoliło nam określić, kiedy wyświetlić wynik następnego gracza lub kiedy przerwać wstęp.

Co dalej?
Mamy nadzieję, że w miarę rozwoju UEFN szablon ten będzie również ewoluował. Naszym celem jest umożliwienie wam dalszego tworzenia bardziej zaawansowanych treści za pomocą UEFN, wykorzystując najnowsze funkcje do tworzenia zabawnych, angażujących wysp.Mamy nadzieję na aktualizację tego szablonu w miarę pojawiania się nowych funkcji, które poprawią jego wygląd. W międzyczasie można pobrać szablon, zapoznać się z jego komponentami i zintegrować jego funkcjonalność ze swoimi projektami za pośrednictwem karty szablonu w UEFN. Nie możemy się doczekać, aby zobaczyć, jak wyścigi, rankingi i nie tylko nabierają kształtu!