Wywołania funkcji dla poszczególnych ramek a komunikaty sterowane zdarzeniami w projektowaniu gier

11

Tradycyjny projekt gry , tak jak ja to wiem, wykorzystuje polimorfizm i funkcje wirtualnych do aktualizacji gry objects stany. Innymi słowy, ten sam zestaw funkcji wirtualnych jest wywoływany w regularnych odstępach czasu (np. Na klatkę) na każdym obiekcie w grze.

Niedawno odkryłem, że istnieje inny system przesyłania wiadomości sterowany zdarzeniami, który umożliwia aktualizowanie stanów obiektów gry. W tym przypadku obiekty zwykle nie są aktualizowane dla poszczególnych klatek. Zamiast tego budowany jest wysoce wydajny system powiadamiania o zdarzeniach , a obiekty gry są aktualizowane tylko po otrzymaniu prawidłowego komunikatu o zdarzeniu.

Architektura gier sterowana zdarzeniami jest dobrze opisana w: Game Coding Complete autorstwa Mike'a McShaffry'ego .

Czy mogę prosić o pomoc w następujących kwestiach:

  • Jakie są zalety i wady obu podejść?
  • Gdzie jest jedno lepsze od drugiego?
  • Czy projektowanie gier oparte na zdarzeniach jest uniwersalne i lepsze we wszystkich obszarach? Czy jest zatem zalecany do stosowania nawet na platformach mombile?
  • Który jest bardziej wydajny, a który trudniejszy do opracowania?

Aby wyjaśnić, moje pytanie nie dotyczy całkowitego usunięcia polimorfizmu z projektu gry. Chcę po prostu zrozumieć różnicę i czerpać korzyści z używania komunikatów sterowanych zdarzeniami w porównaniu do zwykłych (na ramkę) wywołań funkcji wirtualnych w celu aktualizacji stanu gry.


Przykład: To pytanie wywołało tutaj trochę kontrowersji, więc dam wam przykład: Według MVC silnik gry jest podzielony na trzy główne części:

  1. Warstwa aplikacji (komunikacja sprzętu i systemu operacyjnego)
  2. Logika gry
  3. Widok gry

W grze wyścigowej Game View odpowiada za renderowanie ekranu tak szybko, jak to możliwe, co najmniej 30 klatek na sekundę. Widok gry nasłuchuje również danych gracza. Teraz tak się dzieje:

  • Gracz naciska pedał paliwa do 80%
  • GameView konstruuje komunikat „Pedał paliwa samochodu 2 wciśnięty do 80%” i ​​wysyła go do Game Logic.
  • Game Logic odbiera komunikat, ocenia, oblicza pozycję i zachowanie nowego samochodu i tworzy następujące komunikaty dla GameView: „Draw Car 2 Fuel Pedal Pressed 80%”, „Car 2 Sound Acceleration”, „Car 2 Coordinates X, Y” .. .
  • GameView odbiera wiadomości i odpowiednio je przetwarza
Bunkai.Satori
źródło
gdzie znalazłeś? jakieś linki lub referencje? nie znam tego podejścia (ale ogólnie znam dość konstrukcję wzorcową i zalecam dobre wykorzystanie zasad orientacji obiektowej w ogóle), ale myślę, że lepsze jest podejście oparte na zdarzeniach. Pomyśl o ewolucji w systemach sieciowych: od odpytywanie do połączeń asynchronicznych .. jest zoptymalizowany w obliczeniach i na poziomie abstrakcji
nkint
Cześć Nkint, dziękuję za komentarz. Zasadniczo chciałem porównać komunikację sterowaną zdarzeniami z wywołaniami funkcji wirtualnych. Zmodyfikuję trochę moje pytanie. Przy okazji, spójrz na ten link zawierający wzorce projektowania gier: gameprogrammingpatterns.com .
Bunkai.Satori
5
Może jestem głupi, ale jak sprawisz, że system przesyłania wiadomości będzie działał bez użycia polimorfizmu? Czy nie potrzebujesz jakiejś klasy bazowej (abstrakcyjnej lub innej) do zdefiniowania interfejsu odbiornika zdarzeń? (Edytuj: zakładając, że nie pracujesz w języku z należytą refleksją).
Tetrad
2
Ponadto wydajność będzie ściśle powiązana ze szczegółami implementacji. Nie sądzę, że „który jest bardziej wydajny” to pytanie, na które można odpowiedzieć w obecnej formie.
Tetrad
1
Obiekty gry muszą tykać. Obiekty gry muszą się ze sobą komunikować. Te dwie rzeczy muszą się wydarzyć. Po pierwsze nie robisz tego z wiadomościami, ponieważ są one zbędne (prawdopodobnie masz gdzieś listę wszystkich obiektów, po prostu updateje wywołaj ). Drugie, co możesz zrobić z wiadomościami z różnych powodów.
Tetrad

