Jak zsynchronizować stan gry wieloosobowej bardziej efektywnie niż aktualizacje w pełnym stanie?

10

Wcześniej robiłem małe kodowanie sieci gier, ale przede wszystkim za pomocą TCP dla gier bez potrzeb czasu rzeczywistego. Pracuję nad dwuwymiarową grą Java z sieciowym multiplayerem. Do nauki chcę to zrobić sam, bez istniejącego sieciowego interfejsu API.

Jak skutecznie reprezentować stan gry wysyłany do klientów z serwera? Istnieje najbardziej oczywisty, ale prawdopodobnie najmniej wydajny sposób, polegający na utworzeniu pewnego rodzaju obiektu kontekstu stanu gry z lokalizacją każdego gracza, stanem animacji itp. I wysyłaniem go do każdego gracza przy każdej aktualizacji . Nie wydaje się to zbyt trudne do wdrożenia, ale prawdopodobnie byłoby zbyt duże, aby osiągnąć coś zbliżonego do interakcji w czasie rzeczywistym (oczywiście moje doświadczenie z tym jest ograniczone, więc mogę się mylić).

Czy jest jakiś solidny sposób, w jaki ktoś z was używał wcześniej, aby tylko przekazywać zmiany stanu i czy istnieje nawet wystarczająco duża różnica w wydajności, że warto dodatkową pracę?

Haz
źródło
2
Wypróbuj pełny stan każdej klatki, a jeśli jest zbyt wolny (jak na nieco prostą grę 2D, prawdopodobnie jest wystarczająco wydajna), spróbuj zoptymalizować. Jeśli działa dobrze, to działa dobrze i nie musisz go zmieniać, chyba że później zauważysz, że sieć jest wąskim gardłem.
Robert Rouhani,

Odpowiedzi:

10

Regularne przekazywanie pełnego stanu gry zazwyczaj nie jest możliwe, choć zależy to w dużej mierze od złożoności gry. W przypadku prostej gry z małym modelem światowym może ona działać.

Osobiście odniosłem znacznie większy sukces z następującym modelem:

  • Stan gry przechowywany w dobrze zdefiniowanym modelu obiektowym w strukturze danych przestrzennych (np. Oktawie)
  • Wszystkie zmiany stanu gry (czy to na kliencie, czy na serwerze) są opisywane jako zdarzenia. Zdarzeniem może być zmiana właściwości obiektu gry, zmiana kafelka mapy, ruch obiektu gry itp.
  • Silnik gry na serwerze generuje strumień zdarzeń w trakcie gry. Są one bezpośrednio stosowane do stanu gry serwera.
  • Wydarzenia są również wysyłane do graczy, ale tylko wtedy, gdy wydarzenie jest istotne dla tego gracza (np. Czy zdarzenie jest widoczne z bieżącej pozycji?)
  • Zmiany w widoczności gracza mogą również powodować zdarzenia „ujawniające” nowe części mapy itp. Podczas ruchu gracza. Można to również wykorzystać, aby zapewnić graczowi dokładny początkowy widok odpowiedniego stanu gry, gdy po raz pierwszy dołączy do gry.
  • Stan gry odtwarzacza jest aktualizowany niezależnie od otrzymywanych zdarzeń. Jako taki ma tylko częściowy model stanu gry, ale powinien pozostać zsynchronizowany z serwerem, zakładając, że wszystkie zdarzenia są poprawnie przetwarzane

Zapewniło mi to dobrą wydajność nawet w dość dużych światach gier.

Kolejna wskazówka: pozwól klientowi zająć się animacją, efektami cząstek itp. Bez odniesienia do serwera. Przekazywanie ich nie ma sensu - wystarczy, że zostaną uruchomione przez odpowiednie zdarzenia w grze.

mikera
źródło
6

Synchronizacja jest zwykle podzielona na dwie części: przyrostową i bezwzględną.

Czasami musisz przesłać wszystko, jest duże, ale jeśli zapakujesz go we właściwy sposób, możesz to zrobić raz na kilka sekund. Dobrze jest wprowadzić wszystko na swoim miejscu, korygując wady przyrostowych odświeżeń.

Aby uzyskać doświadczenie w czasie rzeczywistym, musisz szybko przesłać niektóre zmiany, ale tylko atrybuty, które mogą ulec zmianie. Na przykład, jeśli rakieta leci w linii prostej, nie musisz aktualizować pozycji, każdy klient może ją obliczyć od punktu początkowego. Ale kiedy trafi, możesz wygenerować komunikat o tym, aby każdy klient mógł eksplodować rakietą we właściwym miejscu. Drobne usterki można zignorować.

Oczywiście aktualizujesz tylko te rzeczy, które mogą wpłynąć na klienta! Coś daleko od ekranu nie jest tego warte. Niektóre wartości mogą być aktualizowane rzadziej. Na przykład pozycje są ważne, aby być mniej lub bardziej precyzyjnym, zdarzenia (śmierć, oddanie strzału, eksplozja itp.) Muszą zostać wysłane natychmiast, podczas gdy nieistotne bezpośrednio wartości mogą mieć krótsze okresy odświeżania, na przykład tablicę wyników, czat.

Ważne jest również pakowanie danych. Możesz przesłać około 1400 bajtów (zależnie od konfiguracji, jest to ustawienie domyślne) w jednym pakiecie UDP, zwykle jest kilka bajtów nagłówka. Dzięki temu możesz łatwo aktualizować 50-100 pozycji jednostek w jednym pakiecie.

Matzi
źródło
Dzięki za radę Matzi. Nadal pracuję nad implementacją serwera i klienta, ale sprawdzę za kilka dni i prawdopodobnie zaakceptuję twoją odpowiedź.
Haz
Powodzenia! ;)
Matzi
1

W zależności od gry możesz rozważyć model „zsynchronizowanego wykonania”, w którym każdy klient gra w tę samą grę, po prostu udostępniając niedeterministyczne dane wejściowe, takie jak dane z klawiatury / joysticka i zdarzenia czasowe. (W porównaniu do modelu, w którym każdy klient uruchamia lokalne symulacje i oczekuje zintegrowania wyników ze zdalnych symulacji). Aby działał, silnik gry musi być całkowicie deterministyczny, co może być dużym obciążeniem w zależności od gry. Ale jeśli gra jest już deterministyczna, może to być łatwiejsze podejście.

Ten post #AltDevBlogADay opisuje niektóre aspekty tego podejścia we współczesnym RTS (w szczególności jak wykryć, kiedy Twoi klienci zaczynają uruchamiać „różne” gry).

Pamiętaj jednak, aby zachować prostotę, dopóki nie zostanie udowodnione inaczej. :)

PT
źródło
1
Są to dobre lektury twórcy Factorio, który korzysta z tego podejścia, i wskazują na złożoność tego podejścia, ale także pokazują, że jest on wykonalny: factorio.com/blog/post/fff-76 factorio.com/blog/post/fff -147 factorio.com/blog/post/fff-188
AaronLS