2016-09-01 83 views
5

Próbuję stworzyć architekturę dla gry MMO i nie wiem, w jaki sposób mogę przechowywać tak wiele zmiennych, jakich potrzebuję w GameObjects, bez konieczności wykonywania wielu połączeń wyślij je na drucie w tym samym czasie, kiedy je aktualizuję.Słuchaj zmienników o zmiennej C++ (ponad 100 klas)

Co mam teraz jest:

Game::ChangePosition(Vector3 newPos) { 
    gameobject.ChangePosition(newPos); 
    SendOnWireNEWPOSITION(gameobject.id, newPos); 
} 

To sprawia, że ​​śmieci kodu, trudne do utrzymania, zrozumieć, przedłużyć. Więc pomyśl o przykład Champion:

GameObject Champion example

musiałbym zrobić wiele funkcji dla każdej zmiennej. I to jest tylko uogólnienie dla tego Czempiona, mogę mieć 1-2 inne zmienne dla każdego bohatera typu/klasy.

Byłoby idealnie, gdybym mógł mieć OnPropertyChange z .NET lub coś podobnego. Architektura Próbuję odgadnąć będzie działać dobrze jest, jeśli miałem coś podobnego do:

Dla HP: gdy go zaktualizować, automatycznie zadzwonić SendFloatOnWire("HP", hp);

na stanowisko: gdy go zaktualizować, automatycznie zadzwonić SendVector3OnWire("Position", Position)

Dla Nazwa: gdy go zaktualizować, automatycznie zadzwonić SendSOnWire("Name", Name);

Jakie są dokładnie SendFloatOnWire, SendVector3OnWire, SendSOnWire? Funkcje, które serializują te typy w buforze znaków.

lub metoda 2 (Preferowany), ale może być drogie

Aktualizacja Hp, pozycja normalnie i wtedy każdy wątek Network kleszcz skanowania wszystkie instancje GameObject na serwerze dla zmiennych zmieniły i wysłać ci.

W jaki sposób zostałby on wdrożony na serwerze gry o dużej skali i jakie są moje opcje? Czy przydatna książka do takich przypadków?

Czy makra okażą się przydatne? Wydaje mi się, że zostałem odsłonięty do jakiegoś kodu źródłowego czegoś podobnego i myślę, że używał makr.

Z góry dziękuję.

EDYTOWANIE: Myślę, że znalazłem rozwiązanie, ale nie wiem, jak bardzo jest ono w rzeczywistości. Zamierzam spróbować i zobaczyć, gdzie stoję. https://developer.valvesoftware.com/wiki/Networking_Entities

+0

Czy na pewno nie chcesz, aby serwer był tym, który uruchamia grę, a klientem po prostu głupim rendererem? W ten sposób nie musisz wysyłać wszystkich tych atrybutów przez przewód (i pomaga to zapobiec oszustwom, ponieważ nie można manipulować klientem w celu zgłoszenia nieuczciwych wartości). – Cornstalks

+0

Dokładnie to działa, ale nadal muszę powiadomić klienta o tym, co ma on do renderowania. – ioanb7

+0

ChangePosition() to tylko przykład, w którym klient decyduje, dokąd chce się udać. To tylko cel, serwer może zdecydować, czy jest ważny, czy nie, itp. Podałem go jako przykład. – ioanb7

Odpowiedz

0

Ogólny wniosek, do którego doszedłem: kolejna rozmowa po aktualizacji pozycji nie jest taka zła. Jest to linia kodu dłuższa, ale jest lepsza dla różnych motywów:

  1. Jest wyraźna. Wiesz dokładnie, co się dzieje.
  2. Nie zwalniasz kodu, robiąc wszelkiego rodzaju hacki, aby działał.
  3. Nie korzystasz z dodatkowej pamięci.

Metody Próbowałem:

  1. Posiadanie mapy dla każdego typu, jak sugerują przez @Christophe. Główną jego wadą było to, że nie był podatny na błędy. Mogłeś mieć HP i Hp zadeklarowane na tej samej mapie i mógłbyś dodać kolejną warstwę problemów i frustracji, takich jak deklarowanie map dla każdego typu, a następnie poprzedzanie każdej zmiennej nazwą mapy.
  2. Używanie czegoś SIMILAR do zaworu engine: Stworzyło oddzielną klasę dla każdej żądanej zmiennej sieciowej. Następnie użył szablonu do podsumowania podstawowych typów, które zadeklarowałeś (int, float, bool), a także rozszerzonych operatorów dla tego szablonu. Używano o wiele za dużo pamięci i dodatkowych wywołań dla podstawowej funkcjonalności.
  3. Korzystanie z data mapper, które dodało wskaźniki dla każdej zmiennej w konstruktorze, a następnie wysłało je z przesunięciem. Opuściłem projekt przedwcześnie, gdy zdałem sobie sprawę, że kod zaczął być mylący i trudny do utrzymania.
  4. Używanie struktury, która jest wysyłana za każdym razem, gdy coś się zmieni, ręcznie. Można to łatwo zrobić, używając: protobuf. Rozszerzanie struktur jest również łatwe.
  5. Każde zaznaczenie, wygeneruj nową strukturę z danymi dla klas i wyślij ją. To utrzymuje bardzo ważne rzeczy zawsze aktualne, ale je dużo przepustowości.
  6. Użyj refleksji z pomocą wzmocnienia. To nie było świetne rozwiązanie.

