Autor Wątek: wzorzec fasadowy i powiazania obiektow  (Przeczytany 6911 razy)

Offline Esidar

  • Użytkownik

# Maj 15, 2007, 00:22:21
Np. do modułu matematycznego ? Nie ma znaczenia, bo to nie problem tylko zwykłe ustalenie jak Ci wygodniej. Nie wiem czy do końca zrozumiałeś ideę komponentów :)
Wydaje mi się że brakuje Ci jednego modułu. Nazwę go "GameSystem", który będzie centralnym zarządcą gry. Jest to miejsce o którym piszesz że
Cytuj
Ktos kto bedzie korzystal z silnika doda najpierw jakis obiekt do modulu graficznego:
FasadaGrafiki->DodajObiekt(ID,kolor,rozmiar);

Potem doda jakis obiekt do modulu logicznego:
FasadaLogiki->DodajObiekt(ID,x,y,z);

Czyli tutaj będą wywoływane wszystkie inne moduły. GameSystem zajmuje się tym, że gdy uruchamiasz grę, to wczytuje on planszę z grą "Zamek.level". Na planszy masz 4 obiekty. Pierwszy wczytany obiekt posiada wygląd "Human.model", posiada logikę "Rycerz.logic" i zestaw dźwięków "Rycerz.sounds". GameSystem najpierw tworzy obiekt klasy GOS. Następnie dołacza do niego wygląd, następnie dołącza do niego logikę a na końcu dołącza do niego dźwięki.
    objekt = new GOS();
    obiekt->AttacheComponent( "VisualizationComponent", "Human.model" );
    obiekt->AttacheComponent( "LogicComponent", "Rycerz.logic" );
    obiekt->AttacheComponent( "SoundsComponent", "Rycerz.sounds" );

class VisualizationComponent : GOSComponent
{
    LocationComponent* location;
    VisualizationComponent( string plik_z_definicja, GOS* obiekt )
    {
        location = obiekt->AttacheComponent( "Location" );
        location->OnChangeNotifyMe( this, &FuncZmianaPozycji );
        FasadaGrafiki->DodajObiekt( ID );
    }
    void FuncZmianaPozycji ()
    {
        FasadaGrafiki->ZmienPozycje( ID, location->x, location->y );
    }
}

class LogicComponent : GOSComponent
{
    LocationComponent* location;
    LogicComponent( string plik_z_definicja, GOS* obiekt )
    {
        location = obiekt->AttacheComponent( "Location" );
        location->OnChangeNotifyMe( this, &FuncZmianaPozycji );
        FasadaLogiki->DodajObiekt( ID );
    }
    void FuncZmianaPozycji ()
    {
        FasadaGrafiki->ZmienPozycje( ID, location->x, location->y );
    }
}

class SoundsComponent: GOSComponent
{
    LocationComponent* location;
    SoundsComponent( string plik_z_definicja, GOS* obiekt )
    {
        location = obiekt->AttacheComponent( "Location" );
        location->OnChangeNotifyMe( this, &FuncZmianaPozycji );
        FasadaDzwieku->DodajObiekt( ID );
    }
    void FuncZmianaPozycji ()
    {
        FasadaGrafiki->ZmienPozycje( ID, location->x, location->y );
    }
}

class LocationComponent : GOSComponent
{
    float x, y, z;
    LocationComponent( string plik_z_definicja, GOS* obiekt )
    {
    }
}

Kod jest tylko poglądowy żeby było widać zasadę działania.

Przy "obiekt->AttacheComponent( "Location" );" tworzony jest tylko jeden obiekt. Pozostałe komponenty dostają wskaźnik do wcześniej stworzonego komponentu. Komponenty są widoczne tylko wewnątrz modułu "GameSystem".

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

upshader

  • Gość