Odpowiedzi:

8

Wierzę w konieczność bycia matką wynalazku. Nie lubię niczego kodować, chyba że jego potrzeba jest jasna i dobrze zdefiniowana. Myślę, że jeśli zaczniesz projekt od skonfigurowania systemu powiadamiania o zdarzeniach, robisz to źle. Uważam, że dopiero po utworzeniu i przetestowaniu infrastruktury i posiadaniu wszystkich elementów projektu, które mają dobrze zdefiniowane, działające moduły, powinieneś skoncentrować się na sposobie łączenia tych modułów ze sobą. Próba zaprojektowania systemu powiadamiania o zdarzeniach, zanim zrozumiesz kształt i potrzeby każdego modułu, jest niesprawna.

Jakie są zalety i wady obu podejść?

Myślę, że niektórzy twierdzą, że istnieją dobre systemy przesyłania wiadomości gotowe do zainstalowania i używania. Być może to prawda. Z pewnością wydajność niestandardowego rozwiązania specjalnie dostosowanego do twoich potrzeb byłaby większa niż magistrala komunikatów ogólnego przeznaczenia. Najważniejsze pytanie brzmi: ile czasu poświęcisz na zbudowanie systemu przesyłania wiadomości zamiast na korzystanie z czegoś, co już zostało zbudowane. Oczywiście, jeśli możesz znaleźć system zdarzeń z dokładnie takim zestawem funkcji, jakiego potrzebujesz, to dość łatwa decyzja. Dlatego upewniam się, że dokładnie rozumiem, czego potrzebuję, zanim podejmę tę decyzję.

Gdzie jest jedno lepsze od drugiego?

Tutaj moglibyśmy porozmawiać o pamięci i zasobach. Jeśli projektujesz dla dowolnego sprzętu z ograniczonymi zasobami, z pewnością problemem jest użycie systemu zdarzeń, który znacznie się w to zaangażuje. Naprawdę myślę, że problem sprowadza się do tego, czego potrzebujesz, co, jak już wspomniałem, jest czymś, czego nie wiesz, dopóki nie zobaczysz, jak wyglądają wszystkie elementy. Jeśli masz wystarczające doświadczenie w budowaniu tych systemów, które wiesz z góry dokładnie, jak będą wyglądać wszystkie elementy, możesz również wcześniej odpowiedzieć na to pytanie. Zgaduję, że nie masz tego doświadczenia, ponieważ nie zadałbyś tego pytania, gdybyś miał.

Czy projektowanie gier oparte na zdarzeniach jest uniwersalne i lepsze we wszystkich obszarach? Czy jest zatem zalecany do stosowania nawet na platformach mobilnych?

Uniwersalny i lepszy we wszystkich obszarach? Takie dość ogólne oświadczenie można łatwo odrzucić. Wszystko, co dodaje koszty ogólne, musi być obciążone pracą. Jeśli nie używasz zdarzeń wystarczająco, aby uzasadnić narzut projektu, oznacza to, że jest to projekt niewłaściwy.

Który jest bardziej wydajny, a który trudniejszy do opracowania?

Wydajność zależy od sposobu jej wdrożenia. Myślę, że dobrze rozwinięty tradycyjny projekt przewyższy system oparty na wydarzeniach każdego dnia tygodnia. Wynika to z faktu, że komunikacja między elementami może być mikro-zarządzana i super wydajna. Oczywiście utrudnia to również projektowanie z punktu widzenia doświadczenia i czasu potrzebnego na jego ukończenie i zwiększenie wydajności. Z drugiej strony, jeśli nie masz wystarczającego doświadczenia lub nie masz czasu, aby dobrze rozwinąć aplikację, to bardziej efektywne będzie zastosowanie projektu opartego na zdarzeniu, który odpowiada Twoim potrzebom. Jeśli masz niestandardowe potrzeby dotyczące zdarzeń, które nie mieszczą się łatwo w strukturze opartej na zdarzeniu, może to bardzo utrudnić zaprojektowanie struktury opartej na zdarzeniu - szczególnie w celu utrzymania wydajności projektu.

