Pracuję nad izometryczną grą 2D z umiarkowaną skalą dla wielu graczy, około 20-30 graczy jednocześnie połączonych z trwałym serwerem. Miałem pewne trudności z wdrożeniem dobrej implementacji przewidywania ruchu.
Fizyka / ruch
Gra nie ma prawdziwej implementacji fizyki, ale wykorzystuje podstawowe zasady do implementacji ruchu. Zamiast ciągłego sprawdzania danych wejściowych zmiany stanu (tj. / Zdarzenia myszy w dół / w górę / ruch) są używane do zmiany stanu jednostki postaci, którą kontroluje gracz. Kierunek gracza (tj. / Północny-wschód) jest łączony ze stałą prędkością i zamieniany w prawdziwy wektor 3D - prędkość istoty.
W głównej pętli gry przed aktualizacją nazywa się „Aktualizacja”. Logika aktualizacji uruchamia „zadanie aktualizacji fizyki”, która śledzi wszystkie byty z niezerową prędkością, wykorzystuje bardzo podstawową integrację do zmiany pozycji bytów. Na przykład: entity.Position + = entity.Velocity.Scale (ElapsedTime.Seconds) (gdzie „Seconds” jest wartością zmiennoprzecinkową, ale to samo podejście działałoby w przypadku wartości całkowitych milisekundowych).
Kluczową kwestią jest to, że do ruchu nie stosuje się interpolacji - podstawowy silnik fizyki nie ma pojęcia „poprzedniego stanu” lub „bieżącego stanu”, a jedynie pozycję i prędkość.
Pakiety zmiany stanu i aktualizacji
Gdy prędkość jednostki postaci kontrolowanej przez gracza zmienia się, do serwera wysyłany jest pakiet „awatar ruchu” zawierający typ akcji istoty (stan, chód, bieg), kierunek (północno-wschodni) i aktualną pozycję. Różni się to od działania gier 3D w pierwszej osobie. W grze 3D prędkość (kierunek) może zmieniać się klatka po klatce, gdy gracz się porusza. Wysłanie każdej zmiany stanu skutecznie przesłałoby pakiet na ramkę, co byłoby zbyt drogie. Zamiast tego gry 3D wydają się ignorować zmiany stanu i wysyłają pakiety „aktualizacji stanu” w ustalonych odstępach czasu - powiedzmy, co 80-150 ms.
Ponieważ aktualizacje prędkości i kierunku występują znacznie rzadziej w mojej grze, mogę uniknąć wysyłania każdej zmiany stanu. Chociaż wszystkie symulacje fizyki odbywają się z tą samą prędkością i są deterministyczne, opóźnienie jest nadal problemem. Z tego powodu wysyłam rutynowe pakiety aktualizacji pozycji (podobne do gry 3D), ale znacznie rzadziej - teraz co 250 ms, ale podejrzewam, że z dobrą prognozą mogę z łatwością zwiększyć je do 500 ms. Największy problem polega na tym, że odeszłam od normy - cała inna dokumentacja, przewodniki i próbki online wysyłają rutynowe aktualizacje i interpolują oba te stany. Wydaje się to niezgodne z moją architekturą i muszę wymyślić lepszy algorytm przewidywania ruchu, który jest bliższy (bardzo podstawowej) architekturze „fizyki sieciowej”.
Serwer następnie odbiera pakiet i określa prędkość gracza na podstawie jego typu ruchu na podstawie skryptu (Czy gracz jest w stanie uruchomić? Uzyskaj prędkość biegania gracza). Gdy osiągnie prędkość, łączy ją z kierunkiem uzyskania wektora - prędkości bytu. Występuje pewne wykrywanie oszustw i podstawowe sprawdzanie poprawności, a jednostka po stronie serwera jest aktualizowana o bieżącą prędkość, kierunek i pozycję. Podstawowe dławienie jest również wykonywane, aby uniemożliwić graczom zalanie serwera żądaniami ruchu.
Po zaktualizowaniu własnego bytu serwer rozgłasza pakiet „aktualizacja pozycji awatara” do wszystkich innych graczy w zasięgu. Pakiet aktualizacji pozycji służy do aktualizacji symulacji fizyki po stronie klienta (stan świata) zdalnych klientów oraz do wykonywania prognoz i kompensacji opóźnień.
Prognozowanie i kompensacja opóźnień
Jak wspomniano powyżej, klienci są wiarygodni dla swojej pozycji. Z wyjątkiem przypadków oszukiwania lub anomalii, awatar klienta nigdy nie zostanie zmieniony przez serwer. Awatar klienta nie wymaga ekstrapolacji („przenieś teraz i popraw później”) - to, co gracz widzi, jest poprawne. Jednak dla wszystkich poruszających się jednostek zdalnych wymagana jest pewna ekstrapolacja lub interpolacja. Pewne przewidywanie i / lub kompensacja opóźnień jest wyraźnie wymagana w lokalnym silniku symulacji / fizyki klienta.
Problemy
Walczę z różnymi algorytmami i mam wiele pytań i problemów:
Czy powinienem ekstrapolować, interpolować, czy jedno i drugie? Moje „przeczucie” polega na tym, że powinienem stosować czystą ekstrapolację opartą na prędkości. Klient otrzymuje zmianę stanu, klient oblicza „przewidywaną” prędkość, która kompensuje opóźnienie, a normalny system fizyki zajmuje się resztą. Czuje się jednak w sprzeczności z innymi przykładowymi kodami i artykułami - wszystkie wydają się przechowywać szereg stanów i przeprowadzać interpolację bez silnika fizyki.
Kiedy nadchodzi pakiet, próbowałem interpolować pozycję pakietu z prędkością pakietu w określonym czasie (powiedzmy 200 ms). Następnie biorę różnicę między pozycją interpolowaną a bieżącą pozycją „błędu”, aby obliczyć nowy wektor i umieścić go na bycie zamiast prędkości, która została wysłana. Zakłada się jednak, że w tym przedziale czasowym nadejdzie kolejny pakiet i niezwykle trudno jest „zgadnąć”, kiedy nadejdzie następny pakiet - zwłaszcza, że nie wszystkie one docierają w ustalonych odstępach czasu (tj. Również zmiany stanu). Czy koncepcja jest zasadniczo wadliwa, czy jest poprawna, ale wymaga pewnych poprawek / korekt?
Co się stanie, gdy zdalny odtwarzacz się zatrzyma? Mogę natychmiast zatrzymać byt, ale będzie on ustawiony w „złym” miejscu, dopóki nie ruszy się ponownie. Jeśli oszacuję wektor lub spróbuję interpolować, mam problem, ponieważ nie zapisuję poprzedniego stanu - silnik fizyki nie może powiedzieć „musisz zatrzymać się po osiągnięciu pozycji X”. Po prostu rozumie prędkość, nic bardziej złożonego. Nie chcę dodawać informacji o stanie ruchu pakietu do encji lub silnika fizyki, ponieważ narusza to podstawowe zasady projektowania i upuszcza kod sieciowy w pozostałej części silnika gry.
Co powinno się stać, gdy byty zderzą się? Istnieją trzy scenariusze - kontrolujący gracz zderza się lokalnie, dwa byty zderzają się na serwerze podczas aktualizacji pozycji lub zdalna aktualizacja bytu zderza się z lokalnym klientem. We wszystkich przypadkach nie jestem pewien, jak poradzić sobie z kolizją - oprócz oszukiwania oba stany są „poprawne”, ale w różnych okresach. W przypadku jednostki zdalnej nie ma sensu rysować jej przechodząc przez ścianę, dlatego wykonuję wykrywanie kolizji na lokalnym kliencie i powoduję, że „zatrzymuje się”. W oparciu o punkt 2 powyżej mogę obliczyć „poprawiony wektor”, który nieustannie próbuje przenieść byt „przez ścianę”, co nigdy się nie powiedzie - zdalny awatar utknie tam, dopóki błąd nie stanie się zbyt wysoki i „zaskoczy” pozycja. Jak działają gry?
źródło
Odpowiedzi:
Jedyną rzeczą do powiedzenia jest to, że 2D, izometryczny, 3D, wszystkie są takie same, jeśli chodzi o ten problem. Ponieważ widzisz wiele przykładów 3D i używasz jedynie systemu wprowadzania 2D z ograniczeniem oktantowym o natychmiastowej prędkości, nie oznacza to, że możesz wyrzucić zasady sieciowe, które ewoluowały przez ostatnie 20 lat.
Zasady projektowania niech będą przeklęte, gdy gra jest zagrożona!
Wyrzucając poprzednie i bieżące, odrzucasz kilka informacji, które mogłyby rozwiązać Twój problem. Do tych danych dodałbym znaczniki czasu i obliczone opóźnienie, aby ekstrapolacja mogła lepiej przewidzieć, gdzie będzie ten gracz, a interpolacja może lepiej wygładzić zmiany prędkości w czasie.
Powyższe jest głównym powodem, dla którego serwery wydają dużo informacji o stanie i nie kontrolują danych wejściowych. Innym ważnym powodem jest protokół, którego używasz. UDP z zaakceptowaną utratą pakietu i dostawą poza kolejnością? TCP z zapewnioną dostawą i ponownymi próbami? Z dowolnym protokołem będziesz otrzymywać pakiety w dziwnych momentach, opóźnione lub ułożone jeden na drugim w przypływie aktywności. Wszystkie te dziwne pakiety muszą pasować do kontekstu, aby klient mógł dowiedzieć się, co się dzieje.
Wreszcie, mimo że Twoje nakłady są bardzo ograniczone do 8 kierunków, faktyczna zmiana może nastąpić w dowolnym momencie - wymuszenie cyklu 250 ms po prostu frustruje szybkich graczy. 30 graczy to nic wielkiego do poradzenia sobie z jakimkolwiek serwerem. Jeśli mówisz o tysiącach ... nawet wtedy ich grupy są podzielone na wiele boxen, więc pojedyncze serwery przenoszą tylko rozsądne obciążenie.
Czy kiedykolwiek profilowałeś silnik fizyki, taki jak Havok lub Bullet? Są naprawdę bardzo zoptymalizowane i bardzo, bardzo szybkie. Możesz wpaść w pułapkę zakładając, że operacja ABC będzie powolna i optymalizuje coś, co jej nie potrzebuje.
źródło
Więc twój serwer jest w zasadzie „sędzią”? W tym przypadku uważam, że wszystko w twoim kliencie musi być deterministyczne; musisz upewnić się, że wszystko na każdym kliencie zawsze da taki sam rezultat.
Jeśli chodzi o twoje pierwsze pytanie, gdy lokalny gracz osiągnie kierunek innych graczy, poza tym, że będzie w stanie spowolnić swój ruch w czasie i zastosować kolizje, nie widzę, w jaki sposób można przewidzieć, w którym kierunku następny gracz się obróci, szczególnie w 8-kierunkowe środowisko.
Po otrzymaniu aktualizacji „rzeczywistej pozycji” każdego gracza (że być może możesz spróbować zataczania się na serwerze) tak, będziesz musiał interpolować pozycję i kierunek gracza. Jeśli pozycja „zgadnięta” jest bardzo błędna (tj. Gracz całkowicie zmienił kierunek zaraz po wysłaniu pakietu z ostatnim kierunkiem), będziesz miał ogromną lukę. Oznacza to, że albo gracz przeskakuje na pozycję, albo możesz interpolować do następnej odgadniętej pozycji. Zapewni to płynniejszą interpolację w czasie.
Gdy byty zderzają się, jeśli możesz stworzyć system deterministyczny, każdy gracz może symulować kolizję lokalnie, a ich wyniki nie powinny być zbyt dalekie od rzeczywistości. Każda lokalna maszyna powinna symulować kolizję dla obu graczy, w takim przypadku upewniając się, że stan końcowy będzie nieblokujący i akceptowalny.
Po stronie serwera serwer sędziowski może nadal wykonywać proste obliczenia, aby na przykład sprawdzić prędkość gracza w krótkim czasie i wykorzystać go jako prosty mechanizm zapobiegający oszukiwaniu. Jeśli przejdziesz przez monitorowanie każdego gracza przez 1 s na raz, wykrywanie oszustów będzie skalowalne, ale znalezienie oszustów potrwa dłużej.
źródło
Czy nie możesz uwzględnić prędkości w komunikatach o zmianie stanu i użyć jej do przewidywania ruchu? np. zakładasz, że prędkość się nie zmienia, dopóki nie pojawi się komunikat o zmianie? Myślę, że już wysyłasz pozycje, więc jeśli coś „przeskakuje” z tego powodu, masz poprawną pozycję od następnej aktualizacji. Następnie możesz przesuwać pozycje podczas aktualizacji, jak to już robisz, używając prędkości z ostatniej wiadomości i zastępując pozycję za każdym razem, gdy wiadomość zostanie odebrana z nową pozycją. Oznacza to również, że jeśli pozycja się nie zmienia, ale prędkość musi wysłać wiadomość (jeśli jest to nawet słuszny przypadek w grze), ale nie wpłynie to znacząco na wykorzystanie przepustowości, jeśli w ogóle.
Interpolacja nie powinna mieć tutaj znaczenia, to znaczy np. Kiedy wiesz, gdzie coś będzie w przyszłości, czy masz ją, jakiej metody używasz itp. Czy może mylisz się z ekstrapolacją? (dla których opisuję jedno, proste podejście)
źródło
Moje pierwsze pytania brzmiałyby: co jest złego w korzystaniu z modelu, w którym serwer ma uprawnienia? Dlaczego ma to znaczenie, czy środowisko jest 2D czy 3D? Ułatwiłoby to ochronę przed oszustwami, gdyby serwer był autorytatywny.
Podczas wykonywania przewidywania konieczne jest utrzymanie kilku stanów (lub przynajmniej delt) na kliencie, aby po otrzymaniu z serwera autorytatywnego stanu / delty można go porównać z stanami klienta i dokonać niezbędnych korekty. Chodzi o to, aby zachować jak najwięcej determinizmu, aby zminimalizować liczbę wymaganych poprawek. Jeśli nie utrzymasz poprzednich stanów, nie będziesz wiedział, czy coś innego wydarzyło się na serwerze.
Dlaczego potrzebujesz interpolować? Autorytatywny serwer powinien zastąpić wszelkie błędne ruchy.
Są to sytuacje, w których wystąpiłby konflikt między serwerem a klientem i dlatego należy utrzymywać stany na kliencie, aby serwer mógł poprawić wszelkie błędy.
Przepraszam za szybkie odpowiedzi, muszę iść. Przeczytaj ten artykuł , wspomina o strzelankach, ale powinien działać w każdej grze wymagającej sieci w czasie rzeczywistym.
źródło