# Maj 15, 2007, 01:06:30
Wydaje mi się że brakuje Ci jednego modułu. Nazwę go "GameSystem", który będzie centralnym zarządcą gry. Jest to miejsce o którym piszesz że [..]
U mnie to wszystko co nazywasz "GameSystem" bedzie robione na skryptach. Caly gameplay bede mial na skryptach.
Cytuj
ale jeżeli chodzi o zasoby współdzielone pomiędzy modułami, to wystarczy stworzyć managera, który będzie odpowiedzialny za przechowywanie i udostępnianie tych zasobów.
Gdzie bede fizycznie przechowywal obiekty, zasoby to oczywiscie zadnen problem. Moge sobie zrobic managera.
Tutaj chodzi o powiazania logiczne miedzy klasami. Ktory modul musi wiedziec o jakich klasach i umiec ich uzywac
Calosc rozbija sie oto jak rozluznic powiazania miedzy modulami. Juz tlumacze o co chodzi. Przykladowo po roku pisania mojego projektu zmienia sie troche zalozenia odnosnie pewnych technik/algorytmow, i zdecyduje sie wymienic moj System Graficzny na nowy (a w rzeczywistosci pewnie nie wymienic w calosci a bardzo zmodyfikowac)... wtedy liczy sie to ile oprocz samego systemu bede musial wymienic/obsluzyc dodatkowych klas.
W tym przykladzie ktory podawalem, przy wdrazaniu nowego Systemu Graficznego owy system musialby wiedziec, ze gameObject ktory do niego przychodzi posiada w sobie obiekt typu Location gdzie zawiera x,y,z a np. wspolrzedna x moze pobrac poprzez gameObject->GetX(); Musialby takze wiedziec, ze ten gameObcjet posiada takze w sobie obiekt typu BoundingBox ktory tez sie obsluguje pewnymi metodami. W zaleznosci od tego na ile projekt jest rozbudowany takich dodatkowych powiazanych klas moga zrobic sie dziesiatki, setki albo jeszcze wiecej. Doprowadzi to do tego, ze chcac wymienic moj System Graficzny bede musial rowniez wymienic, lub zintegrowac z dzialaniem takich klas jak wlasnie przykladowe Location, BoundingBox i jeszcze setki innych. A ja chcialbym wymienic sam System Graficzny i nie zajmowac sie tym calym szajstwem w okol tego. Jak zrobic aby jedyna klasa o ktorej bedzie wiedzial System Graficzny jako calosc mogla byc np. fasada Systemu Logicznego? To jest pytanie na ktore probuje znalesc odpowiedz :)

Offline Esidar

  • Użytkownik

# Maj 15, 2007, 01:46:52
Wydaje mi się że brakuje Ci jednego modułu. Nazwę go "GameSystem", który będzie centralnym zarządcą gry. Jest to miejsce o którym piszesz że [..]
U mnie to wszystko co nazywasz "GameSystem" bedzie robione na skryptach. Caly gameplay bede mial na skryptach.
To co napisałem to tylko pseudocode. Możesz go zrobić na skryptach jeśli chcesz.

Cytat: upshader
Tutaj chodzi o powiazania logiczne miedzy klasami. Ktory modul musi wiedziec o jakich klasach i umiec ich uzywac
To staram Ci się cały czas pokazać :) Tylko moduł "GameSystem" :) lub jeśli wolisz - skrypty, musi wiedzieć o istnieniu lub sposobie działania pozostałych modułów. GameSystem działa jak mediator (jak ktoś już wspomniał). Gdy logika zmienia w obiekcie pozycję xyz, to wtedy GameSystem / mediator informuje o tym wszystkie moduły podpięte pod obiekt. Czyli np. moduł renderera, moduł dźwięku (żeby pozycjonować dźwięki), moduł fizyki, moduł multiplayera (wysyła zmianę do innych graczy) itd.
W takim GameSystem'ie są użyte 3 paterny:
1. Adapter - czyli komponent który łączy GameSystem z jakimś modułem (np. VisualizationComponent jest adapterem dla renderera, a SoundComponent jest adapterem dla dźwięku).
2. Mediator - czyli wszystkie adaptery są informowane o zmianie danych w dowolnym innym adapterze
3. Visitor - czyli rozszeżanie obiektu który jest zupełnie transparentny, o kolejne moduły.

Jeśli użyte są wszystkie 3 paterny, to uzyskasz enkapsulację jakiej oczekujesz:
1. Żaden z modułów nie musi wiedzieć o istnieniu pozostałych modułów (renderer nie musi wiedzieć o istnieniu logiki)
2. Wspólne dane wykorzystane do podobnych celów, znajdują się w obiekcie GameSystemu i są wymieniane między adapterami

Edit: Nie rób żadnych sztywnych powiązań między modułami czyli nie uzależniaj np. logiki od renderera. Podam przykłady obiektów growych które posiadają współrzędną xyz ale nie można stwierdzić, który moduł jest w stanie tą współrzedną przechować:
1. Postać - posiada obiekt wyświetlający się, posiada obiekt logiki, posiada obiekt z dźwiękami
2. Drzewo - posiada obiekt wyświetlający się, NIE posiada obiektu logiki, posiada obiekt z dźwiękami
3. Szum lasu - NIE posiada obiektu wyświetlającego się, NIE posiada obiektu logiki, posiada obiekt z dźwiękami
4. Pułapka. Gracz po wejściu na obszar włącza dialog lub film. NIE posiada obiektu wyświetlającego się, posiada obiekt logiki, NIE posiada obiektu z dźwiękami
itd
« Ostatnia zmiana: Maj 15, 2007, 01:56:32 wysłana przez Esidar »

