Pracuję nad silnikiem gry wieloosobowej serwer-klient 2D (który możesz wypróbować tutaj ). Wykorzystuje WebRTC DataChannel
. (Połączenia są peer-to-peer, ale host-host nadal działa jako serwer).
Największym problemem (oprócz łączności) jest lokalna prognoza wejściowa. Robimy to, co zwykle: po naciśnięciu klawisza gracze poruszają się natychmiast, mówią gospodarzowi, jakie klawisze są naciskane, odbierają dane z hosta i porównują je z historyczną pozycją. Pozycja jest korygowana w czasie, jeśli występuje różnica. Działa to dobrze przy niskiej utracie pakietów lub PDV , nawet jeśli ping jest wysoki.
Jeśli występuje strata lub PDV, odchylenie może być większe. Myślę, że dzieje się tak, ponieważ jeśli pierwszy pakiet wskazujący zmianę danych wejściowych jest opóźniony lub upuszczony, host dowiaduje się później i zaczyna zmieniać tego odtwarzacza później, niż pokazuje lokalny przewidywanie wejściowe.
Jeśli gracz się porusza, zwiększamy zastosowaną korekcję, ponieważ jest ona mniej zauważalna. Wydaje się, że to zasłania luki, gdy zaczynasz się poruszać i podczas ruchu. Jednak każda korekta jest bardziej zauważalna, jeśli nagle się zatrzymają. Następnie, jeśli PDV lub utrata oznacza, że gospodarz myśli, że zatrzymał się później, gospodarz przeskakuje, przesyła dane z informacją, że są nieco dalej, a korekta sprawia, że gracz trochę dryfuje. Na niestabilnych połączeniach gracze często zauważalnie dryfują po zatrzymaniu.
Nie zauważyłem tego w innych grach. Jak można to złagodzić?
źródło
Odpowiedzi:
Warstwa sieci musi mieć uzgodniony zegar. Mogą uzgodnić wartość zegara na początku gry (i okresowo go synchronizować w przypadku dryfu), aby gospodarz wiedział, ile czasu konkretny pakiet rzeczywiście przybył i kiedy klient wykonał akcję, i odwrotnie.
W tym artykule znajduje się jeden z możliwych sposobów synchronizacji zegarów w grach. Są inni. Konkretne środki nie mają znaczenia.
Druga połowa problemu polega na tym, że serwer stosuje dane wejściowe po upływie czasu, w którym klient przestał stosować dane wejściowe. Wymaga to bufora wcześniejszych ruchów na serwerze i pewnej logiki na kliencie, aby zignorować dane wejściowe ruchu z serwera po ostatnim znanym ruchu.
Najpierw bufor serwera. Serwer musi śledzić znacznik zegara ostatniego wejścia otrzymanego od odtwarzacza. Potrzebuje również wszystkich ruchów, które stosuje wobec gracza, znacznika czasu ruchu. Jeśli zostanie odebrany sygnał wejściowy, wszystkie ostatnie ruchy zostaną zastosowane z nowszym stemplem zegarowym niż pakiet wejściowy zostaną odrzucone, a cały ruch zostanie ponownie zastosowany z pakietu wejściowego. Dlatego jeśli serwer przesunie gracza na podstawie niektórych danych wejściowych, zaktualizowane dane wejściowe anulują te ruchy, a nowa pozycja gracza będzie oparta na najnowszej wiedzy wejściowej, jaką posiada serwer.
Po stronie klienta klient wie, kiedy ostatnio wysłał dane wejściowe do serwera. Ponieważ każda aktualizacja odtwarzacza z serwera powinna być oznaczona zegarem ostatniego wejścia, o którym wiedział serwer, klient może zignorować aktualizacje serwera, które wygasły, i po prostu trzymać się prognozy klienta. W końcu pojawią się nowe aktualizacje serwera z aktualnymi danymi wejściowymi, a klient może je poprawić.
Serwer musi zweryfikować zegary wejściowe i upewnić się, że nie odbiegają zbytnio od oczekiwań, aby zapobiec oszukiwaniu. Zegar wejściowy nie powinien być drastycznie dłuższy niż czas, który należy obliczyć w połowie okrążenia. Zacisnąć wszystkie, które są w rozsądnym zakresie (
[Now-2*RTT,Now]
na przykład).Klienci zobaczą wiele drgań awatarów innych graczy, jeśli opóźnienie jest duże, ponieważ będą otrzymywać aktualizacje z serwera na podstawie nieaktualnych danych wejściowych, ale nie będą mogli wiedzieć, że jest przestarzały, a następnie serwer może zacząć wysyłać dość różne lokalizacje na podstawie zaktualizowane dane wejściowe, które otrzymał (i usunięcie części historii i odtworzenie ich z nowymi danymi wejściowymi). Ten ostatni problem z innymi graczami widzącymi drgania awatara nie jest naprawiony. Opóźnienia są do bani, a gracze, którzy utknęli w połączeniach o dużym opóźnieniu, zobaczą wiele drgań innych graczy, nawet jeśli ich własny gracz porusza się płynnie. Jedyną korektą jest gra na lepszych połączeniach lub z serwerami równorzędnymi / serwerami o mniejszym opóźnieniu.
źródło
Użyłem wiarygodnych komunikatów UDP do wskazania zmian stanu przycisków i niewiarygodnych komunikatów UDP do korekcji pozycji. Zasadniczo następujące artykuły bardzo mi pomogły: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
Mówi o przewidywaniu ruchu poprzez przechowywanie stanów gracza w stałych odstępach czasu zgodnie z nadejściem komunikatów korekcji pozycji dla około 20 lub 30 zapisów stanu. Wygląda więc na to, że Twoi zdalni gracze będą żyli w niezbyt odległej „przeszłości”, ciągle stosując technikę przewidywania :) Na podstawie opóźnienia wiadomości w sieci możesz uzyskać pozycję obiektu w przybliżeniu w czasie, gdy wiadomość została właśnie wysłana z hosta.
Bieżącą pozycję „na ekranie” można następnie płynnie przełożyć na przewidywaną pozycję za pomocą matematyki Lerp (interpolacja liniowa). Chodzi o interpolację wartości w odstępach czasowych między pakietami korekcji. Wygląda więc na to, że wyświetlany obiekt zawsze przesuwa się w kierunku przewidywanej pozycji. Dla wartości interpolacji biorę 1 podzieloną przez „średni czas oczekiwania wiadomości” podzielony przez „średni czas renderowania ramki”, aby ruch wyglądał płynnie.
W tym scenariuszu gra oblicza na wszystkich klientach, a serwer od czasu do czasu koryguje wartości, takie jak prędkość i pozycja.
Jeszcze jedna rzecz, która bardzo pomaga w tym przypadku: zoptymalizuj logikę gry, abyś mógł łatwo negować efekty opóźnień, zapewniając serwerowi i klientom możliwość emulacji prawie podobnych zachowań na podstawie danych wejściowych gracza.
Opisałem cały schemat zastosowany w moim projekcie, więc mam nadzieję, że znajdziesz odpowiedź na swoje pytanie.
źródło