Ten samouczek bazuje na koncepcjach opisanych w artykule Persystencja danych statystyk graczy, więc najpierw się z nim zapoznaj!
Tablice rankingowe stanowią tradycyjny element gier rywalizacyjnych, umożliwiając graczom pochwalenie się swoimi umiejętnościami i zyskanie sławy wśród innych. Pomagają one graczom zyskać poczucie postępu i zachęcają do wracania do gry, aby móc obserwować, własną drogę po szczeblach rankingu.
Persystencja Verse oferuje narzędzie, które umożliwia tworzenie takich tablic rankingowych w celu wzbogacenia swojej przygody o element rywalizacji. Z samouczka dotyczącego persystencji danych statystyk graczy wiesz już, w jaki sposób możesz śledzić możliwe do persystencji dane między sesjami gry i jak modyfikować oraz aktualizować te dane w oparciu o różne zdarzenia. Teraz wykorzystasz tę wiedzę, aby nauczyć się tworzenia kompletnych lokalnych tablic rankingowych, sortowania statystyk graczy oraz łączenia tych wszystkich elementów w grze wyścigowej!
Używane funkcje języka Verse
-
Klasa: W tym przykładzie utworzysz możliwą do persystencji klasę Verse, która będzie śledzić grupę statystyk dla poszczególnych graczy.
-
Konstruktor: Konstruktor to specjalna funkcja, która tworzy instancję klasy, z którą jest związana.
-
Weak_map: weak_map to prosta mapa, w przypadku której nie można wykonywać iteracji. Możliwe do persystencji dane Verse muszą być przechowywane w weak_map.
Ustawianie poziomu
W tym przykładzie wykorzystano następujące rekwizyty i urządzenia:
-
3 x urządzenie billboardu: Te urządzenia będą wyświetlacz statystyki poszczególnych graczy z całego czasu życia i będziesz je sortować w zależności od punktów czasu życia, aby zaprezentować w lobby wykaz najlepszych graczy.
-
3 x urządzenie odwołania do gracza: W połączeniu z billboardami odwołania do gracza pozwolą spersonalizować najlepszych graczy, aby inni wiedzieli, na kogo uważać podczas gry.
-
3 x urządzenie punktu kontrolnego: Są to punkty kontrolne, które gracze muszą zaliczyć, aby ukończyć wyścig.
-
1 x urządzenie menadżera wyścigu: Śledzi, kiedy gracze rozpoczynają i kończą wyścig, a następnie przyznaje im punkty w zależności od lokaty na mecie.
-
1 x urządzenie generatora pickupów: Spawnuje pojazd, który będzie używany podczas wyścigu, jednak możesz go zastąpić dowolnym pojazdem pasującym do przygody.
Postępuj zgodnie z poniższą instrukcją, aby skonfigurować poziom:
Billboardy i odwołania do graczy
Do wyświetlenia statystyk graczy potrzebna jest kombinacja billboardów i odwołań do graczy. Każdy billboard będzie wyświetlał statystyki czasu życia gracza, natomiast odwołanie do gracza pozwoli utworzyć wizualną reprezentację danego gracza. Postępuj zgodnie z poniższą instrukcją, aby dodać te elementy:
-
Dodaj do swojego poziomu trzy urządzenia odwołania do gracza, umieszczając je obok siebie.
-
Wybierz je w Outlinerze dla każdego odwołania do gracza. W panelu Szczegóły w sekcji Opcje użytkownika ustaw w pozycji Niestandardowy kolor kolor, który chcesz zastosować do oznaczenia w lobby graczy zajmujących pierwszą, drugą i trzecią lokatę. W tym przykładzie zastosowane kolory to złoto, srebro i brąz.
-
Dodaj do swojego poziomu trzy urządzenia billboardu, umieszczając je przed poszczególnymi odwołaniami do graczy. Gdy rozpocznie się gra, będą one aktualizowane o statystyki każdego z graczy przy użyciu Verse.
Punkty kontrolne, pickup i menadżer wyścigu
Tworzysz wyścig, więc potrzebujesz czegoś, czym można się ścigać! Musisz dodać również punkty kontrolne do zaliczenia oraz menadżera wyścigu, który pokieruje wyścigiem podczas gry. Postępuj zgodnie z poniższą instrukcją, aby dodać te elementy:
-
Dodaj do swojego poziomu trzy urządzenia punktu kontrolnego wyścigu. Ustaw je w takiej kolejności, w jakiej gracze powinni przez nie przejechać. Dla każdego punktu kontrolnego upewnij się w Outlinerze, że wartość Numer punktu kontrolnego odpowiada kolejności, w jakiej gracze będą przejeżdżali przez punkty kontrolne.
-
Dodaj jedno urządzenie menadżera wyścigu do poziomu. Jego zadaniem będzie uruchomienie wyścigu i pokierowanie graczy w kierunku punktów kontrolnych. Później będziesz nasłuchiwać zdarzenia
RaceCompletedEvent()
z tego urządzenia, aby wiedzieć, kiedy gracz ukończy wyścig. -
Dodaj jedno urządzenie generatora pickupów do swojego poziomu. Pojazd jest opcjonalny, jednak w tym przewodniku wykorzystaną pickup pasujący do szablonu szosy, aby gracze mieli się czym poruszać.
Modyfikowanie tabeli statystyk
W tym przykładzie wykorzystano zmodyfikowaną wersję pliku player_stats_table
z artykułu Persystencja danych statystyk graczy. Plik będzie wyglądał podobnie, jak w tamtym przykładzie, jednak zawiera ważne różnice, które zmieniają sposób implementacji.
Aby utworzyć tabelę statystyk graczy, wykonaj następującą procedurę:
-
W swojej klasie
player_stats_table
:-
Usuń statystykę
Losses
. -
Zamień statystykę
Score
naPoints
.
# Śledzi różne persystentne statystyki dla każdego gracza. player_stats_table<public>:= class<final><persistable>: # Wersja tabeli aktualnych statystyk. Version<public>:int = 0 # Punkty gracza. Points<public>:int = 0 # Liczba wygranych gracza. Wins<public>:int = 0
-
-
Zmodyfikuj funkcję konstruktora
MakePlayerStatsTable()
w swoim pliku, aby odzwierciedlała zaktualizowane statystyki.# Tworzy nową player_stats_table z takimi samymi wartościami, jak poprzednia player_stats_table. MakePlayerStatsTable<constructor>(OldTable:player_stats_table)<transacts> := player_stats_table: Version := OldTable.Version Points := OldTable.Points Wins := OldTable.Wins
-
Dodaj nową strukturę (struct)
player_and_stats
do pliku player_stats_table.verse. Struktura ta zawiera odwołanie doplayer
oraz jego klasęplayer_stats_table
, umożliwiając wykorzystanie obydwu danych w funkcji bez konieczności ich wielokrotnego pobierania. Gotowa strukturaplayer_and_stats
powinna wyglądać następująco:# Struktura do przekazywania gracza i jego statystyk jako argumentów. player_and_stats<public> := struct: Player<public>:player StatsTable<public>:player_stats_table
Zarządzanie statystykami
Podobnie jak w przypadku persystencji danych statystyk graczy, wykorzystasz plik menadżera do zarządzania i rejestrowania zmian w statystykach dla graczy.
Aby utworzyć zmodyfikowany plik player_stats_manager
, wykonaj poniższe kroki.
-
Zmodyfikuj sygnaturę funkcji
InitializeAllPlayers()
orazInitializePlayer()
naInitializeAllPlayerStats()
iInitializePlayerStat()
. Nazwy te lepiej odzwierciedlają ich relację względem funkcjiGetPlayerStat()
. Zaktualizowana funkcja powinna wyglądać następująco:# Zainicjuj statystyki dla wszystkich bieżących graczy. InitializeAllPlayerStats<public>(Players:[]player):void = for (Player : Players): InitializePlayerStat(Player) # Zainicjuj statystyki dla danego gracza. InitializePlayerStat<public>(Player:player):void= if: not PlayerStatsMap[Player] set PlayerStatsMap[Player] = player_stats_table{} else: Print("Nie można zainicjować statystyk gracza")
-
Zmodyfikuj sygnaturę funkcji
AddScore()
naAddPoints()
. Następnie usuń funkcjęAddLosses()
, ponieważplayer_stats_table
nie zawiera już tej wartości. Gotowy plikplayer_stats_manager
powinien wyglądać następująco:# Ten plik obsługuje kod do inicjowania, aktualizowania i zwracania player_stats_tables # dla każdego gracza. Definiuje również abstrakcyjną klasę stat_type, która będzie używana do aktualizacji statystyk, # oraz moduł StatType, który będzie używany podczas wyświetlania statystyk. using { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } # Zwrot player_stats_table dla podanego agenta. GetPlayerStats<public>(Agent:agent)<decides><transacts>:player_stats_table= var PlayerStats:player_stats_table = player_stats_table{} if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] set PlayerStats = MakePlayerStatsTable(PlayerStatsTable) PlayerStats # Zainicjuj statystyki dla wszystkich bieżących graczy. InitializeAllPlayerStats<public>(Players:[]player):void = for (Player : Players): InitializePlayerStat(Player) # Zainicjuj statystyki dla danego gracza. InitializePlayerStat<public>(Player:player):void= if: not PlayerStatsMap[Player] set PlayerStatsMap[Player] = player_stats_table{} else: Print("Nie można zainicjować statystyk gracza") # Dodaje do punktów danego agenta i aktualizuje tabele jego statystyk # w PlayerStatsManager i billboardzie w poziomie. AddPoints<public>(Agent:agent, NewPoints:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentPoints := PlayerStatsTable.Points set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Points := CurrentPoints + NewPoints else: Print("Nie można zarejestrować punktów gracza") # Dodaje do zwycięstw danego agenta i aktualizuje tabele jego statystyk # w PlayerStatsManager i billboardzie w poziomie. AddWin<public>(Agent:agent, NewWins:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentWins := PlayerStatsTable.Wins set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Wins := CurrentWins + NewWins else: Print("Nie można zarejestrować wygranych gracza")
Tworzenie tablic rankingowych graczy
Aby wyświetlić dane graczy na tablicy rankingowej, potrzebujesz kilku rzeczy. Musisz znaleźć sposób na aktualizowanie tekstu na billboardach oraz graczy w urządzeniach odwołania do gracza. Potrzebujesz również sposobu sortowania tych urządzeń, aby najlepsi gracze zajmowali szczytowe miejsca na tablicy rankingowej. Funkcje te mają podobny cel polegający na modyfikowaniu urządzeń w poziomie, dlatego dobrym rozwiązaniem jest zgrupowanie ich we wspólnym pliku.
Aby utworzyć funkcje, które będą aktualizowały urządzenia w poziomie, wykonaj poniższą procedurę:
-
Utwórz nowy plik Verse o nazwie player_leaderboards.verse. W tym pliku będą przechowywane funkcje stosowane wspólnie do aktualizacji tablic rankingowych w poziomie.
-
Do skonfigurowania tekstu na billboardzie użyjesz komunikatu, do którego można przekazywać argumenty. Utwórz nowy komunikat o nazwie
StatsMessage
, który będzie pobierałCurrentPlayer
,Points
iWins
typumessage
, a następnie zwracał połączony tekst jakomessage
.# Komunikat do wyświetlenia na billboardzie statystyk. StatsMessage<localizes>(CurrentPlayer:message, Points:message, Wins:message):message= "{CurrentPlayer}:\n{Points}\n{Wins}"
-
Dodaj jeszcze trzy zmienne
message
, po jednej dla każdej z danych wejściowych doStatsMessage
. KomunikatPlayerText
pobiera agentaAgent
, komunikatPointsText
pobiera punkty agenta, a komunikatWinsText
pobiera zwycięstwa agenta.StatsMessage
utworzy komunikat na podstawie wszystkich tych danych, aby czytelnie zaprezentować dane w poziomie.# Komunikat do wyświetlenia na billboardzie statystyk. StatsMessage<localizes>(CurrentPlayer:message, Points:message, Wins:message):message= "{CurrentPlayer}:\n{Points}\n{Wins}" PlayerText<localizes>(CurrentPlayer:agent):message = "Gracz {CurrentPlayer}" PointsText<localizes>(Points:int):message = "Łączna liczba punktów: {Points}" WinsText<localizes>(Wins:int):message = "Łączna liczba wygranych: {Wins}"
-
Aby zaktualizować billboard, wywołasz funkcję
UpdateStatsBillboard()
z samouczka dotyczącego persystencji danych statystyk graczy. Funkcja ta jest zdefiniowana w pliku odrębnym od pliku urządzenia Verse, dlatego musisz dodaćStatsBillboard
jako dodatkowy argument, aby wskazać, który billboard będzie aktualizowany.# Aktualizuje dane urządzenie billboardu, aby wyświetlało statystyki danego gracza. UpdateStatsBillboard<public>(Player:agent, StatsBillboard:billboard_device):void=
-
Najpierw pobierz statystyki gracza przekazane jako argument przy użyciu
GetPlayerStats[]
. Nie musisz się odwoływać doplayer_stats_manager
, ponieważ nie jest to już odrębna klasa. Następnie utwórz nowy komunikatStatsMessage
przy użyciu gracza oraz wartościPoints
iWins
z jegoCurrentPlayerStats
. Na końcu wywołajSetText()
dlaStatsBillboard
, aby zaktualizować tekst na billboardzie w poziomie. Gotowa funkcjaUpdateStatsBillboard()
powinna wyglądać następująco:# Aktualizuje dane urządzenie billboardu, aby wyświetlało statystyki danego gracza. UpdateStatsBillboard<public>(Player:agent, StatsBillboard:billboard_device):void= if: CurrentPlayerStats := GetPlayerStats[Player] then: PlayerStatsText := StatsMessage( PlayerText(Player), PointsText(CurrentPlayerStats.Points), WinsText(CurrentPlayerStats.Wins)) StatsBillboard.SetText(PlayerStatsText)
Sortowanie i wyświetlanie najlepszych graczy
Zanim przejdziesz dalej, zastanów się, w jaki sposób chcesz posortować te billboardy. Czy chcesz, aby na szczycie znalazł się gracz, który ma najwięcej punktów czy ten, który ma najwięcej zwycięstw? A może chcesz posortować graczy według innych statystyk? W każdym z tych przypadków potrzebujesz odpowiedniej metody, a odpowiedzią jest algorytm sortowania. Wykorzystując algorytm sortowania oraz funkcję porównawczą, możesz zdefiniować kryteria, według których będzie odbywać się sortowanie. Następnie posortuj billboardy i odwołania do graczy, aby wyświetlić najlepszych graczy ze swojej przygody. W tym przykładzie wykorzystano algorytm scalania i sortowania, jednak nic nie stoi na przeszkodzie, aby wdrożyć własny.
Wykonaj poniższą procedurę, aby dodać porównanie i sortowanie do swoich billboardów oraz zakończyć aktualizowanie urządzeń w poziomie.
-
W pliku
player_stats_table
zdefiniujesz funkcje porównania dla każdej ze statystyk. Każda z nich pobiera strukturę (struct)Left
iRight
player_and_stats
i porównuje je w oparciu o konkretną statystykę. Funkcje te posiadają modyfikatory<decides><transacts>
, więc jeśli porównanie się nie powiedzie, funkcja również się nie powiedzie. Na przykład poinformuje cię, żeLeft
jest mniejsze niżRight
. Dodaj nową funkcję o nazwieMorePointsComparison()
do pliku player_stats_table.verse. Ta funkcja sprawdza, czy
Left.Pointsjest większe niż
Right.Points, a jeśli nie jest, kończy się niepowodzeniem. W przypadku powodzenia zwraca
Left`.# Zwraca Left, jeśli Left ma więcej punktów niż Right. MorePointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:Left= Left.StatsTable.Points > Right.StatsTable.Points Lewa strona
-
Skopiuj tę funkcję trzy razy, raz dla porównania mniejszej liczby punktów i dwa razy dla porównania zwycięstw. Otrzymane funkcje powinny wyglądać następująco:
# Zwraca Left, jeśli Left ma więcej punktów niż Right. MorePointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points > Right.StatsTable.Points Lewa strona # Zwraca Left, jeśli Left ma mniej punktów niż Right. LessPointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points < Right.StatsTable.Points Lewa strona # Zwraca Left, jeśli Left ma większą liczbę podiów niż Right. MorePodiumsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points > Right.StatsTable.Points Lewa strona # Zwraca Left, jeśli Left ma mniejszą liczbę podiów niż Right. LessPodiumsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats= Left.StatsTable.Points < Right.StatsTable.Points Lewa strona
-
Dodaj algorytm sortowania przez scalanie. Możesz umieścić kod w osobnym pliku lub module i przetestować algorytm na dostarczonym pliku testowym.
-
W pliku
player_leaderboards
dodaj nową funkcjęUpdateStatsBillboards()
. Ta funkcja pobiera tablicę agentów i tablicę billboardów, sortuje je i wywołujeUpdateStatsBillboard()
, aby zaktualizować każdy billboard w poziomie.# Zaktualizuj billboardy statystyk, sortując je na podstawie liczby punktów życia # każdego gracza. UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void=
-
W funkcji
UpdateStatsBillboards()
zainicjuj nową zmienną tablicyplayer_and_stats
o nazwiePlayerAndStatsArray
. Ustaw wartość równą wynikowi wyrażeniafor
. W tym wyrażeniufor
dla każdegoagent
pobierzplayer
dla tegoagent
i pobierz jegoplayer_stats_table
, używającGetPlayerStats[]
. Następnie zwróć strukturęplayer_and_stats
zbudowaną zplayer
i jego tabeli statystyk.UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void= var PlayerAndStatsArray:[]player_and_stats = for: Agent:Players Player := player[Agent] PlayerStats := GetPlayerStats[Player] do: player_and_stats: Player := Player StatsTable := PlayerStats
-
Aby posortować tablicę
PlayerAndStatsArray
, zainicjuj nową zmiennąSortedPlayersAndStats
z zastosowaniem wyniku wywołaniaMergeSort()
, przekazując tablicę iMorePointsComparison
. Po posortowaniu w wyrażeniufor
, wykonaj iterację przez każdy element wSortedPlayerAndStats
, przechowując indeks elementu w zmiennejPlayerIndex
. UżyjPlayerIndex
do przeprowadzenia indeksowania do tablicyStatsBillboards
, a następnie wywołajUpdateStatsBillboard
, przekazując gracza i billboard do aktualizacji. Gotowa funkcjaUpdateStatsBillboards()
powinna wyglądać następująco.# Zaktualizuj billboardy statystyk, sortując je na podstawie liczby punktów życia # każdego gracza. UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void= var PlayerAndStatsArray:[]player_and_stats = for: Agent:Players Player := player[Agent] PlayerStats := GetPlayerStats[Player] do: player_and_stats: Player := Player StatsTable := PlayerStats # Porównaj i posortuj graczy na podstawie ich łącznej liczby punktów, która jest ogólnym wyznacznikiem # "najlepszego" gracza w lobby. Możesz tutaj zamienić funkcję porównywania, aby dopasować ją # do wymagań swojej przygody. SortedPlayersAndStats := SortingAlgorithms.MergeSort( PlayerAndStatsArray, MorePointsComparison) for: PlayerIndex -> PlayerAndStats : SortedPlayersAndStats StatsBillboard := StatsBillboards[PlayerIndex] do: UpdateStatsBillboard(PlayerAndStats.Player, StatsBillboard)
-
Aby zaktualizować odwołania do gracza, użyjesz bardzo podobnej funkcji o nazwie
UpdatePlayerReferences()
. Ta funkcja pobiera tablicęplayer_reference_device
zamiast billboardów, a zamiast wywoływaćUpdateStatsBillboard()
na końcu, wywołujeRegister()
na urządzeniu odwołania do gracza dla każdego gracza. Skopiuj kod funkcjiUpdateStatsBillboard()
do nowej funkcjiUpdatePlayerReferences()
z zastosowaniem powyższych zmian. Gotowa funkcjaUpdatePlayerReferences()
powinna wyglądać następująco:# Zaktualizuj urządzenia odwołania do gracza, sortując je na podstawie liczby # punktów życia każdego gracza. UpdatePlayerReferences<public>(Players:[]player, PlayerReferences:[]player_reference_device):void= var PlayerAndStatsArray:[]player_and_stats = for: Agent:Players Player := player[Agent] PlayerStats := GetPlayerStats[Player] do: player_and_stats: Player := Player StatsTable := PlayerStats # Porównaj i posortuj graczy na podstawie ich łącznej liczby punktów, która jest ogólnym wyznacznikiem # "najlepszego" gracza w lobby. Możesz tutaj zamienić funkcję porównywania, aby dopasować ją # do wymagań swojej przygody. SortedPlayersAndStats := SortingAlgorithms.MergeSort( PlayerAndStatsArray, MorePointsComparison) for: PlayerIndex -> PlayerAndStats : SortedPlayersAndStats PlayerReference := PlayerReferences[PlayerIndex] do: PlayerReference.Register(PlayerAndStats.Player)
Rankingi graczy w twoim poziomie
Wszystko gotowe, więc czas zaprezentować swoich graczy! Utworzysz urządzenie do przyznawania punktów graczom za interakcję z przyciskiem oraz posortujesz odwołania do graczy i billboardy tak, aby najlepsi gracze byli z przodu i na środku. Aby utworzyć urządzenie Verse do testowania rankingów w poziomie, wykonaj poniższe kroki:
-
Utwórz nowe urządzenie Verse o nazwie player_leaderboards_example. Procedurę zawiera sekcja Tworzenie własnego urządzenia przy użyciu Verse.
-
Na początku definicji klasy
player_leaderboards_example class
dodaj następujące pola:-
Edytowalna tablica urządzeń odwołania do graczy o nazwie
PlayerReferences
. Zapewniają one wizualną reprezentację każdego gracza w wyścigu.# Wizualna reprezentacja każdego gracza. @editable PlayerReferences:[]player_reference_device = array{}
-
Edytowalna tablica urządzeń billboardu o nazwie
Leaderboards
. Wyświetlają one statystyki każdego gracza na billboardzie w poziomie.# Billboardy wyświetlające statystyki każdego gracza. @editable Leaderboards:[]billboard_device = array{}
-
Edytowalne urządzenie menedżera wyścigu o nazwie
RaceManager
. Będziesz subskrybować zdarzenia z menedżera wyścigu, aby wiedzieć, kiedy gracz ukończy wyścig.# Śledzi, kiedy gracze kończą wyścig, a gracze na pierwszym miejscu odnoszą zwycięstwo. @editable RaceManager:race_manager_device = race_manager_device{}
-
Edytowalna liczba całkowita o nazwie
PlacementRequiredForWin
. Jest to miejsce, na którym gracz musi się znaleźć, aby odnieść zwycięstwo.# Wartość miejsca gracza musi być równa podanej lub niższa, aby można było przyznać mu zwycięstwo. @editable PlacementRequiredForWin:int = 1
-
Edytowalna tablica liczb całkowitych o nazwie
PointsPerPlace
. Jest to liczba punktów, które każdy gracz zdobywa na podstawie zajętego miejsca.# Liczba punktów zdobywanych przez graczy za zajęcie poszczególnych miejsc. # Dostosuj tę opcję, aby przyznać graczom żądaną liczbę punktów # w oparciu o zajęte przez nich miejsce. @editable PointsPerPlace:[]int = array{5, 3, 1}
-
Zmienna z liczbą całkowitą o nazwie
CurrentFinishOrder
. Jest to miejsce gracza, który ostatnio ukończył wyścig.# Miejsce gracza, który właśnie ukończył wyścig. # Zwycięstwo zostanie przyznane trzem pierwszym graczom, którzy ukończą wyścig. var CurrentFinishOrder:int = 0
-
Przyznawanie statystyk na podstawie zajętego miejsca
Gdy gracz ukończy wyścig, zaktualizujesz jego statystyki w oparciu o zajęte miejsce. Gracze, którzy zajęli wysokie miejsca, powinni otrzymać większą liczbę punktów, a gracze, którzy zajęli najlepsze miejsca, powinni odnieść zwycięstwo.
Postępuj zgodnie z poniższą instrukcją, aby przyznać graczom statystyki po ukończeniu wyścigu:
-
Aby to zrobić, dodaj nową funkcję
RecordPlayerFinish()
do definicji klasyplayer_leaderboards_example
. Ta funkcja przyjmuje jako parametr gracza, któremu chcesz przyznać statystyki.# Gdy gracz ukończy wyścig, przyznaj mu punkty w oparciu o zajęte miejsce i przyznaj mu zwycięstwo, # jeśli jego miejsce było lepsze niż PlacementRequiredForWin. RecordPlayerFinish(Player:agent):void=
-
W
RecordPlayerFinish()
pobierz miejsce tego gracza, pobierając bieżącą wartośćCurrentFinishOrder
w nowejint
o nazwiePlayerFinishOrder
. Następnie zastosuj przyrost doCurrentFinishOrder
, aby następny gracz, który ukończy wyścig, nie ukończył go na tym samym miejscu.RecordPlayerFinish(Player:agent):void= PlayerFinishOrder:int = CurrentFinishOrder set CurrentFinishOrder += 1
-
Teraz czas na przyznanie statystyk. Aby określić, ile punktów przyznać temu graczowi, w wyrażeniu
if
przeprowadź indeksację do tablicyPointsPerPlace
, używającPlayerFinishOrder
. Następnie wywołaj funkcjęAddPoints()
, aby przyznać graczowi taką liczbę punktów.set CurrentFinishOrder += 1 if: PointsToAward := PointsPerPlace[PlayerFinishOrder] then: AddPoints(Player, PointsToAward)
-
Jeśli gracz zajął wystarczająco wysokie miejsce, aby wygrać, musisz odnotować wygraną w jego tabeli statystyk. W kolejnym wyrażeniu
if
sprawdź, czyPlayerFinishOrder
było mniejsze niżPlacementRequiredToWin
. Jeśli tak, wywołajAddWin()
, przekazując gracza i wygraną, aby mu ją przyznać. Gotowa funkcjaRecordPlayerFinish()
powinna wyglądać następująco:# Gdy gracz ukończy wyścig, przyznaj mu punkty w oparciu o zajęte miejsce i przyznaj mu zwycięstwo, # jeśli jego miejsce było lepsze niż PlacementRequiredForWin. RecordPlayerFinish(Player:agent):void= PlayerFinishOrder:int = CurrentFinishOrder set CurrentFinishOrder += 1 if: PointsToAward := PointsPerPlace[PlayerFinishOrder] then: AddPoints(Player, PointsToAward) # Jeśli gracz ukończył wyścig na miejscu o numerze niższym lub równym PlacementRequiredToWin, # przyznaj mu zwycięstwo i zapisz je w jego tabeli player_stats_table. if: PlayerFinishOrder < PlacementRequiredForWin then: AddWin(Player, 1)
Oczekiwanie na zakończenie wyścigu przez graczy
Teraz, gdy masz już gotowe rejestrowanie statystyk, musisz wiedzieć, kiedy gracz ukończy wyścig, aby zaktualizować jego statystyki. W tym celu będziesz nasłuchiwać komunikatów menadżera wyścigu RaceCompletedEvent()
. To zdarzenie jest uruchamiane za każdym razem, gdy którykolwiek z graczy ukończy wyścig, więc musisz stale nasłuchiwać go w funkcji asynchronicznej.
-
Dodaj nową funkcję
WaitForPlayerToFinishRace()
do definicji klasyplayer_leaderboards_example
. Ta funkcja pobiera gracza i czeka, aż ten gracz ukończy wyścig.# Gdy gracz ukończy wyścig, odnotuj ukończenie w jego tabeli statystyk. WaitForPlayerToFinishRace(Player:agent)<suspends>:void=
-
W funkcji
WaitForPlayerToFinishRace()
, w wyrażeniurace
rozpocznij dwie pętle. Pierwsza z nich będzie czekać, aż gracz ukończy wyścig, a druga zajmie się tym, co się wydarzy, jeśli gracz opuści sesję przed ukończeniem wyścigu. Jeśli gracz opuści grę, nie chcesz, aby pętla działała w nieskończoność, więc potrzebujesz sposobu na przerwanie jej w takiej sytuacji.# Gdy gracz ukończy wyścig, odnotuj ukończenie w jego tabeli statystyk. WaitForPlayerToFinishRace(Player:agent)<suspends>:void= race: # Poczekaj, aż ten gracz ukończy wyścig, a następnie zarejestruj ukończenie. loop: # Poczekaj, aż ten gracz opuści grę. loop:
-
W pierwszej pętli oczekuj na zdarzenie
RaceManager.RaceCompletedEvent
i zapisz wynik w zmiennej o nazwieFinishingPlayer
. Zdarzenie to jest uruchamiane za każdym razem, gdy dowolny gracz ukończy wyścig, dlatego musisz się upewnić, że zapisany gracz jest tym, którego monitorujesz. PorównajFinishingPlayer
z graczem, którego monitoruje ta pętla. Jeśli oba elementy są równe, przekaż gracza doRecordPlayerFinish()
i przerwij pętlę.# Poczekaj, aż ten gracz ukończy wyścig, a następnie zarejestruj ukończenie. loop: FinishingPlayer := RaceManager.RaceCompletedEvent.Await() if: FinishingPlayer = Player then: RecordPlayerFinish(Player) break
-
W drugiej pętli zaczekaj na zdarzenie przestrzeni gry
PlayerRemovedEvent()
. Tak jak poprzednio, pobierz gracza, który właśnie opuścił grę, i zapisz go w zmiennejLeavingPlayer
. Jeśli gracz, który właśnie opuścił grę, jest graczem, na którego czeka ta pętla, przerwij ją. Gotowa funkcjaWaitForPlayerToFinishRace()
powinna wyglądać następująco:# Gdy gracz ukończy wyścig, odnotuj ukończenie w jego tabeli statystyk. WaitForPlayerToFinishRace(Player:agent)<suspends>:void= race: # Poczekaj, aż ten gracz ukończy wyścig, a następnie zarejestruj ukończenie. loop: FinishingPlayer := RaceManager.RaceCompletedEvent.Await() if: FinishingPlayer = Player then: RecordPlayerFinish(Player) break # Poczekaj, aż ten gracz opuści grę. loop: LeavingPlayer := GetPlayspace().PlayerRemovedEvent().Await() if: LeavingPlayer = Player then: break
Łączenie wszystkiego razem
Po skonfigurowaniu funkcji nadszedł czas, aby połączyć je z urządzeniami i rozpocząć wyścig!
Postępuj zgodnie z poniższą instrukcją, aby połączyć logikę z urządzeniami:
-
W funkcji
OnBegin()
pobierz wszystkich graczy w przestrzeni gry za pomocąGetPlayers()
. Przekaż tę tablicę doInitializeAllPlayerStats()
, aby skonfigurowaćplayer_stats_tables
dla każdego z nich.# Działa po uruchomieniu urządzenia w aktywnej grze OnBegin<override>()<suspends>:void= # Pobierz graczy w bieżącym wyścigu i utwórz player_stat_table # dla każdego z nich. Players := GetPlayspace().GetPlayers() InitializeAllPlayerStats(Players)
-
Wywołaj
UpdateStatsBillboards()
, przekazując tablicePlayers
iLeaderboards
, aby zaktualizować billboardy w poziomie o aktualne dane każdego gracza. Następnie wywołajUpdatePlayerReferences()
, aby zaktualizować odwołania do graczy w poziomie. Na koniec w wyrażeniufor
zespawnuj funkcjęWaitForPlayerToFinishRace()
dla każdego gracza. Gotowa funkcjaOnBegin()
powinna wyglądać następująco:# Działa po uruchomieniu urządzenia w aktywnej grze OnBegin<override>()<suspends>:void= # Pobierz graczy w bieżącym wyścigu i utwórz player_stat_table # dla każdego z nich. Players := GetPlayspace().GetPlayers() InitializeAllPlayerStats(Players) UpdateStatsBillboards(Players, Leaderboards) UpdatePlayerReferences(Players, PlayerReferences) # Zaczekaj na ukończenie wyścigu przez wszystkich graczy. for: Player:Players do: spawn{WaitForPlayerToFinishRace(Player)}
-
Zapisz kod i skompiluj go.
Przeciągnij urządzenie player_leaderboards_example do swojego poziomu. Przypisz odwołania do graczy do tablicy PlayerReferences, zwracając uwagę na kolejność. Urządzenie w pierwszym indeksie powinno odpowiadać odwołaniu do gracza dla najlepszego gracza, drugi indeks powinien być powiązany z drugim z kolei najlepszym graczem itd. Zrób to samo z rankingami, upewniając się, że są one zsynchronizowane z urządzeniami odwołań do graczy. Nie zapomnij również przypisać swojego urządzenia menedżera wyścigu!

Testowanie persystentnych tablic rankingowych
Możesz przetestować trwałe dane w sesji edycji, ale dane te zostaną zresetowane po wyjściu i ponownym uruchomieniu sesji. Aby dane między sesjami były utrwalone, musisz uruchomić sesję testu gry i zmienić pewne ustawienia w swoich Ustawieniach wyspy. Aby uzyskać informacje na temat konfigurowania wyspy w celu testowania persystentnych danych zarówno w sesjach edycji, jak i testów gry, zapoznaj się z artykułem Testowanie z persystencją danych i zmień niektóre ustawienia wyspy. Aby uzyskać informacje na temat konfigurowania wyspy w celu testowania persystentnych danych zarówno w sesjach edycji, jak i testów gry, zapoznaj się z artykułem Testowanie z persystencją danych.
Po skonfigurowaniu sesji, podczas testowania twojego poziomu w grze, gracze kończący wyścig powinni otrzymywać punkty w zależności od zajętego miejsca. Powinni oni zdobyć zwycięstwo, jeśli zajmą wystarczająco wysokie miejsce, a statystyki te powinny być zachowywane przy kolejnych sesjach gry. Gracze i ich statystyki powinny być posortowane, a gracz z największą liczbą punktów powinien znajdować się na pierwszym miejscu.

We własnym zakresie
Dzięki tej instrukcji wiesz już, jak stworzyć tablice rankingowe, które wyświetlają trwałe statystyki graczy w poziomie. Wiesz także, jak sortować i aktualizować tablice rankingowe, aby każdy wiedział, kto jest najlepszym graczem. Spróbuj dostosować ten samouczek do własnych przygód i pochwal się najlepszymi graczami!
Kompletny kod
player_stats_table.verse
# Ten plik definiuje player_stats_table, kolekcję persystentnych statystyk gracza.
# Zawiera również funkcje do porównywania tabel statystyk według każdej z nich, aby uporządkować graczy
# podczas sortowania.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Struktura do przekazywania gracza i jego statystyk jako argumentów.
player_and_stats<public> := struct:
Player<public>:player
StatsTable<public>:player_stats_table
# Śledzi różne persystentne statystyki dla każdego gracza.
player_stats_table<public>:= class<final><persistable>:
# Wersja tabeli aktualnych statystyk.
Version<public>:int = 0
# Punkty gracza.
Points<public>:int = 0
# Liczba wygranych gracza.
Wins<public>:int = 0
# Zwraca Left, jeśli Left ma więcej punktów niż Right.
MorePointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points > Right.StatsTable.Points
Lewa strona
# Zwraca Left, jeśli Left ma mniej punktów niż Right.
LessPointsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points < Right.StatsTable.Points
Lewa strona
# Zwraca Left, jeśli Left ma większą liczbę wygranych niż Right.
MoreWinsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points > Right.StatsTable.Points
Lewa strona
# Zwraca Left, jeśli Left ma mniejszą liczbę wygranych niż Right.
LessWinsComparison<public>(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points < Right.StatsTable.Points
Lewa strona
# Zwraca Left, jeśli Left ma dłuższy czas BestLapTime niż Right.
# Zwróć uwagę, że jest to odwrotność innych statystyk, ponieważ im krótszy czas okrążenia, tym lepiej.
SlowerLapTimeComparison(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points > Right.StatsTable.Points
Lewa strona
# Zwraca Left, jeśli Left ma krótszy czas BestLapTime niż Right.
# Zwróć uwagę, że jest to odwrotność innych statystyk, ponieważ im krótszy czas okrążenia, tym lepiej.
FasterLapTimeComparison(Left:player_and_stats, Right:player_and_stats)<decides><transacts>:player_and_stats=
Left.StatsTable.Points < Right.StatsTable.Points
Lewa strona
# Tworzy nową player_stats_table z takimi samymi wartościami, jak poprzednia player_stats_table.
MakePlayerStatsTable<constructor>(OldTable:player_stats_table)<transacts> := player_stats_table:
Version := OldTable.Version
Points := OldTable.Points
Wins := OldTable.Wins
# Mapuje graczy do tabeli odpowiednich statystyk gracza.
var PlayerStatsMap:weak_map(player, player_stats_table) = map{}
player_leaderboards.verse
# Ten plik zawiera kod, który aktualizuje billboardy, odwołania do graczy i UI na wyspie,
# aby wyświetlać statystyki gracza z jego tabeli statystyk. Pozwala również dodawać zwycięstwa i punkty
# do tabeli statystyk gracza.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { PlayerStatistics }
# Komunikat do wyświetlenia na billboardzie statystyk.
StatsMessage<localizes>(CurrentPlayer:message, Points:message, Wins:message):message=
"{CurrentPlayer}:\n{Points}\n{Wins}"
PlayerText<localizes>(CurrentPlayer:agent):message = "Gracz {CurrentPlayer}"
PointsText<localizes>(Points:int):message = "Łączna liczba punktów: {Points}"
WinsText<localizes>(Wins:int):message = "Łączna liczba wygranych: {Wins}"
# Aktualizuje dane urządzenie billboardu, aby wyświetlało statystyki danego gracza.
UpdateStatsBillboard<public>(Player:agent, StatsBillboard:billboard_device):void=
if:
CurrentPlayerStats := GetPlayerStats[Player]
then:
PlayerStatsText := StatsMessage(
PlayerText(Player),
PointsText(CurrentPlayerStats.Points),
WinsText(CurrentPlayerStats.Wins))
StatsBillboard.SetText(PlayerStatsText)
# Zaktualizuj billboardy statystyk, sortując je na podstawie liczby punktów życia
# każdego gracza.
UpdateStatsBillboards<public>(Players:[]agent, StatsBillboards:[]billboard_device):void=
var PlayerAndStatsArray:[]player_and_stats =
for:
Agent:Players
Player := player[Agent]
PlayerStats := GetPlayerStats[Player]
do:
player_and_stats:
Player := Player
StatsTable := PlayerStats
# Porównaj i posortuj graczy na podstawie ich łącznej liczby punktów, która jest ogólnym wyznacznikiem
# "najlepszego" gracza w lobby. Możesz tutaj zamienić funkcję porównywania, aby dopasować ją
# do wymagań swojej przygody.
SortedPlayersAndStats := SortingAlgorithms.MergeSort(
MorePointsComparison,
PlayerAndStatsArray)
for:
PlayerIndex -> PlayerAndStats : SortedPlayersAndStats
StatsBillboard := StatsBillboards[PlayerIndex]
do:
UpdateStatsBillboard(PlayerAndStats.Player, StatsBillboard)
# Zaktualizuj urządzenia odwołania do gracza, sortując je na podstawie liczby
# punktów życia każdego gracza.
UpdatePlayerReferences<public>(Players:[]player, PlayerReferences:[]player_reference_device):void=
var PlayerAndStatsArray:[]player_and_stats =
for:
Agent:Players
Player := player[Agent]
PlayerStats := GetPlayerStats[Player]
do:
player_and_stats:
Player := Player
StatsTable := PlayerStats
# Porównaj i posortuj graczy na podstawie ich łącznej liczby punktów, która jest ogólnym wyznacznikiem
# "najlepszego" gracza w lobby. Możesz tutaj zamienić funkcję porównywania, aby dopasować ją
# do wymagań swojej przygody.
SortedPlayersAndStats := SortingAlgorithms.MergeSort(
MorePointsComparison,
PlayerAndStatsArray)
for:
PlayerIndex -> PlayerAndStats : SortedPlayersAndStats
PlayerReference := PlayerReferences[PlayerIndex]
do:
PlayerReference.Register(PlayerAndStats.Player)
player_stats_manager.verse
# Ten plik obsługuje kod do inicjowania, aktualizowania i zwracania player_stats_tables
# dla każdego gracza. Definiuje również abstrakcyjną klasę stat_type, która będzie używana do aktualizacji statystyk,
# oraz moduł StatType, który będzie używany podczas wyświetlania statystyk.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Zwrot player_stats_table dla podanego agenta.
GetPlayerStats<public>(Agent:agent)<decides><transacts>:player_stats_table=
var PlayerStats:player_stats_table = player_stats_table{}
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
set PlayerStats = MakePlayerStatsTable(PlayerStatsTable)
PlayerStats
# Zainicjuj statystyki dla wszystkich bieżących graczy.
InitializeAllPlayerStats<public>(Players:[]player):void =
for (Player : Players):
InitializePlayerStat(Player)
# Zainicjuj statystyki dla danego gracza.
InitializePlayerStat<public>(Player:player):void=
if:
not PlayerStatsMap[Player]
set PlayerStatsMap[Player] = player_stats_table{}
else:
Print("Nie można zainicjować statystyk gracza")
# Dodaje do StatToAdd danego Agenta i aktualizuje tabele jego statystyk
# w PlayerStatsManager i billboardzie w poziomie.
AddPoints<public>(Agent:agent, NewPoints:int):void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
CurrentPoints := PlayerStatsTable.Points
set PlayerStatsMap[Player] = player_stats_table:
MakePlayerStatsTable<constructor>(PlayerStatsTable)
Points := CurrentPoints + NewPoints
else:
Print("Nie można zarejestrować punktów gracza")
# Dodaje do StatToAdd danego Agenta i aktualizuje tabele jego statystyk
# w PlayerStatsManager i billboardzie w poziomie.
AddWin<public>(Agent:agent, NewWins:int):void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
CurrentWins := PlayerStatsTable.Wins
set PlayerStatsMap[Player] = player_stats_table:
MakePlayerStatsTable<constructor>(PlayerStatsTable)
Wins := CurrentWins + NewWins
else:
Print("Nie można zarejestrować wygranych gracza")
player_leaderboards_example.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { PlayerStatistics }
using { PlayerLeaderboard }
# Odwiedź stronę: https://dev.epicgames.com/documentation/pl-pl/uefn/create-your-own-device-in-verse, aby dowiedzieć się, jak utworzyć urządzenie Verse.
# Urządzenie trybu kreatywnego utworzone w Verse, które można umieścić w poziomie
player_leaderboards_example := class(creative_device):
# Wizualna reprezentacja każdego gracza.
@editable
PlayerReferences:[]player_reference_device = array{}
# Billboardy wyświetlające statystyki każdego gracza.
@editable
Leaderboards:[]billboard_device = array{}
# Śledzi, kiedy gracze kończą wyścig, a gracze na pierwszym miejscu odnoszą zwycięstwo.
@editable
RaceManager:race_manager_device = race_manager_device{}
# Wartość miejsca gracza musi być równa podanej lub niższa, aby można było przyznać mu zwycięstwo.
@editable
PlacementRequiredForWin:int = 1
# Liczba punktów zdobywanych przez graczy za zajęcie poszczególnych miejsc.
# Dostosuj tę opcję, aby przyznać graczom żądaną liczbę punktów
# w oparciu o zajęte przez nich miejsce.
@editable
PointsPerPlace:[]int = array{5, 3, 1}
# Miejsce gracza, który właśnie ukończył wyścig.
# Zwycięstwo zostanie przyznane trzem pierwszym graczom, którzy ukończą wyścig.
var CurrentFinishOrder:int = 0
# Działa po uruchomieniu urządzenia w aktywnej grze
OnBegin<override>()<suspends>:void=
# Pobierz graczy w bieżącym wyścigu i utwórz player_stat_table
# dla każdego z nich.
Players := GetPlayspace().GetPlayers()
InitializeAllPlayerStats(Players)
UpdateStatsBillboards(Players, Leaderboards)
UpdatePlayerReferences(Players, PlayerReferences)
# Zaczekaj na ukończenie wyścigu przez wszystkich graczy.
for:
Player:Players
do:
spawn{WaitForPlayerToFinishRace(Player)}
# Gdy gracz ukończy wyścig, odnotuj ukończenie w jego tabeli statystyk.
WaitForPlayerToFinishRace(Player:agent)<suspends>:void=
race:
# Poczekaj, aż ten gracz ukończy wyścig, a następnie zarejestruj ukończenie.
loop:
FinishingPlayer := RaceManager.RaceCompletedEvent.Await()
if:
FinishingPlayer = Player
then:
RecordPlayerFinish(Player)
break
# Poczekaj, aż ten gracz opuści grę.
loop:
LeavingPlayer := GetPlayspace().PlayerRemovedEvent().Await()
if:
LeavingPlayer = Player
then:
break
# Gdy gracz ukończy wyścig, przyznaj mu punkty w oparciu o zajęte miejsce i przyznaj mu zwycięstwo,
# jeśli jego miejsce było lepsze niż PlacementRequiredForWin.
RecordPlayerFinish(Player:agent):void=
PlayerFinishOrder:int = CurrentFinishOrder
set CurrentFinishOrder += 1
if:
PointsToAward := PointsPerPlace[PlayerFinishOrder]
then:
AddPoints(Player, PointsToAward)
# Jeśli gracz ukończył wyścig na miejscu o numerze niższym lub równym PlacementRequiredToWin,
# przyznaj mu zwycięstwo i zapisz je w jego tabeli player_stats_table.
if:
PlayerFinishOrder < PlacementRequiredForWin
then:
AddWin(Player, 1)