Nie da się stworzyć idealnego modułu sieciowego w grze realtime. Opóźnienia były, są i będą, niezależnie od sposobu komunikacji – nawet światło w światłowodach potrzebuje czasu, żeby dotrzeć z punktu A do punktu B. Jedyne co może zrobić projektant to balansować między zaimplementowaniem logiki na kliencie albo na serwerze. Niestety ani to, ani to rozwiązanie nie jest idealne. Logika na kliencie to proszenie się desynchronizacje i stada hackerów, a liczenie wszystkiego tylko na serwerze prowadzi do braku płynności rozgrywki. Ale przecież istnieją gry, które potrafią działać jak należy nawet przy 200 albo 400ms pingu. Jak oni to robią?

 

Podejście od strony game designera

W większości gier MMO, każdej akcji wykonywanej przez bohatera towarzyszą nieco przydługie animacje: postać zamaszyście zamachuje się przed zadaniem ciosu albo szaleńczo macha rękami przed inkantacją zaklęcia. To daje serwerowi dodatkowy czas na odebranie sygnału (np wciśnięcie klawisza ataku przez gracza), obliczenie logiki i odesłanie efektów z powrotem do klienta. Do czasu gdy animacja się skończy (i miecz w końcu trafi przeciwnika) klient już dokładnie wie co i jak ma się za chwilę wydarzyć. W efekcie gra wygląda płynnie i na pierwszy rzut oka nie widać żadnych opóźnień, nawet przy dużym pingu.

Podejście od strony programisty – interpolacja

Wyobraźmy sobie, że tworzymy grę wojenną w kosmosie. Statki kosmiczne, duże prędkości, te sprawy. Wiadomo, że trzeba sporo rzeczy liczyć, więc żeby przyoszczędzić trochę CPU serwera ustawimy krok czasowy na dla przykładu 100ms. To znaczy, serwer kolejkuje wszystkie polecenia i wiadomości od graczy i co 100ms liczy całą logikę i wysyła do każdego z klientów.

Od strony klienta sprawa wygląda mniej różowo. Możemy po prostu aktualizować pozycję wszystkiego co 100ms, ale to zaskutkuje skokami i w efekcie uczyni grę nie grywalną. Możemy też próbować przewidywać, gdzie znajdzie się dany obiekt za 100ms (na przykład licząc lokalnie fizykę z założeniem, że kierunek lotu naszych statków nie zmienia się), ale dla myśliwca poruszającego się z prędkością ponad świetlną nawet drobna zmiana parametrów w czasie 100ms wiąże się z ogromnymi zmianami w położeniu. Na chłopski rozum: serwer przyśle nam nowe położenie jednostki, ale u nas na ekranie będzie ona w całkiem innym miejscu, kilkadziesiąt tysięcy kilometrów dalej. Chyba nie to mieliśmy na myśli mówiąc o płynnej rozgrywce i pełnej synchronizacji.

Cała sztuczka polega na przedstawianiu graczom „przeszłości”. Załóżmy, że jesteśmy właśnie w momencie t=1 i mamy od serwera informacje, gdzie byli inni gracze w klatkach t=0.9 i t=0.8. Teraz, w czasie pomiędzy t=1 i t=1.1 musimy pokazać to, co stało się od t=0.8 do t=0.9. Dzięki temu, gracz widzi zawsze PRAWDZIWE położenie wszystkich obiektów, tylko, że opóźnione o 200ms.  Nie jest to rozwiązanie idealne, ponieważ każdy gracz w zasadzie widzi trochę co innego (siebie widzi w czasie rzeczywistym, innych graczy lekko opóźnionych), ale zapewnia jako taką płynność rozgrywki i daje pole manewru do dalszych poprawek i kombinowania.

Jak NIE projektować modułu sieciowego

A jeśli ktoś szukałby przykładu jak NIE projektować modułu sieciowego, zapraszam na ten adres gdzie moja radosna twórczość (gra Color Fighter) dzielnie laguje i desynchronizuje się z godną szacunku konsekwencją. Gdyby ktoś potrzebował: tutaj dostępny jest kod źródłowy.

 

colorfighter

ColorFighter – przykład projektowej porażki

Ludzie mówią, że człowiek uczy się na błędach – dopiero stworzenie tego potworka i zobaczenie jak bardzo źle chodzi zmotywowało mnie do poszperania w internecie, i zastanowienia się „jak to właściwie powinno działać”.

 

  • Did you like it?
  • Yes   No