Komunikacja sterowana zdarzeniami w silniku gry: tak czy nie?

62

Czytam Game Coding Complete, a autor zaleca komunikację sterowaną zdarzeniami między obiektami i modułami gry.

Zasadniczo wszyscy żyjący aktorzy gier powinni komunikować się z kluczowymi modułami (fizyka, sztuczna inteligencja, logika gry, widok gry itp.) Za pośrednictwem wewnętrznego systemu powiadamiania o zdarzeniach. Oznacza to konieczność zaprojektowania wydajnego menedżera wydarzeń. Źle zaprojektowany system zjada cykle procesora, szczególnie wpływając na platformy mobilne.

Czy jest to sprawdzone i zalecane podejście? Jak zdecydować, czy go użyć?

Bunkai.Satori
źródło
Cześć James, dziękuję za odpowiedź. Podoba mi się, chociaż książka opisuje komunikację sterowaną zdarzeniami jako naprawdę złożony system, który pozornie zużywa znacznie dużo zasobów procesora.
Bunkai.Satori
Umieść to w odpowiedzi i dodaj link do ogólnego przeglądu systemu przesyłania komunikatów o zdarzeniach, który podałem jako odpowiedź na przepełnienie stosu. Cieszyć się! Pamiętaj tylko, że to, co niektórzy uważają za złożone, nie musi być :)
James
Możesz sprawdzić tę odpowiedź również za pomocą c ++ 11: stackoverflow.com/a/22703242/2929762
user2826084
libuvniedawno pojawił się jako udana biblioteka wydarzeń. (Znajduje się w C. Node.js jest najbardziej znanym przypadkiem użycia.)
Anko

Odpowiedzi:

46

Jest to rozszerzenie mojego komentarza do pełnej odpowiedzi, zgodnie z sugestią.

Tak , jasne i proste. Komunikacja musi się zdarzyć, a zdarzają się sytuacje, w których „Czy już tam jesteśmy?” wymagane jest odpytywanie typu, sprawdzanie, czy powinni robić coś innego, generalnie marnuje czas. Zamiast tego możesz sprawić, by zareagowali na rzeczy, które im powierzono. Ponadto dobrze zdefiniowana ścieżka komunikacji między obiektami / systemami / modułami znacznie przyspiesza konfiguracje równoległe.

Przedstawiłem ogólny przegląd systemu powiadamiania o zdarzeniach w przepełnieniu stosu . Użyłem go od szkoły do ​​profesjonalnych tytułów gier, a teraz produktów innych niż gry, za każdym razem dostosowanych do przypadku użycia.

EDYCJA : Aby odpowiedzieć na pytanie komentarza, skąd wiesz, które obiekty powinny otrzymać komunikat : Same obiekty powinny Requestbyć powiadamiane o zdarzeniach. Twój EventMessagingSystem(EMS) będzie wymagał Register(int iEventId, IEventMessagingSystem * pOjbect, (EMSCallback)fpMethodToUseForACallback)zarówno dopasowania, jak i utworzenia Unregister(unikalny wpis dla iEventIdwskaźnika obiektu i wywołania zwrotnego). W ten sposób, gdy obiekt chce wiedzieć o wiadomości, może to zrobić Register()w systemie. Kiedy nie musi już wiedzieć o wydarzeniach, może to zrobićUnregister(). Oczywiście chcesz mieć pulę tych obiektów rejestracji wywołania zwrotnego i skuteczny sposób dodawania / usuwania ich z list. (Zwykle korzystam z tablic z funkcją samodzielnego zamawiania; fantazyjny sposób powiedzenia, że ​​śledzą własne przydziały między stosem puli nieużywanych obiektów a tablicami, które w razie potrzeby zmieniają swoje wymiary)

EDYCJA : Aby uzyskać całkowicie działającą grę z pętlą aktualizacji i systemem powiadamiania o zdarzeniach, możesz chcieć sprawdzić mój oldschoolowy projekt . Odwołanie do powyższego wpisu Przepełnienie stosu również się do niego odnosi.