upshader

  • Gość
# Maj 15, 2007, 02:33:20
Nie wiem czy dobrze rozumiem do konca Twoj sposob organizacji. Moze jakbys napisal jeszcze klase GOS a w szczegolnosci metode AttacheComponent to by bylo prosciej.
No ale, tak czy inaczej mozesz odpowiedziec na ponizsze pytanie. Czy komponent "Render" widzi komponent "Location" ? Po ponizszym fragmencie wnioskuje, ze tak
Zaczynasz od "gołego" obiektu do którego dołączasz komponent "Render". Ten komponent na starcie dołącza do tego obiektu, komponent "Location" który zawiera pozycję i orientację obiektu.
Jesli tak to modul Graficzny musi widziec modul Matematyczny co jest sprzeczne z kolei z tym fragmentem:
Cytuj
1. Żaden z modułów nie musi wiedzieć o istnieniu pozostałych modułów (renderer nie musi wiedzieć o istnieniu logiki)
Jesli natomiast modul Graficzny i Logiczny trzymaja pozycje kazdy u siebie oddzielnie w swoich strukturach (a odbieraja tylko zmiany pozycji poprzez adaptery) to te same dane sa powielane, przez co takie rozwiazanie jest zle.
Po tym co wkleilem ponizej jestem juz w 99% pewny, ze tak jest
Cytuj
FasadaGrafiki->ZmienPozycje( ID, location->x, location->y );
Swoja droga czym jest ID? Tzn. skad przychodzi? Nie powinno byc tak? :
FasadaGrafiki->DodajObiekt( GOS->ID );

//EDIT
a w swoim pseudokodzie chyba sie pomyliles dajac 3 razy "FasadaGrafiki->ZmienPozycje( ID, location->x, location->y );" powinno byc raz FasadaGrafiki potem FasadaLogiki i FasadaDzwieku. Czyz tak?
« Ostatnia zmiana: Maj 15, 2007, 03:05:32 wysłana przez upshader »

Offline Reg

  • Administrator
    • Adam Sawicki - Home Page

# Maj 15, 2007, 11:59:01
Moim zdaniem trochę źle do sprawy podchodzisz. Mam wątpliwości, czy to się da aż tak rozdzielić. Oto dwa przykłady niekoniecznie bezpośrednio związane z Twoim pytaniem, ale obrazujące ten problem:

- Siatka modelu jakiegoś potworka. Wczytujesz ją z pliku. Z jednej strony musisz umieścić ją w Vertex Buffer karty graficznej, żeby ją szybko renderować (co jest zależne od API graficznego którego używasz, a więc leży w gestii modułu grafiki), ale z drugiej strony jeśli potrzebujesz dokładnej siatki do obliczeń np. kolizji, to musisz też trzymać ją w pamięci (do użytku dla innego modułu). Jak chcesz to rozdzielić?

- Chodziłem kiedyś na uczelni na kółko, w którym koledzy chcieli zacząć przygodę z programowaniem gier wykorzystując (chyba za bardzo) wiedzę teoretyczną ze studiów. Zaczęli od rozrysowania klas z jakich będzie się składał szkielet (Okienko, Klawiatura, Myszka itp.), powiązań między nimi (oczywiście prawie każda z każdą) i potem zaczęli zastanawiać się, jak uogólnić ich interfejsy żeby można było każdy dowolnie wymieniać, np. okienko stworzone w WinAPI, GLUT czy SDL, klawiatura od WinAPI czy DirectInput itd. Na tym kółko się skończyło :)

Offline novo

  • Użytkownik
    • my devblog

# Maj 15, 2007, 14:10:45
[OT]
Reg, IMO wlasnie takie rzeczy jak Okno, input czy inne zalezne od systemu powinno sie ladnie w interfejsy opakowac, coby latwo sie przenosilo. Ja u siebie tak pisze, ze np mam interface InputDevice i z niego dziedzicze sobie klawe i myszke na DI, tak samo na WinAPI(linuxowych jeszcze nie pisalem, ale nie powinno byc problemow). Nie mowie oczywiscie ze wszystko powinno byc podmienialne, ale ta czesc zalezna od OS'a to raczej nie jest duzo kombinowania.
[/OT]

Pozdr.
novo.