Erick Robertson
źródło
Cześć Eric, dziękuję za szczegółową opinię na moje pytanie. Gdy czytam odpowiedzi Twoich i innych osób, wdrażanie komunikatów o zdarzeniach prawdopodobnie nie powoduje cudu w ogólnej poprawie wydajności gry. Zamiast tego bardzo to skomplikuje. Chciałbym zobaczyć więcej odpowiedzi, zanim zamknę to pytanie. Ponieważ cała książka obejmowała ten temat, wyglądało to na dobry pomysł do rozważenia. Nie jestem pewien, czy decyzja o użyciu lub nieużywaniu wiadomości może zostać podjęta w trakcie realizacji projektu. Powiedziałbym, że jeśli używane są komunikaty o zdarzeniach, należy również dostosować ogólny projekt obiektu.
Bunkai.Satori
Nie zgadzam się. Jeśli ogólny projekt obiektu jest dobrze zamknięty, powinien być wystarczająco elastyczny, aby można go było używać w dowolnym frameworku. Byłem w stanie zamienić metody komunikacji w dużych projektach przy bardzo małym nakładzie pracy, ponieważ każdy kawałek był dobrze zamknięty.
Erick Robertson
7

Myślę, że porównujesz tutaj jabłka i pomarańcze. Polimorfizm wcale nie jest zastępowany wiadomościami. Prawdopodobnie chciałbyś, aby zdarzenia / komunikaty łączyły luźno powiązane komponenty. Na przykład. wysłać wiadomość od bytu, gdy nastąpi kolizja, zaktualizować wynik gracza lub może wywołać efekt dźwiękowy. Aby te poszczególne klasy nie znały innych klas i po prostu wysyłały i / lub obsługiwały wiadomości.

Ale gra jest najprawdopodobniej będzie mieć gdzieś pętli aktualizacji i ponieważ mają tę pętlę aktualizacji, to może być łatwo wykorzystane do aktualizacji wszystkich podmiotów gry, które muszą być aktualizowane co ramki, jak również. Nie przeszkadza to jednak w korzystaniu z wiadomości ...

Jeśli masz jakąś strukturę, w której dodajesz / usuwasz elementy gry, możesz równie dobrze uwzględnić je w pętli aktualizacji, zamiast wysyłać im komunikat aktualizacji w każdej ramce. Dlaczego więc nie wywołać aktualizacji bezpośrednio na elementach gry, gdy używasz wiadomości do łączenia różnych podsystemów gry? Podoba mi się również koncepcja Signal / Slot ( patrz przykład qt ) dla systemów podobnych do zdarzeń.

Podsumowując: Nie ma lepszego podejścia, ani nie są one wyłączne.