James
źródło
Witaj James, ponieważ bardziej myślę o wewnętrznym systemie powiadamiania o zdarzeniach, myślę, że nie musi on zwiększać silnika. Na przykład, jeśli na ekranie jest 50 obiektów, ale tylko 5 z nich ma zmienić swoje zachowanie; w tradycyjnym systemie wszystkie 50 obiektów musiało sprawdzić wszystkie możliwe działania, aby sprawdzić, czy powinny coś zrobić. Jednak przy użyciu powiadamiania o zdarzeniach, tylko 5 wiadomości zostanie wysłanych do tych 5 obiektów z określoną zmianą działania. To wygląda na oszczędność czasu.
Bunkai.Satori
System połączony z powyższą odpowiedzią działa w ten sposób, że obiekty rejestrują się tylko po to, aby usłyszeć o wiadomościach, których chcą. Użylibyśmy tego na naszą korzyść w grach wideo, w których na poziomie może znajdować się 100-200 obiektów, ale ty tylko „aktywuj” te, z którymi gracz może bezpośrednio wchodzić w interakcje, zmniejszając liczbę rzeczy do około 10. Podsumowując, ten rodzaj systemu powinien być „mądrzejszy” niż „czy już tam jesteśmy?” system odpytywania i powinien zmniejszyć obciążenie silnika, przynajmniej jeśli chodzi o komunikację.
James
Jest jedna rzecz związana z systemem sterowanym zdarzeniami, która mnie myli: W tradycyjnym systemie, w którym każdy obiekt ma predefiniowany zestaw metod (OnUpdate (), OnMove (), OnAI (), OnCollisionCheck () ...) regularnie wywoływany, każdy obiekt jest odpowiedzialny za sprawdzanie i zarządzanie swoim stanem. W systemie sterowanym zdarzeniami musi istnieć warunek sprawdzania każdego obiektu przez system nadrzędny, a następnie wysyłanie komunikatów do tych, w których wykryto jakieś zdarzenie. Dla mnie standaryzuje to możliwe stany obiektów i ogranicza swobodę tworzenia unikalnych zachowań obiektów. Czy to prawda?
Bunkai.Satori
Wzorzec Observer jest typową alternatywą dla odpytywania. en.wikipedia.org/wiki/Observer_pattern
Ricket
1
@ hamlin11 Cieszę się, że ta informacja wciąż pomaga ludziom :) Jeśli chodzi o rejestrację, jeśli tak się stanie, pamiętaj, że będziesz chciał ją zoptymalizować pod kątem szybkości, mieć pulę obiektów rejestracyjnych do wyciągnięcia itp.
James
22

Uważam, że powinieneś zacząć tworzyć grę i wdrożyć to, co jest dla Ciebie wygodne. Gdy to zrobisz, będziesz używać MVC w niektórych miejscach, ale nie w innych; wydarzenia w niektórych miejscach, ale nie w innych; elementy niektóre miejsca, ale inne dziedziczą; w niektórych miejscach czysty projekt, w innych nieokrzesany.

I to jest OK, ponieważ kiedy skończysz, będziesz mieć grę, a gra będzie o wiele fajniejsza niż system przekazywania wiadomości.

użytkownik744
źródło
2
Cześć Joe, dziękuję za odpowiedź, podoba mi się. Uważasz, że nie ma potrzeby sztucznego przesuwania żadnego podejścia. Powinienem projektować aplikacje tak, jak myślę, że by działały, a jeśli coś nie zadziała później, po prostu je powtórzę.
Bunkai.Satori
Właśnie dlatego system istnieje. Wiele osób zrobiło dokładnie to, co powiedziałeś, widziało koszmar i powiedział, że musi być lepszy sposób. Możesz albo powtórzyć swoje błędy, albo wyciągnąć z nich naukę, a może jeszcze bardziej je rozwinąć.
user441521,
16

Lepszym pytaniem jest, jakie są alternatywy? W tak złożonym systemie z odpowiednio podzielonymi modułami dla fizyki, sztucznej inteligencji itp., W jaki sposób można zaaranżować te systemy?

Przekazywanie wiadomości wydaje się być „najlepszym” rozwiązaniem tego problemu. Nie mogę teraz wymyślić alternatyw. Ale w praktyce istnieje wiele przykładów przekazywania wiadomości. W rzeczywistości systemy operacyjne używają przekazywania komunikatów do kilku swoich funkcji. Z Wikipedii :

Wiadomości są również powszechnie używane w tym samym sensie jako środek komunikacji międzyprocesowej; inną popularną techniką są strumienie lub potoki, w których dane są przesyłane jako sekwencja elementarnych elementów danych (wersja wyższego poziomu obwodu wirtualnego).

(Komunikacja międzyprocesowa to komunikacja między procesami (tj. Uruchamianie instancji programów) w środowisku systemu operacyjnego)

Więc jeśli jest wystarczająco dobry dla systemu operacyjnego, prawdopodobnie jest wystarczająco dobry dla gry, prawda? Są też inne korzyści, ale wyjaśnię ten artykuł z Wikipedii na temat przekazywania wiadomości .

