W jaki sposób komponenty fizyki lub grafiki są zwykle budowane w systemie zorientowanym na komponenty?

19

Ostatnie 48 godzin spędziłem na czytaniu na temat systemów typu Object Component i czuję, że jestem wystarczająco gotowy, aby rozpocząć wdrażanie. Utworzyłem podstawowe klasy Object i Component, ale teraz, gdy muszę zacząć tworzyć rzeczywiste komponenty, jestem trochę zdezorientowany. Kiedy myślę o nich w kategoriach HealthComponent lub czegoś, co w zasadzie byłoby po prostu właściwością, ma to sens. Kiedy jest to coś bardziej ogólnego jako komponent fizyki / grafiki, jestem trochę zdezorientowany.

Moja klasa Object wygląda tak do tej pory (jeśli zauważysz jakieś zmiany, które powinienem wprowadzić, daj mi znać, nadal jestem w tym nowy) ...

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

Problem, na który napotykam, polega na tym, co ogólna populacja zastosowałaby w komponentach takich jak fizyka / grafika? W przypadku Ogre (mój silnik renderowania) widoczne Obiekty będą składały się z wielu Ogre :: SceneNode (ewentualnie wielu), aby dołączyć go do sceny, Ogre :: Entity (ewentualnie wielu), aby pokazać widoczne siatki i tak dalej. Czy najlepiej byłoby po prostu dodać do obiektu wiele elementów graficznych i pozwolić każdemu elementowi graficznemu obsługiwać jeden element SceneNode / jednostkę, czy też pomysł posiadania jednego z nich jest potrzebny?

W przypadku fizyki jestem jeszcze bardziej zdezorientowany. Podejrzewam, że może stworzenie RigidBody i śledzenie masy / interia / etc. miałoby sens. Ale mam problem z myśleniem o tym, jak w rzeczywistości umieścić specyfikę w komponencie.

Kiedy skończę kilka z tych „wymaganych” komponentów, myślę, że będzie to o wiele bardziej sensowne. W tej chwili jestem trochę zakłopotany.

Aidan Knight
źródło

Odpowiedzi:

32

Po pierwsze, kiedy budujesz systemy oparte na komponentach, nie musisz przyjmować podejścia polegającego na zamianie wszystkiego w komponent. W zasadzie nie powinieneś - to coś w rodzaju pomyłki neofity. Z mojego doświadczenia wynika, że ​​najlepszym sposobem na powiązanie systemów renderowania i fizyki w architekturze opartej na komponentach jest uczynienie z tych komponentów niewiele więcej niż głupich kontenerów dla prawdziwego obiektu renderowania lub prymitywu fizyki.

Ma to tę dodatkową zaletę, że systemy renderowania i fizyki są oddzielone od systemu komponentów.

Na przykład w przypadku systemów fizyki symulator fizyki ogólnie przechowuje dużą listę obiektów sztywnego ciała, którymi manipuluje za każdym razem, gdy tyka. Twój system komponentów nie ma z tym problemów - komponent fizyki po prostu przechowuje odniesienie do sztywnego ciała, które reprezentuje fizyczne aspekty obiektu, do którego należy komponent, i tłumaczy wszelkie wiadomości z systemu komponentów na odpowiednie manipulacje sztywnym ciałem.

Podobnie z renderowaniem - twój renderer będzie miał coś reprezentującego dane instancji do renderowania, a twój komponent wizualny po prostu będzie się do tego odwoływał.

Jest to stosunkowo proste - z jakiegokolwiek powodu wydaje się wprowadzać ludzi w błąd, ale często dzieje się tak, ponieważ zbyt intensywnie się nad tym zastanawiają. Nie ma tam dużo magii. A ponieważ najważniejsze jest, aby robić rzeczy, a nie męczyć się nad idealnym projektem, powinieneś zdecydowanie rozważyć podejście, w którym co najmniej niektóre elementy mają zdefiniowane interfejsy i są bezpośrednimi członkami obiektu gry, jak sugeruje momboco. Jest to równie ważne podejście i zwykle łatwiej jest go zrozumieć.

Inne komentarze

Nie ma to związku z Twoimi bezpośrednimi pytaniami, ale oto rzeczy, które zauważyłem, patrząc na Twój kod:

Dlaczego mieszasz Ogre :: String i std :: string w swoim obiekcie gry, który rzekomo nie jest obiektem graficznym, a zatem nie powinien być zależny od Ogre? Sugerowałbym usunięcie wszystkich bezpośrednich odniesień do Ogra, z wyjątkiem samego komponentu renderującego, w którym powinieneś dokonać transformacji.