grzmot
źródło
Cześć, Bummzack. Wreszcie nadeszła pierwsza odpowiedź. Dziękuję za to :-) Wspominając o architekturze opartej na zdarzeniach, miałem na myśli system MVC z trzema kluczowymi warstwami: Appliaction, GameView, GameLogic. Cała komunikacja między tymi trójkami odbywa się za pośrednictwem wiadomości. Różni się to znacznie od tradycyjnego systemu z pętlą aktualizacji. Każdy system ma inną architekturę, pewne zalety i wady oraz różne koszty wydajności. Dlatego uważam, że w niektórych obszarach byłoby nieco lepiej. Po dobrej analizie powinniśmy być w stanie stwierdzić, który z nich jest lepszy.
Bunkai.Satori
3
Nie rozumiem, dlaczego jest inaczej? Potrzebujesz gdzieś pętli aktualizacji, która prawdopodobnie zaktualizuje kontroler, jeśli chcesz trzymać się MVC. Następnie kontroler może wysłać komunikat o modelu i widoku .. ale ponieważ kontroler zwykle zna widok i model, może również zaktualizować je bezpośrednio. Ale to nie zastępuje żadnego polimorfizmu. Twoje pytanie i założenia brzmią bardzo teoretycznie ... może poprzeć je jakimś przykładem kodu lub referencjami?
bummzack 26.01.11
Wspomniana wyżej książka, Game Coding Complete, zaleca powiadamianie o zdarzeniach zamiast bezpośrednich wywołań metod wirtualnych. Chociaż system przesyłania wiadomości wygląda na bardziej złożony, jego zaletą jest to, że każdy obiekt gry nie musi sprawdzać świata gry. Świat gry jest sprawdzany logiką gry tylko raz, a następnie adresowane są tylko te obiekty, które powinny zmienić swoje stany. Jest to jedna różnica i może oznaczać oszczędności. Jeśli zdecydujesz, że twoje obiekty gry będą komunikować się za pośrednictwem wiadomości, nie będą one nazywane każdą ramką - dlatego te dwa podejścia wykluczają się wzajemnie.
Bunkai.Satori
1
Głosowano za odpowiedzią odzwierciedlającą uwagi Tetrad pod pierwotnym pytaniem. @ Bunkai.Satori „Świat gry jest sprawdzany tylko raz” to pętla aktualizacji, dostaje wszystko, co wymaga aktualizacji. Komunikaty o zdarzeniach mają być wykonywane rzadko, ale nadal mają kluczowe znaczenie w silniku (PlayerDied, MonsterDied itp.), Że sprawdzanie każdej klatki w pętli Update () byłoby marnotrawstwem, ale najprawdopodobniej są generowane przez Pętla Update () sama.
James
1
Jeśli masz drugie wydanie, spójrz na rozdział zatytułowany Kontrolowanie głównej pętli. To samo powinno się nazywać w trzeciej edycji. To powinno naprawdę odpowiedzieć na twoje pytanie.
Ray Dey,
4

Zaczęło się to jako komentarz do odpowiedzi Bummzacka, ale stało się długie.

O ile faktycznie nie uczynisz go asynchronicznym (wysyłanie zdarzeń do nowego wątku), twoje obiekty nadal otrzymają notatkę synchronicznie. Zakładając, że dyspozytor zdarzeń korzysta z podstawowej tabeli skrótów z połączoną listą w komórce, kolejność będzie kolejnością, w której obiekty zostały dodane do tej komórki.

Ponadto, chyba że z jakiegoś powodu zdecydujesz się użyć wskaźników funkcji, nadal używasz wirtualnych wywołań funkcji, ponieważ każdy obiekt otrzymujący komunikat musi implementować IEventListener (lub jakkolwiek to nazwiesz). Wydarzenia to abstrakcja wysokiego poziomu, którą budujesz za pomocą polimorfizmu.

Skorzystaj z wydarzeń, w których musisz wywołać listę prania metod różnych wydarzeń w grze, aby zsynchronizować różne systemy w grze lub gdzie różne obiekty i reakcje systemów, więc wspomniane zdarzenia nie mogą być jasno zdefiniowane lub chcesz dodać więcej reakcji na te wydarzenia w przyszłości.

Są doskonałym narzędziem, ale jak powiedział bummzack, nie zakładaj, że polimorfizm i zdarzenia rozwiązują ten sam problem.