Zadałem również pytanie dotyczące przepełnienia stosu: „Struktury danych do przekazywania wiadomości w programie?” , które możesz chcieć przeczytać.

Ricket
źródło
Cześć Ricket, dziękuję za odpowiedź. Zapytałeś, jakie inne sposoby można wykorzystać, aby obiekty gry mogły się ze sobą komunikować. Przychodzi mi na myśl bezpośrednie wywołanie metod. Najważniejsze jest to, że nie zapewnia ono tak dużej elastyczności, ale z drugiej strony unika generowania komunikatów o zdarzeniach, przekazywania, czytania itp. Wciąż mówimy o platformach mobilnych, a nie o wysokiej klasy grach. System operacyjny korzysta z wewnętrznego systemu przesyłania wiadomości. Jednak nie jest przeznaczony do działania w czasie rzeczywistym, w którym nawet niewielkie opóźnienia powodują zakłócenia.
Bunkai.Satori
Pozwolę sobie przez chwilę pozostawić to pytanie otwarte. Przed zamknięciem chciałbym zebrać więcej opinii.
Bunkai.Satori,
Bezpośrednie wywołania metod generalnie powodują sprzężenie klas, które się nawzajem wywołują. Nie jest to dobre dla systemu podzielonego na komponenty, które powinny być wymienne. Istnieją pewne wzorce, które mogą ułatwiać bezpośrednie wywołania metod o mniejszej spójności (np. Część „kontrolera” MVC zasadniczo służy temu celowi, ułatwiając zapytania do widoku modelu, chyba że je połączysz), ale ogólnie przekazywanie wiadomości jest jedynym sposobem na system mający zerowe sprzężenie między podsystemami (lub przynajmniej jedyny znany mi sposób).
Ricket
Ach, więc teraz zaczęliśmy omawiać wzorce. Słyszałem trochę o tym podejściu do programowania. Teraz zaczynam rozumieć, jakie są wzorce. Zupełnie inaczej wygląda projekt aplikacji. Masowo kontrolujesz obiekty, używając tego samego kodu do sprawdzania każdego obiektu. Po sprawdzeniu obiektu odpowiednio ustawiasz jego atrybuty.
Bunkai.Satori
1
Pytanie „Struktury danych ...” ma NIESAMOWITE odpowiedzi. Powiedziałbym, że wybrana odpowiedź i towarzyszący jej artykuł na temat Nebula3 to najlepszy opis architektury silnika gry o ich wielkości, jaką kiedykolwiek widziałem.
deft_code
15

Wiadomości ogólnie działają dobrze, gdy:

  1. Przesyłanie wiadomości nie ma znaczenia, czy zostanie odebrane.
  2. Nadawca nie musi uzyskiwać natychmiastowej odpowiedzi od odbiorcy.
  3. Może istnieć wiele odbiorników nasłuchujących na jednym nadawcy.
  4. Wiadomości będą wysyłane rzadko lub nieprzewidywalnie. (Innymi słowy, jeśli każdy obiekt musi otrzymać komunikat „aktualizuj” co każdą ramkę, wiadomości tak naprawdę niewiele kupują).

Im bardziej Twoje potrzeby będą pasować do tej listy, tym lepsze będą odpowiednie wiadomości. Na bardzo ogólnym poziomie są całkiem niezłe. Wykonują dobrą robotę, nie marnując cykli procesora tylko po to, aby zrobić nic innego niż odpytywanie lub inne bardziej globalne rozwiązania. Są fantastyczni w rozłączaniu części bazy kodu, co zawsze jest pomocne.

hojny
źródło
4

Świetne odpowiedzi do tej pory od Jamesa i Ricket'a, odpowiadam tylko, aby dodać nutkę ostrożności. Komunikacja przekazująca / sterowana zdarzeniami jest z pewnością ważnym narzędziem w arsenale programisty, ale można ją łatwo wykorzystać. Kiedy masz młotek, wszystko wygląda jak gwóźdź i tak dalej.

Absolutnie, w 100%, powinieneś pomyśleć o przepływie danych wokół swojego tytułu i mieć pełną świadomość tego, jak informacje są przekazywane z jednego podsystemu do drugiego. W niektórych przypadkach przekazywanie wiadomości jest zdecydowanie najlepsze; w innych bardziej odpowiednie może być, aby jeden podsystem działał na liście współdzielonych obiektów, umożliwiając innym podsystemom działanie na tej samej wspólnej liście; i wiele innych metod.

