Autor Wątek: [Projektowanie] Wskaźniki do klas równorzędnych, nadrzędnej?  (Przeczytany 2886 razy)

Offline Kos

  • Użytkownik
    • kos.gd

# Grudzień 25, 2007, 20:50:15
Mam nadzieję że post w dobrym dziale :]

Cóż, oto pytanie które mnie dręczy. Załóżmy, że mamy sobie klasę gry zaprojektowaną w ten sposób (pseudokod):
class Gra
{
    obiekty;
    mapa;
    // ...
    // cała reszta, funkcje, etc.
};

Gdzie 'obiekty' to załóżmy lista, mapa to jakaś tam klasa, nieistotne.
Mamy teraz załóżmy obiekt reprezentujący naszego bohatera. Chcemy by ten odbijał się od ścian, więc zakładając że funkcja sprawdzająca i przetwarzająca kolizję jest funkcją składową naszego bohatera, ten skądś musi wiedzieć "z czym" ma się zderzać. Więc oto nasz bohater potrzebuje dodatkowe pole - wskaźnik na obiekt klasy mapa. Ten wskaźnik trzeba mu podczas tworzenia ustawić na naszą instancję tej klasy zawartą w instancji klasy Gra. Takie rozwiązanie stosowałem, lecz nie jestem pewien czy jest sensowne i optymalne od strony projektowej.
Rozważmy teraz inną sytuację, wprowadzamy do gry, powiedzmy, system jakichś tam efektów, niech będzie np: dźwięk.
class Gra
{
    obiekty;
    mapa;
    obsługiwacz_dźwięków;
    // ...
};
Jeżeli teraz chcemy by nasz bohater w jakiejś tam sytuacji robił "brzdęk", to znowu musimy dodać mu nowe pole i wyposażyć go we wskaźnik na obsługiwacz_dźwięków, i znowu musimy ten wskaźnik podczas tworzenia bohatera ustawić.

Niezbyt wygodne, moim zdaniem rozwiązanie. Przychodzi mi do głowy by dać każdemu obiektowi wskaźnik na całą klasę Gra i ustawiać go tuż po stworzeniu, by miał dostęp do całości i sobie z niej korzystał wedle woli, lecz mam wrażenie że takie rozwiązanie jest niezbyt optymalne od strony projektowej. Dlatego pytam - jakie rozwiązanie takiej lub podobnej sytuacji Wy stosujecie, uważacie za optymalne, poprawne, etc?

Offline Mr. Spam

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

Offline Xion

  • Redaktor
    • xion.log

# Grudzień 25, 2007, 21:23:45
Poprawnie byłoby, aby to klasa Gra lub Mapa (w każdym razie - nadrzędna wobec obiektów) sprawdzała kolizje. Dlaczego? Ano dlatego, że sam jeden obiekt nie potrafi sprawdzić, czy koliduje z innym, bo potrzebuje do tego informacji z zewnątrz.

Nie wyklucza to oczywiście możliwości, aby obiekty jakoś w tym sprawdzaniu pomagały, przy pomocy metod w rodzaju CollidesWith(Object*), GetAABB(), GetOBB(), itd. Innymi słowy, zamiast przekazywać obiektom klasę Gra, aby wykorzystały z niej tylko jakiś mały ułamek informacji, połączyły go z informacjami o sobie i wykonały akcję, niech obiekty gry pozwalają pobrać informacje potrzebne do sprawdzania kolizji klasie Gra. Wtedy dodanie kolejnych efektów - jak dźwięk, eksplozja partikli, Force Feedback itp. - będzie o wiele prostsze, bo taka klasa w naturalny sposób ma o wiele większe możliwości i nie trzeba tutaj nic obchodzić przy pomocy (niezbyt zgodnego z hermetyzacją) przekazywania jakiś wskaźników "do góry".

Offline mINA87

  • Użytkownik

# Grudzień 25, 2007, 21:29:48
A gdy już naprawdę musimy przekazywać masowo wskaźniki na jakąś skończoną liczbę obiektów to warto pomyśleć o zmiennych globalnych lub wręcz wzorcu Singletona.