update () to czysty wirtualny, co oznacza, że ​​należy podklasować GameObject, co w rzeczywistości jest dokładnie przeciwne do kierunku, w którym chcesz iść z architekturą komponentów. Głównym celem korzystania z komponentów jest preferowanie agregacji funkcjonalności zamiast dziedziczenia.

Możesz skorzystać z niektórych zastosowań const(szczególnie tam, gdzie wracasz przez referencję). Nie jestem też pewien, czy naprawdę chcesz, aby prędkość była skalarem (a jeśli tak, i pozycja, powinna być obecna w obiekcie gry w rodzaju zbyt ogólnej metodologii komponentów, o którą ci się wydaje).

Twoje podejście do używania dużej mapy komponentów i update()wywołania w obiekcie gry jest dość nieoptymalne (i częsta pułapka dla tych, którzy pierwszy budują tego rodzaju systemy). Powoduje to bardzo niską spójność pamięci podręcznej podczas aktualizacji i nie pozwala korzystać z współbieżności i trendu w kierunku procesu dużych partii danych lub zachowania w stylu SIMD jednocześnie. Często lepiej jest użyć projektu, w którym obiekt gry nie aktualizuje swoich komponentów, a raczej podsystem odpowiedzialny za sam komponent aktualizuje je wszystkie naraz. To może być warte przeczytania.


źródło
Ta odpowiedź była doskonała. Nadal nie wymyśliłem sposobu na umieszczanie akapitów w komentarzach (wpisz tylko klawisz, który przesłałem), ale odpowiem na te odpowiedzi. Twój opis systemu Object / Component miał wiele sensu. Aby wyjaśnić, w przypadku PhysicsComponent, w konstruktorze komponentu, mógłbym po prostu wywołać funkcję CreateRigidBody () mojego PhysicsManager, a następnie zapisać odniesienie do tego w komponencie?
Aidan Knight
Co do części „Inne komentarze”, dziękuję za to. Mieszałem łańcuchy, ponieważ ogólnie wolę używać wszystkich funkcji Ogre jako podstawy dla moich projektów, które używają Ogre, ale zacząłem przełączać go w systemie Obiekt / Komponent, ponieważ brakowało w nim mapy / pary / etc. Masz jednak rację, a ja nawróciłem ich na członków std, aby oddzielić go od Ogre.
Aidan Knight
1
+1 To pierwsza rozsądna odpowiedź na temat modelu opartego na komponentach, który tu przeczytałem, ładny kontrast z nadmiernym uogólnieniem
Maik Semder
5
Pozwala to na lepszą spójność (zarówno danych, jak i pamięci podręcznej instrukcji) i daje możliwość prawie trywialnego odciążenia aktualizacji na odrębne wątki lub proces roboczy (na przykład SPU). W niektórych przypadkach komponenty nawet nie muszą być aktualizowane, co oznacza, że ​​można uniknąć narzutu wywołania braku operacji w metodzie update (). Pomaga także budować systemy zorientowane na dane - chodzi o przetwarzanie masowe, które wykorzystuje nowoczesne trendy w architekturze procesora.
2
+1 za „najważniejsze jest, aby robić rzeczy, a nie męczyć się nad perfekcyjnym projektem”
Trasplazio Garzuglio
5

Architektura komponentów jest alternatywą dla uniknięcia dużych hierarchii, które są osiągane podczas dziedziczenia wszystkich elementów z tego samego węzła, oraz problemów z łączeniem, które prowadzi.

Pokazujesz architekturę komponentów z komponentami „ogólnymi”. Masz podstawową logikę do dołączania i odłączania „ogólnych” komponentów. Architektura z ogólnymi komponentami jest trudniejsza do osiągnięcia, ponieważ nie wiesz, który jest komponentem. Korzyścią jest to, że takie podejście jest rozszerzalne.

Prawidłową architekturę komponentów uzyskuje się za pomocą zdefiniowanych komponentów. Mam na myśli zdefiniowany interfejs, a nie implementację. Na przykład GameObject może mieć IPhysicInstance, Transformation, IMesh, IAnimationController. Ma to tę zaletę, że znasz interfejs, a GameObject wie, jak postępować z każdym komponentem.

Odpowiadając na termin nadmiernie uogólniający

Myślę, że koncepcje nie są jasne. Kiedy mówię komponent, nie oznacza to, że wszystkie komponenty obiektu gry muszą dziedziczyć z interfejsu komponentu lub czegoś podobnego. Jednak jest to podejście do Komponentów Ogólnych, myślę, że jest to koncepcja, którą nazywasz komponentem.