michael.bartnett
źródło
Cześć Bearcdp, dziękuję za odpowiedź. Ma to dla mnie sens. W mojej głowie jest jedna rzecz, która głosuje na komunikaty o wydarzeniach: powiedzmy, że na scenie jest 200 obiektów. Jeśli używasz wywołań funkcji dla poszczególnych klatek na wszystkich obiektach, musisz sprawdzić wszystkie 200 obiektów pod kątem kolizji w każdej klatce (przykład; oczywiście można zarządzać interwałami wywołań). W przypadku powiadamiania o zdarzeniach świat gry jest badany tylko raz. Jeśli występuje 5 kolizji, tylko 5 obiektów otrzymuje powiadomienia o zdarzeniu kolizji, zamiast 200 testów wykrywania kolizji z wywołaniami na ramkę. Jaka jest Twoja opinia?
Bunkai.Satori
2
zbadać świat gry? co to znaczy? Musiałoby to oznaczać uruchomienie tych samych 200 testów, aby wyizolować 5, o których musi wysyłać wiadomości. Dzięki koncepcji funkcji wirtualnych świat gry jest sprawdzany tylko raz (funkcja każdego obiektu z kolei), a następnie przechodzi na tylko 5, które wymagają zmiany stanu ... bez narzutu systemu przesyłania wiadomości.
Steve H
1
Posłuchaj Steve'a H, świat się nie zderzy. Przede wszystkim używam zdarzeń do wydarzeń na wysokim poziomie, takich jak PlayerJump, EnemyHit. Aby poradzić sobie z wydajnością sprawdzania kolizji, musisz wybrać strukturę danych, aby zorganizować fizyczną lokalizację obiektów w grze, abyś mógł ustalić, które obiekty wymagają i nie muszą być sprawdzane pod kątem kolizji. Po określeniu wszystkich par obiektów, które zderzyły się, możesz wywołać zdarzenie z odpowiednimi danymi kolizji (dwa obiekty, dane prędkości / pozycji / normalne itp.).
michael.bartnett
Cześć Steve i Beardcp, dziękuję za komentarze. To, co napisałeś, ma sens. Wygląda na to, że może istnieć wiele możliwych implementacji systemu powiadamiania o zdarzeniach. Może istnieć czysta zamiana koncepcji funkcji wirtualnych na ramkę, jak mówi wspomniana wyżej książka. Jednak, jak mówisz, system przesyłania wiadomości może również współistnieć z koncepcją funkcji wirtualnych na ramkę.
Bunkai.Satori
Dlaczego mówisz „z jakiegoś powodu” w odniesieniu do wskaźników funkcji? Tak w zasadzie zaimplementowałem system zdarzeń, gdy pisałem go od zera (pracuję w językach, które mają pierwszorzędne funkcje, ale to ten sam pomysł; przekaż funkcję do obiektu centrum zdarzeń, a ten obiekt mapujący funkcje detektora na wydarzenia), więc zastanawiam się, czy takie podejście ma wadę.
jhocking
2

Powiedziałbym, że wybranie jednego lub drugiego jest całkiem złe. Niektóre obiekty wymagają wywołania każdej klatki, niektóre nie. Jeśli masz obiekt, który chcesz wyrenderować, musisz wykonać wywołanie renderowania do każdej klatki. Wydarzenia nie mogą wprowadzić tej zmiany. To samo dotyczy wszelkich obiektów fizycznych opartych na czasie - musisz wywoływać je w każdej klatce.

Jednak nie wykonuję wywołań co ramkę do moich obiektów zarządzania obiektami, obiektów wejściowych użytkownika ani niczego podobnego. Masowe wirtualne połączenia z każdym obiektem w ramce to straszny pomysł.

Projekt zastosowany do dowolnego konkretnego obiektu powinien opierać się na potrzebach tych obiektów, a nie na nich na jakiejś ideologii dla całego systemu.

Edytowane, aby było bardziej jasne w pierwszym zdaniu.

DeadMG
źródło
Cześć DeadMG, pozwól mi wyjaśnić: w wyżej wymienionym przypadku projekt jest podzielony na trzy główne części: warstwę aplikacji (dostęp sprzętowy), logikę gry, widok gry. To, co jest renderowane na podstawie ramki, to widok gry. Jednak, gdy Game View zarejestruje dane wprowadzone przez użytkownika (np. Wciśnięty pedał hamulca w pogoni za grą), wysyła komunikat „Car 2 Brake Pressed” do Game Logic w celu przetworzenia. Game Logic ocenia tę akcję, oblicza zachowanie samochodu i wysyła komunikat do widoku gry: „Car 2 Block Tires”, „Car 2 Move to X, Y”. W ten sposób nie trzeba sprawdzać każdej ramy, czy w każdym samochodzie naciśnięto hamulec.
Bunkai.Satori
@Bunkai: Masz więc projekty oparte na zdarzeniach i projekty nazywane każdą ramką. To właściwie zgadza się z moją odpowiedzią.
DeadMG
Cieszę się, że mogę się z tobą porozumieć, ponieważ pytanie to było dzisiaj nieco kontrowersyjne :-)
Bunkai.Satori