W końcu użyłem kombinacji 4 i 5. A teraz wdrażam to w mojej grze. Jedną z ogromnych zalet protokołu protobuf jest możliwość generowania struktur z pliku .proto, a także oferowanie serializacji dla struktury. Jest niesamowicie szybki.

Dla tych specjalnych nazwanych zmiennych, które pojawiają się w podklasach, mam inną konstrukcję. Alternatywnie, z pomocą protobuf, mógłbym mieć szereg właściwości, które są tak proste jak: ENUM_KEY_BYTE VALUE. Gdzie ENUM_KEY_BYTE to tylko bajt odnoszący się do enum do właściwości takich jak IS_FLYING, IS_UP, IS_POISONED i VALUE jest string.

Najważniejszą rzeczą, jakiej nauczyłem się od tego, jest jak największa serializacja. Lepiej jest używać więcej procesorów na obu końcach niż mieć więcej danych wyjściowych.

Jeśli ktoś ma jakieś pytania, komentarz i zrobię, co w mojej mocy, aby ci pomóc.

ioanb7

1

Na Metoda 1:

Takie podejście może być stosunkowo „łatwe” do wdrożenia przy użyciu mapy, które są dostępne poprzez pobierające/ustawiające. Ogólna idea byłoby coś jak:

class GameCharacter { 
    map<string, int> myints; 
    // same for doubles, floats, strings 
public: 
    GameCharacter() { 
     myints["HP"]=100; 
     myints["FP"]=50; 
    } 
    int getInt(string fld) { return myints[fld]; }; 
    void setInt(string fld, int val) { myints[fld]=val; sendIntOnWire(fld,val); } 
}; 

Online demo

Jeśli wolisz, aby zachować właściwości w swojej klasie, można by pójść na mapie do wskaźników lub wskaźników członkowskich zamiast wartości. W trakcie budowy zainicjalizujesz mapę odpowiednimi wskaźnikami.Jeśli jednak zdecydujesz się zmienić zmienną składową, powinieneś zawsze przejść przez setera.

Można nawet posunąć się dalej i zintensyfikować swoją Champion, tworząc z niej tylko zestaw właściwości i zachowań, do których można uzyskać dostęp za pośrednictwem mapy. Ta architektura komponentów jest wyświetlana pod Mike McShaffry w Game Coding Complete (książka do przeczytania dla każdego twórcy gier). W książce jest community site z kodem źródłowym do pobrania. Możesz zajrzeć do pliku actor.h i actor.cpp. Niemniej jednak, naprawdę polecam przeczytać pełne wyjaśnienia w książce.

Zaletą komponowania jest możliwość wbudowania logiki przekierowania sieci w klasie bazowej wszystkich właściwości: może to uprościć kod o rząd wielkości.

Na Metoda 2:

Myślę, że pomysł baza doskonale nadaje, chyba że pełna analiza (lub, co gorsza, transmisja) wszystkich obiektów byłoby przesadą.

Ładną alternatywą byłby znacznik, który jest ustawiony, gdy zmiana jest wykonywana i jest resetowany, gdy zmiana jest przesyłana. Jeśli transmitujesz zaznaczone obiekty (i być może tylko zaznaczone ich właściwości), zminimalizujesz obciążenie wątku synchronizacji i zmniejszysz obciążenie sieci, łącząc transmisję kilku zmian wpływających na ten sam obiekt.

+2

Aby rozwinąć metodę 2, można przechowywać globalny zestaw obiektów, które są nieaktualne. Za każdym razem, gdy właściwość się zmienia, dodaj wskaźnik do zmienionego na ten zestaw, a na teście sieciowym przeprowadź tylko obiekty w tym zbiorze, prześlij ich zmienione elementy, a na koniec wyczyść zestaw. Pozwoli to zaoszczędzić na konieczności iteracji nad wszystkimi obiektami w każdym teście sieci, aby sprawdzić, czy wymagają one aktualizacji. – Rotem

+0

Witam, dziękuję za sugestie. Czy sądzisz, że mógłbyś dodać swoją odpowiedź za pomocą prawdziwego kodu lub pseudokodu? Zaznaczając zmienione właściwości, masz na myśli, że chciałbym mieć kilka pustych * wskaźników dla każdego obiektu (object-> propertyName)? – ioanb7

+0

@ ioanb7 Edytowałem swoją odpowiedź, aby wyjaśnić dalej i zilustrować. Ale jeden ważny punkt: nigdy "void *"! Możesz mieć rodzinę funkcji odrzuconych według typu, np. 'SendXXXOnWire()' dla różnych obsługiwanych typów (pokazałem to w edycji). Lub lepiej użyj szablonów pobierających/ustawiających szablon, aby uniknąć redundancji kodu. Alternatywnie możesz wybrać opcję 'boost :: variant' lub union. – Christophe