Przez cały czas powinieneś mieć świadomość, które podsystemy potrzebują dostępu do danych i kiedy mają do nich dostęp. Wpływa to na zdolność do równoległości i optymalizacji, a także pomaga uniknąć bardziej podstępnych problemów, gdy różne części silnika są zbyt mocno splecione. Musisz pomyśleć o zminimalizowaniu niepotrzebnego kopiowania danych oraz o tym, jak układ danych wpływa na użycie pamięci podręcznej. Wszystko to poprowadzi Cię do najlepszego rozwiązania w każdym przypadku.

To powiedziawszy, prawie każda gra, nad którą kiedykolwiek pracowałem, ma solidny system asynchronicznego przekazywania wiadomości / powiadamiania o zdarzeniach. Umożliwia pisanie zwięzłego, wydajnego i łatwego w utrzymaniu kodu, nawet w obliczu złożonych i szybko zmieniających się potrzeb projektowych.

MrCranky
źródło
+1 za dobre dodatkowe informacje. Cześć, MrCranky, Dzięki. Zgodnie z tym, co mówisz, warto rozważyć system powiadamiania o zdarzeniach, nawet w przypadku gier mobilnych. Nie należy jednak zapominać o planowaniu i należy bardzo dobrze rozważyć, gdzie będzie używany system przesyłania wiadomości.
Bunkai.Satori
4

Wiem, że ten post jest dość stary, ale nie mogłem się oprzeć.

Niedawno zbudowałem silnik gry. Wykorzystuje biblioteki stron 3D do renderowania i fizyki, ale napisałem główną część, która definiuje i przetwarza byty i logikę gry.

Silnik z pewnością stosuje tradycyjne podejście. Istnieje główna pętla aktualizacji, która wywołuje funkcję aktualizacji dla wszystkich jednostek. Kolizje są zgłaszane bezpośrednio przez jednostki oddzwaniania. Komunikacja między jednostkami odbywa się za pomocą inteligentnych wskaźników wymienianych między jednostkami.

Istnieje prymitywny system komunikatów, który przetwarza tylko niewielką grupę jednostek w celu przesyłania komunikatów. Te wiadomości są lepiej przetwarzane pod koniec interakcji w grze (na przykład tworzenie lub zniszczenie bytu), ponieważ mogą zepsuć się z listą aktualizacji. Pod koniec każdej pętli gry zużywa się niewielka lista wiadomości.

Mimo prymitywnego systemu komunikatów powiedziałbym, że system jest w dużej mierze „oparty na pętli aktualizacji”.

Dobrze. Po użyciu tego systemu uważam, że jest on bardzo prosty, szybki i dobrze zorganizowany. Logika gry jest widoczna i zamknięta w bytach, a nie dynamiczna jak w quewe wiadomości. Naprawdę nie chciałbym, aby zdarzenia były sterowane zdarzeniami, ponieważ moim zdaniem systemy zdarzeń wprowadzają niepotrzebną złożoność do logiki gry i sprawiają, że kod gry jest bardzo trudny do zrozumienia i debugowania.

Ale myślę też, że czysty system oparty na pętli aktualizacji, taki jak mój, również ma pewne problemy.

Na przykład: W niektórych momentach jedna istota może znajdować się w stanie „nic nie rób”, może czekać na zbliżającego się gracza lub coś innego. W większości tych przypadków jednostka spala czas procesora za darmo i lepiej jest ją wyłączyć i włączyć, gdy nastąpi określone zdarzenie.

Więc w moim następnym silniku gry zamierzam przyjąć inne podejście. Podmioty zarejestrują się do operacji silnika, takich jak aktualizacja, rysowanie, wykrywanie kolizji i tak dalej. Każde z tych zdarzeń będzie miało osobne listy interfejsów encji dla rzeczywistych encji.

Artur Correa
źródło
Przekonasz się, że istoty stają się bazami kodów potworów, które stają się trudne do opanowania przy dowolnej złożoności gry. Skończysz łącząc je ze sobą i będzie do bani dodawać lub zmieniać funkcje. Dzięki podzieleniu funkcji na małe elementy łatwiejsze będzie ich utrzymanie i dodawanie. Wtedy pojawia się pytanie, w jaki sposób te komponenty się komunikują? Odpowiedzią są zdarzenia z jednego komponentu podnoszące funkcje drugiego, przekazujące prymitywne dane wraz z argumentami.
user441521,
3

Tak. Jest to bardzo skuteczny sposób komunikacji między systemami gier. Wydarzenia pomagają rozdzielić wiele systemów i umożliwiają nawet samodzielne kompilowanie rzeczy bez wzajemnego poznania się. Oznacza to, że Twoje klasy mogą być łatwiej prototypowane, a czas kompilacji krótszy. Co ważniejsze, zamiast bałaganu zależności powstaje płaska konstrukcja kodu.