Offline Reg

  • Administrator
    • Adam Sawicki - Home Page

# Maj 15, 2007, 19:09:33
Jakoś nie potrafię w to uwierzyć. Co jeśli na przykład niektóre z rodzajów klas do wejścia z klawiatury czy z myszy potrzebują uchwytu do okna albo dostępu do pętli komunikatów Windows, a te posiadają tylko niektóre rodzaje klas do okna? Trudno to uogólnić.

Offline novo

  • Użytkownik
    • my devblog

# Maj 15, 2007, 22:55:01
U mnie to wyglada tak, ze input device'y korzystajace z DI po prostu rejestruja sie w EventProcessorze dla komunikatu WindowCreateEvent. Taki event zawiera miedzy innymi wskaznik na wlasnie stworzone okno. Jako ze korzystam z DI, to wiem ze jest to okno WinAPI, wiec rzutuje dynamic_castem na klase okna WinAPI i pobieram sobie uchwyt. Cos mniej wiecej takiego:
DIMouseDevice::DIMouseDevice(){
  EventProcessor::get().registerHandler(bind(this, &DIMouseDevice::onWindowCreate));
  // ..
}
void DIMouseDevice::onWindowCreate(const WindowCreateEvent &e){
  WinAPIWindow *pWnd=dynamic_cast<WinAPIWindow*>(e.pWnd);
  HWND handle=pWnd->getHandle();
  // I dalsze tworzenie urzadzenia myszki w DI.
}

Pozdr.
novo.
« Ostatnia zmiana: Maj 15, 2007, 22:58:32 wysłana przez novo »

upshader

  • Gość
# Maj 24, 2007, 01:38:01
Witam,

Pisalem dwukrotnie do esidara na pw aby zechcial odpowiedziec mi na mojego ostatniego posta w tym temacie(byl kierowany do niego) ale widac nie ma na to ochoty. Coz, trudno. Zadam to pytanie w takim razie do was.
Abstrahujac od redundancji (sprawe powtarzajacych sie danych narazie odkladam na bok - zalozmy, ze chwilowo akceptuje), dochodzi sprawa komunikacji miedzy modulami. Powiedzmy, ze system graficzny trzyma swoje obiekty identyfikujac je po ID (unikalny identyfikator). To samo system logiczny. No ale w przypadku kiedy system logiczny wysla informacje do systemu graficznego aby uaktualnil swoja pozycje musi zrobic cos takiego jak napisal esidar:
FasadaGrafiki->ZmienPozycje( ID, location->x, location->y );
System graficzny w takim przypadku musi odnalesc u siebie obiekt o okreslonym ID co trwa niestety O(log(n)) a nie O(1)!!
A takich obiektow beda pewnie dziesiatki, setki co kazda klatke. Rowniez podsystemow ktore musza sie ze soba komunikowac podajac ID obiektow moze byc sporo. To bedzie zauwazalne obciazenie. Co  o tym myslicie?
« Ostatnia zmiana: Maj 24, 2007, 01:40:35 wysłana przez upshader »

Offline Esidar

  • Użytkownik

# Maj 24, 2007, 02:09:15
Pisalem dwukrotnie do esidara na pw aby zechcial odpowiedziec mi na mojego ostatniego posta w tym temacie(byl kierowany do niego) ale widac nie ma na to ochoty.
Albo nie zauważył :) Tutaj pewnie sugestia dla adminów, PW są chyba za bardzo schowane. W PHPBB wyskakuje popup gdy się ma jakieś wiadomości w skrzynce.

Cytuj
System graficzny w takim przypadku musi odnalesc u siebie obiekt o okreslonym ID co trwa niestety O(log(n)) a nie O(1)!!

Dlatego najlepiej przechowywać wskaźnik lub handler na obiekt. Czyli np. w tym co pisałem, VisualComponent, zamiast:
FasadaGrafiki->DodajObiekt( ID );
miałby
HANDLE m_ObiektRenderujacy;
...
m_ObiektRenderujacy = FasadaGrafiki->DodajObiekt();
FasadaGrafiki->ZmienPozycje( m_ObiektRenderujacy, location->x, location->y );

Taki HANDLE to może być zwykły wskaźnik, na obiekt klasy zdefiniowanej w rendererze, zrzutowany na void*.

Albo poprostu mieć wskaźnik na obiekt, w stylu:
IRenderable* m_ObiektRenderujacy;
...
m_ObiektRenderujacy = FasadaGrafiki->DodajObiekt();
m_ObiektRenderujacy->ZmienPozycje( location->x, location->y );