Architektura komponentów jest kolejną z architektur istniejących dla modelu obiektów wykonawczych. To nie jedyne i nie najlepsze dla wszystkich przypadków, ale doświadczenie pokazało, że ma wiele zalet, takich jak niskie sprzężenie.

Główną cechą wyróżniającą architekturę komponentów jest konwersja relacji is-a na has-a. Na przykład architektura obiektu to:

jest architekturą

Zamiast architektury obiektowej has-a (komponenty):

ma architekturę

Istnieją również architektury zorientowane na nieruchomości:

Na przykład normalna architektura obiektowa wygląda następująco:

  • Object1

    • Pozycja = (0, 0, 0)
    • Orientacja = (0, 43, 0)
  • Object2

    • Pozycja = (10, 0, 0)
    • Orientacja = (0, 0, 30)

Architektura zorientowana na nieruchomości:

  • Pozycja

    • Object1 = (0, 0, 0)
    • Object2 = (10, 0, 0)
  • Orientacja

    • Object1 = (0, 43, 0)
    • Object2 = (0, 0, 30)

Czy jest lepsza architektura modelu obiektowego? Z punktu widzenia wydajności lepiej jest skoncentrować się na właściwościach, ponieważ jest bardziej przyjazny dla pamięci podręcznej.

Składnik odnosi się do relacji is-a zamiast has-a. Składnikiem może być macierz transformacji, czwartorzęd itp. Ale relacja z obiektem gry jest relacją to -a, obiekt gry nie ma transformacji, ponieważ jest dziedziczony. Obiekt gry ma (relacja ma-a) transformację. Transformacja jest wówczas składnikiem.

Obiekt gry jest jak hub. jeśli chcesz komponent, który zawsze tam jest, to może komponent (podobnie jak matryca) powinien znajdować się wewnątrz obiektu, a nie wskaźnik.

Nie ma idealnego obiektu do gry we wszystkich przypadkach, zależy to od silnika, bibliotek, z których korzysta silnik. Może chcę aparat, który nie ma siatki. Problem z architekturą is polega na tym, że jeśli obiekt gry ma siatkę, to kamera dziedzicząca z obiektu gry musi mieć siatkę. Dzięki komponentom kamera ma transformację, kontroler ruchu i wszystko, czego potrzebujesz.

Aby uzyskać więcej informacji, rozdział „14.2. Architektura modelu obiektów wykonawczych” z książki „Architektura silnika gry” Jason Gregory dotyczy tego tematu.

Stamtąd pobierane są diagramy UML

momboco
źródło
Nie zgadzam się z ostatnim akapitem, istnieje tendencja do nadmiernego uogólniania. Model oparty na komponentach nie oznacza, że ​​każda funkcjonalność musi być komponentem, wciąż należy wziąć pod uwagę relacje funkcjonalności i danych. AnimationController bez siatki jest bezużyteczny, więc umieść go tam, gdzie należy, w siatce. Obiekt gry bez transformacji nie jest obiektem gry, z definicji należy do świata 3D, więc umieść go jako zwykły element w obiekcie gry. Nadal obowiązują normalne zasady funkcji enkapsulacji i danych.
Maik Semder
@Maik Semder: Zgadzam się z tobą w uogólnieniu, ale myślę, że składnik terminu nie jest jasny. Zredagowałem swoją odpowiedź. Przeczytaj i podziel się swoimi wrażeniami.
momboco
@momboco cóż, mniej powtarzałeś te same punkty;) Transformacja i AnimationController są nadal opisywane jako komponenty, co z mojego punktu widzenia jest nadużywaniem modelu komponentu. Kluczem do sukcesu jest określenie, co należy umieścić w komponencie, a co nie:
Maik Semder,
Jeśli coś jest potrzebne, aby Game-Object (GO) działało w pierwszej kolejności, innymi słowy, jeśli nie jest opcjonalne, ale obowiązkowe, to nie jest składnikiem. Komponenty mają być opcjonalne. Transformacja jest dobrym przykładem, w ogóle nie jest opcjonalna dla GO, GO bez Transformacji nie ma sensu. Z drugiej strony nie każdy GO potrzebuje fizyki, więc może to być komponent.
Maik Semder
Jeśli istnieje silny związek między 2 funkcjami, jak między AnimationController (AC) a Mesh, to w żaden sposób nie przerywaj tej relacji poprzez wciśnięcie AC w ​​komponent, w przeciwieństwie do Mesh jest idealnym komponentem, ale AC należy w siatkę.
Maik Semder