Inną dużą zaletą zdarzeń jest to, że można je łatwo przesyłać strumieniowo przez sieć lub inny kanał tekstowy. Możesz je nagrać do późniejszego odtworzenia. Możliwości są nieskończone.

Kolejna korzyść: możesz mieć kilka podsystemów nasłuchujących tego samego zdarzenia. Na przykład wszystkie Twoje zdalne widoki (odtwarzacze) mogą automatycznie subskrybować zdarzenie tworzenia encji i spawnować encje na każdym kliencie przy niewielkiej pracy z twojej strony. Wyobraź sobie, ile by to było pracy, gdybyś nie używał zdarzeń: musiałbyś Update()gdzieś dzwonić, a może dzwonić view->CreateEntityz logiki gry (gdzie wiedza na temat widoku i wymaganych informacji nie należy). Trudno rozwiązać ten problem bez wydarzeń.

Dzięki wydarzeniom otrzymujesz eleganckie i oddzielone rozwiązanie, które obsługuje nieskończoną liczbę obiektów o nieograniczonej liczbie rodzajów, które mogą po prostu subskrybować wydarzenia i robić swoje, gdy coś się wydarzy w Twojej grze. Dlatego wydarzenia są świetne.

Więcej informacji i wdrożenie tutaj .

użytkownik 2826084
źródło
Świetne punkty, choć jednostronne. Zawsze jestem podejrzliwy o ogólność: czy istnieją przypadki przeciwdziałania nadużyciom w przypadku wydarzeń?
Anko
1
Punkty anty-use: kiedy absolutnie musisz mieć bezpośrednią reakcję. Kiedy cokolwiek chcesz zrobić na jakimś wydarzeniu, jest zawsze wykonywane bezpośrednio i w tym samym wątku. Gdy nie ma absolutnie żadnego zewnętrznego opóźnienia, które może opóźnić zakończenie połączeń. Gdy masz tylko zależności liniowe. Kiedy rzadko robisz wiele rzeczy jednocześnie. Jeśli spojrzysz na node.js, wszystkie wywołania io są oparte na zdarzeniach. Node.js to jedno miejsce, w którym zdarzenia zostały wdrożone w 100% poprawnie.
user2826084
System zdarzeń nie musi być asynchroniczny. Możesz użyć coroutines do symulacji asynchronizacji, jednocześnie synchronizując ją, gdy jej potrzebujesz.
user441521,
2

Z tego, co widziałem, nie wydaje się zbyt powszechne, aby silnik był całkowicie oparty na przesyłaniu wiadomości. Oczywiście istnieją podsystemy, które bardzo dobrze nadają się do takiego systemu przesyłania wiadomości, takiego jak sieć, GUI i ewentualnie inne. Ogólnie rzecz biorąc, jest kilka problemów, o których mogę myśleć:

  • Silniki gier obsługują duże ilości danych.
  • Gry muszą być szybkie (minimum 20-30 FPS).
  • Często trzeba wiedzieć, czy coś zostało zrobione lub kiedy to zostanie zrobione.

Dzięki powszechnemu podejściu twórców gier, że „musi być możliwie jak najbardziej wydajny”, taki system przesyłania wiadomości nie jest tak powszechny.

Zgadzam się jednak, że powinieneś po prostu spróbować. Z pewnością taki system ma wiele zalet, a moc obliczeniowa jest dziś tania.

mrbinary
źródło
Nie wiem czy się z tym zgadzam. Alternatywą jest odpytywanie, które jest marnotrawstwem. Reagowanie tylko na zdarzenia jest najbardziej wydajne. Tak, nadal będzie trochę ankiet, które będą miały miejsce i wywołują wydarzenia, ale istnieje wiele rzeczy, które są również zorientowane na zdarzenia.
user441521,
Ja też się z tym nie zgadzam. Ilość obsługiwanych silników gier danych jest w dużej mierze nieistotna, ponieważ powiadamianie o zdarzeniach jest przeznaczone do komunikacji między podsystemem. Obiekty gry nie powinny komunikować się bezpośrednio z innymi obiektami gry (chociaż niestandardowe procedury obsługi zdarzeń w obiektach mogą być wyjątkiem). Z powodu tej stosunkowo niewielkiej liczby podsystemów i ich obszarów „zainteresowania” koszt wydajności dobrze zaprojektowanego szkieletu przesyłania komunikatów w silniku wielowątkowym jest znikomy.
Ian Young,