Offline Mattrick

  • Użytkownik

# Grudzień 25, 2007, 22:32:45
Tu się przyda wzorzec projetkowy 'Mediator'.
http://www.google.pl/search?q=wzorce+projektowe+mediator

Offline Moriturius

  • Użytkownik

# Grudzień 25, 2007, 22:43:02
Tu się przyda wzorzec projetkowy 'Mediator'.
http://www.google.pl/search?q=wzorce+projektowe+mediator

Z mediatorem to trzeba uważać bo przy pewnym stopniu skomplikowania zależności pomiędzy obiektami które mediator osługuje zaczyna się robić lekki syf :P

Tak na prawdę sztuką to dopiero jest programowanie w taki sposób żeby nie robić za wielu zależności pomiędzy klasami. Jednak żeby zminimalizować takie zależności trzeba mieć po prostu doświadczenie ^^ Trzeba napisać kilka razy źle aby w końcu napisać dobrze. Jak ktoś Ci po prostu powie jak to ma być to i tak nie zapamiętasz bo nie będziesz wiedział dlaczego ma być tak, a nie inaczej. Przynajmniej ja tak mam. Zawsze odkrywam koło na nowo - ale to jest moje koło! ^^

Offline mi-ku

  • Użytkownik
    • miku DevBlog

# Grudzień 25, 2007, 23:43:50
Jestem za rozwiązaniem Xiona. Przenieść detekcję kolizji do klasy zawierającej obiekty, z kolei obsługę kolizji umieścić już w danych obiektach przekazując jedynie dane o kolizji, tak aby obiekty mogły zareagować w odpowiedni dla siebie sposób.

Aby się pozbyć wskaźnika do "obsługiwacza dźwięków" w obiektach, można dźwięki odgrywać przy detekcji kolizji, pobierając jedynie od obiektów rodzaj dźwięku wydawany przy kolizji. Przy zastosowaniu wzroca MVC dane o kolizji należałoby przesłać do reprezentacji obiektu, gdzie klasa reprezentacji mogłaby zawierać wskaźniki lub instancje singletonów do wszystkich systemów obsługi wyjścia (m. in. "obsługiwacz dźwięków"), ale z tego co widzę raczej nie stosujesz tego podziału.

Jeżeli już zajdzie potrzeba wglądu do danych klasy zawierajcej obiekt, właśnie przez ten obiekt, można by rozważyć zastosowanie dataportów opisanych m. in. tu: http://www.gamasutra.com/view/feature/1779/implementing_dataports.php?page=1.

Offline SauRooN

  • Użytkownik

# Grudzień 26, 2007, 00:02:43
Zanim zadasz pytanie o projektowanie kodu, najpierw poczytaj o wzorcach projektowych i zacznij z nich korzystać (rozsądnie).

Offline Reg

  • Administrator
    • Adam Sawicki - Home Page

# Grudzień 26, 2007, 09:55:18
Kos: Ja to widzę tak.

Jeśli chodzi o rysowanie grafiki, wydawanie dźwięków itp., to sprawa jest prosta. W grze istnieje zawsze dokładnie jeden obiekt typu system dźwiękowy czy system graficzny i nic nie szkodzi zrobić go dostępnym dla każdego zawsze i wszędzie, np. jako singleton albo prościej jako zmienna globalna. Oczywiście z umową że systemu graficznego klasy używają tylko w czasie rysowania itd.

Natomiast co do znajomości wskaźnika do mapy przez poszczególne jednostki, tu faktycznie jest problem. Omawialiśmy to już nieraz i są różne podejścia. Jedno z nich jest takie jak zaproponowałeś. Inne to na przykład żeby obiekt tylko przeliczał gdzie się chce ruszać, dopiero mapa odbierała ten wynik i faktycznie poruszała obiektem, liczyła czy może się poruszyć (kolizje z innymi obiektami) oraz powiadamiała go wywołaniem osobnej metody, że zaszła kolizja.