Disclamer: Wiem, co to jest wzorzec systemu encji i nie używam go.
Dużo czytałem o rozdzielaniu obiektów i renderowaniu. O tym, że logika gry powinna być niezależna od silnika renderującego. To wszystko jest w porządku i eleganckie i ma doskonały sens, ale powoduje również wiele innych bólów:
- potrzeba synchronizacji między obiektem logicznym a obiektem renderującym (tym, który utrzymuje stan animacji, duszków itp.)
- trzeba otworzyć obiekt logiczny publicznie, aby obiekt renderujący mógł odczytać rzeczywisty stan obiektu logicznego (często doprowadzając obiekt logiczny do łatwej transformacji w głupi obiekt pobierający i ustawiający)
Nie wydaje mi się to dobrym rozwiązaniem. Z drugiej strony bardzo intuicyjne jest wyobrażanie sobie obiektu jako jego reprezentacji 3d (lub 2d), a także bardzo łatwego w utrzymaniu (i być może również o wiele bardziej enkapsulowanym).
Czy istnieje sposób na utrzymanie reprezentacji graficznej i logiki gry w połączeniu (unikanie problemów z synchronizacją), ale oderwanie silnika renderowania? Czy istnieje sposób na oddzielenie logiki gry i renderowania, która nie powoduje powyższych wad?
(być może z przykładami, nie jestem zbyt dobry w rozumieniu abstrakcyjnych rozmów)
Odpowiedzi:
Załóżmy, że masz scenę złożoną ze świata , gracza i bossa. Aha, to jest gra dla trzeciej osoby, więc masz też aparat .
Twoja scena wygląda następująco:
(Przynajmniej są to podstawowe dane . To, jak je przechowujesz, zależy od ciebie.)
Chcesz aktualizować i renderować scenę tylko podczas gry, a nie w trybie pauzy lub w menu głównym ... więc dołącz ją do stanu gry!
Teraz twój stan gry ma scenę. Następnie chcesz uruchomić logikę na scenie i renderować scenę. Dla logiki wystarczy uruchomić funkcję aktualizacji.
W ten sposób możesz zachować całą logikę gry w
Scene
klasie. I tylko dla odniesienia, system komponentu encji może to zrobić w ten sposób:W każdym razie udało Ci się zaktualizować scenę. Teraz chcesz to wyświetlić! W tym celu wykonujemy coś podobnego do powyższego:
Proszę bardzo. RenderSystem odczytuje informacje ze sceny i wyświetla odpowiedni obraz. Uproszczona metoda renderowania sceny może wyglądać następująco:
Naprawdę uproszczone, nadal musisz na przykład zastosować rotację i tłumaczenie w zależności od tego, gdzie jest twój gracz i gdzie szuka. (Mój przykład to gra 3D, jeśli wybierzesz 2D, będzie to spacer po parku).
Mam nadzieję, że tego właśnie szukałeś? Jak miejmy nadzieję, możesz przypomnieć sobie z powyższego, system renderowania nie dba o logikę gry . Używa tylko bieżącego stanu sceny do renderowania, tj. Pobiera z niej niezbędne informacje w celu renderowania. A logika gry? Nie ma znaczenia, co robi moduł renderujący. Cholera, nie ma znaczenia, czy w ogóle jest wyświetlany!
Nie musisz też dołączać informacji o renderowaniu do sceny. Powinno wystarczyć, aby mechanizm renderujący wiedział, że musi wyrenderować orka. Załadujesz już model orka, który renderer wie, aby wyświetlić.
To powinno spełnić twoje wymagania. Reprezentacja graficzna i logika są połączone , ponieważ oba wykorzystują te same dane. Jednak są one oddzielne , ponieważ żadne z nich nie polega na drugim!
EDYCJA: I tylko po to, żeby odpowiedzieć, dlaczego tak się dzieje? Ponieważ to łatwiejsze jest najprostszym powodem. Nie musisz myśleć o „tak i tak się stało, powinienem teraz zaktualizować grafikę”. Zamiast tego sprawiasz, że coś się dzieje, a każda klatka gra patrzy na to, co się obecnie dzieje, i interpretuje to w jakiś sposób, dając ci wynik na ekranie.
źródło
W tytule zadajesz inne pytanie niż treść ciała. W tytule pytasz, dlaczego logika i rendering powinny być oddzielone, ale w treści pytasz o implementacje systemów logiki / grafiki / renderowania.
Drugie pytanie zostało już poruszone , więc skupię się na pierwszym pytaniu.
Powody oddzielenia logiki i renderowania:
W ustawieniach OOP tworzenie nowych obiektów wiąże się z pewnym kosztem, ale z mojego doświadczenia wynika, że koszt zasobów systemowych jest niewielką ceną, jaką należy zapłacić za zdolność do zastanowienia się i wdrożenia konkretnych rzeczy, które muszę wykonać.
źródło
Ta odpowiedź ma jedynie na celu zbudowanie intuicji, dlaczego oddzielenie renderowania i logiki jest ważne, zamiast bezpośredniego sugerowania praktycznych przykładów.
Załóżmy, że mamy dużego słonia , nikt w pokoju nie widzi całego słonia. może nawet wszyscy nie zgadzają się co do tego, co to właściwie jest. Ponieważ każdy widzi inną część słonia i może sobie z tym poradzić. Ale ostatecznie nie zmienia to faktu, że jest to duży słoń.
Słoń reprezentuje obiekt gry ze wszystkimi szczegółami. Ale nikt tak naprawdę nie musi wiedzieć wszystkiego o słoniu (obiekcie gry), aby móc wykonywać swoją funkcjonalność.
Połączenie logiki gry i renderowania jest tak, jakby wszyscy widzieli całego słonia. Jeśli coś się zmieniło, wszyscy muszą o tym wiedzieć. Podczas gdy w większości przypadków muszą zobaczyć tylko tę część, którą są zainteresowani. Jeśli coś zmieniło osobę, która o tym wie, wystarczy tylko powiedzieć drugiej osobie o wyniku tej zmiany, to jest dla niego ważne (traktuj to jako komunikację za pośrednictwem wiadomości lub interfejsów).
Punkty, o których wspomniałeś, nie są wadami, są tylko wadami, jeśli było więcej zależności, niż powinno być w silniku, innymi słowy, systemy widzą części słonia bardziej niż powinny. A to oznacza, że silnik nie został „poprawnie” zaprojektowany.
Synchronizacja z jej formalną definicją jest potrzebna tylko wtedy, gdy używasz silnika wielowątkowego, w którym logika i rendering znajdują się w dwóch różnych wątkach, a nawet silnik, który wymaga dużej synchronizacji między systemami, nie jest specjalnie zaprojektowany.
W przeciwnym razie naturalnym sposobem radzenia sobie z takim przypadkiem jest zaprojektowanie systemu jako wejścia / wyjścia. Aktualizacja wykonuje logikę i wyświetla wynik. Renderowanie jest tylko kanałem z wynikami aktualizacji. Naprawdę nie musisz ujawniać wszystkiego. Udostępniasz tylko interfejs, który komunikuje się między dwoma etapami. Komunikacja między różnymi częściami silnika powinna odbywać się za pomocą abstrakcji (interfejsów) i / lub komunikatów. Nie należy ujawniać wewnętrznej logiki ani stanów.
Weźmy prosty przykład wykresu sceny, aby wyjaśnić ten pomysł.
Aktualizacja odbywa się zwykle za pośrednictwem pojedynczej pętli zwanej pętlą gry (lub ewentualnie za pośrednictwem wielu pętli gier, z których każda działa w osobnym wątku). Po pętli zaktualizowano kiedykolwiek obiekt gry. Musi jedynie stwierdzić za pośrednictwem wiadomości lub interfejsów, że obiekty 1 i 2 zostały zaktualizowane, i przekazać je z ostateczną transformacją.
System renderujący dokonuje tylko ostatecznej transformacji i nie wie, co faktycznie zmieniło się w obiekcie (na przykład nastąpiła konkretna kolizja itp.). Teraz, aby wyrenderować ten obiekt, potrzebuje tylko identyfikatora tego obiektu i ostatecznej transformacji. Następnie renderer będzie zasilał interfejs API renderowania siatką i ostateczną transformacją, nie wiedząc nic więcej.
źródło