Wskaźniki mają tą przewagę nad uchwytami, że FasadaGrafiki będzie mniej skomplikowana i nie będzie mieć syndromu "Everything, except kitchen sink". Czyli nie skończy się na tym, że FasadaGrafiki będzie mieć 300 metod ustawiających wszystkie możliwe parametry w każdym możliwym typie obiektów (inne parametry dla świateł, inne parametry dla kamer, inne parametry dla modeli itd).

Kaźdy rodzaj komponentu, może przechowywać własny wskaźnik na własny obiekt, odpowiedni dla obsługiwanego modułu.


W tym co pisałem wcześniej użyłem ID, gdyż sam wcześniej użyłeś systemu opartego na identyfikatorach, więc przyjąłem, że zarządzanie identyfikatorami masz już rozwiązane :)


upshader

  • Gość
# Maj 24, 2007, 14:40:09
A co np. z LODem? Wyliczanie ktore sektory terenu wyswietlac z jaka dokladnoscia itp. powinno byc zapewne wykonywane w module logicznym. Natomiast pozniej musi byc oczywiscie przeslane do modulu graficznego w celu renderingu. Jak rozwiazac ta kwestie przesylania pomiedzy modulami skoro one maja w ogole o sobie nie wiedziec i nie "widziec" sie na wzajem?

PS. Czy "renderer" i "silnik graficzny" rozumiecie jako jedno i to samo, czy renderer to jedynie czesc silnika graficznego odpowiedzialna za rendering a do tego jest ta cala otoczka nie zwiazana z nim bezposrednio?

Offline Esidar

  • Użytkownik

# Maj 24, 2007, 19:17:33
Wyliczaniem LOD'ów oraz widocznością powinien zajmować się tylko renderer (silnik graficzny to polskie określenie). To czy teren lub obiekt ma teraz zmniejszoną dokładność albo to czy obiekt jest zasłonięty przez ścianę i się nie rysuje, nie powinno wpływać na resztę logiki.
Renderer może zmienić sposób wyświetlania jeśli uzna, że obiekt jest z tyłu kamery albo za ścianą, albo za daleko. Natomiast reszta logiki traktuje obiekty według swojego uznania czyli liczone są ścieszki dla postaci, kolizja między obiektami, lub obiekt wydaje dźwięk, niezależnie od tego co renderer zrobił z obiektem.

upshader

  • Gość
# Maj 24, 2007, 20:28:11
Wyliczaniem LOD'ów oraz widocznością powinien zajmować się tylko renderer (silnik graficzny to polskie określenie). To czy teren lub obiekt ma teraz zmniejszoną dokładność albo to czy obiekt jest zasłonięty przez ścianę i się nie rysuje, nie powinno wpływać na resztę logiki.
Renderer może zmienić sposób wyświetlania jeśli uzna, że obiekt jest z tyłu kamery albo za ścianą, albo za daleko. Natomiast reszta logiki traktuje obiekty według swojego uznania czyli liczone są ścieszki dla postaci, kolizja między obiektami, lub obiekt wydaje dźwięk, niezależnie od tego co renderer zrobił z obiektem.

Ok, a co powiesz o takiej rzeczy jak OCT Tree? Silnik graficzny musi posiadac swoja reprezentacje drzewa osemkowego do testow widocznosci chodzby. Natomiast modul logiczny (czy tam fizyczny na jedno wychodzi) musi rowniez z niego korzystac np. do kolizji. Jak to pogodzic? Znow trzymac oddzielne, redundantne wersje drzewa na potrzeby roznych modulow?

Offline Reg

  • Administrator
    • Adam Sawicki - Home Page

# Maj 25, 2007, 10:58:38
Nigdy bym czegoś takiego nie zrobił. Nie wiem jakie jest najlepsze rozwiązanie, ale każde inne. Już prędzej rozbebeszyłbym całe drzewo dając każdemu swobodny dostęp do niego (i samemu sobie ustalając kto w jaki sposób będzie z niego korzystał) niż przechowywał dwie kopie drzewa. Chyba, żeby to były inne struktury danych albo... gdyby nie było innego wyjścia, tak jak jest w FMOD (biblioteka dźwiękowa), której trzeba podać siatkę trójkątów a ona sama sobie to wszystko wewnętrzne organizuje i tego używa.

upshader

  • Gość
# Maj 26, 2007, 21:50:54
Ok, ale zalezy mi na odpowiedzi Esidara jako, iz ma on podobna wizje budowy silnika jak ja. A jest jeszcze kilka takich kwestii nie do konca jasnych jak miedzy innymi ta ktora teraz wymienilem.