Komponujesz ciężkie OOP kontra systemy składowe czystych bytów? [Zamknięte]


13

Przyznaję, że popełniłem grzech nadużywania, a nawet nadużywania dziedzictwa. Pierwszy (tekstowy) projekt gry, który stworzyłem, gdy brałem kurs OOP, obejmował „Zamknięte drzwi” i „odblokowane drzwi” z „Drzwi” i „Pokój z jednymi drzwiami”, „Pokój z dwojgiem drzwi” oraz z „pokoju”.

W grze (graficznej), nad którą ostatnio pracowałem, pomyślałem, że nauczyłem się mojej lekcji i ograniczyłem korzystanie z dziedziczenia. Zauważyłem jednak, że problemy wkrótce zaczynają się pojawiać. Moja klasa root zaczęła coraz bardziej wzdychać, a moje klasy liści były pełne zduplikowanych kodów.

Myślałem, że nadal robię coś źle i po sprawdzeniu tego w Internecie odkryłem, że nie tylko ja miałem ten problem. W ten sposób odkryłem systemy Entity po dokładnych badaniach (czytaj: googlefu)

Kiedy zacząłem czytać o tym, mogłem zobaczyć, jak wyraźnie było w stanie rozwiązać problemy, które miałem z tradycyjną hierarchią OOP z komponentami. Były to jednak pierwsze czytania. Kiedy natknąłem się więcej ... „radykalne” ES podejść, takich jak ten, w T-maszyna .

Zacząłem nie zgadzać się z metodami, których używali. System czysto komponentowy wydawał się albo przesadny, albo raczej nieintuicyjny, co prawdopodobnie jest siłą OOP. Autor posunął się nawet do stwierdzenia, że ​​system ES jest przeciwieństwem OOP i chociaż może on być użyteczny podczas OOP, tak naprawdę nie powinien. Nie twierdzę, że to źle, ale po prostu nie czułem się rozwiązaniem, które chciałbym wdrożyć.

Więc dla mnie i rozwiązanie problemów, które miałem na początku postu, bez naruszania moich intuicji, to nadal używać hierarchii, jednak nie będzie to monolityczna hierarchia, jak te, które stosowałem wcześniej, ale raczej polilityczny (nie mogłem znaleźć słowa przeciwnego do monolitycznego), który składa się z kilku mniejszych drzew.

Poniższy przykład pokazuje, co mam na myśli (zainspirowany przykładem, który znalazłem w Game Engine Architecture, rozdział 14).

Chciałbym mieć małe drzewo na pojazdy. Główna klasa pojazdu miałaby komponent renderujący, komponent kolizji, komponent pozycji itp.

Następnie czołg, podklasa pojazdu, odziedziczyłaby po nim te komponenty i otrzymałaby własny komponent „armaty”.

To samo dotyczy postaci. Postać miałaby własne komponenty, a następnie Klasa Gracza odziedziczyłaby ją i otrzymałaby kontroler Wejścia, podczas gdy inne klasy wroga odziedziczyłyby po klasie Postaci i otrzymałyby kontroler AI.

Tak naprawdę nie widzę żadnych problemów z tym projektem. Pomimo braku używania czystego systemu kontrolera encji, problem z efektem bulgotania, a duża klasa root jest rozwiązana za pomocą hierarchii wielu drzew, a problem ciężkich, powielających kod listków zniknął, ponieważ liście nie mieć na początku dowolny kod, tylko komponenty. Jeśli trzeba dokonać zmiany na poziomie liścia, jest to tak proste, jak zmiana pojedynczego komponentu, zamiast kopiowania wklejania kodu wszędzie.

Oczywiście będąc tak niedoświadczonym jak ja, nie widziałem żadnych problemów, kiedy po raz pierwszy zacząłem używać modelu z pojedynczą hierarchią i dziedziczenia, więc jeśli pojawią się problemy z modelem, który obecnie zamierzam wdrożyć, nie być w stanie to zobaczyć.

Twoje opinie?

PS: Korzystam z Javy, więc używanie wielokrotnego dziedziczenia do implementacji tego zamiast używania normalnych komponentów nie jest możliwe.

PPS: Komunikacja między komponentami będzie realizowana poprzez łączenie wzajemnie zależnych komponentów. Doprowadzi to do sprzężenia, ale myślę, że jest to dobra kompromis.


2
To wszystko wydaje mi się w porządku. Czy jest jakiś konkretny przykład w twojej hierarchii lub systemie encji, który „wącha” ciebie? Ostatecznie chodzi o to, aby gra była czymś więcej niż tylko poczuciem czystości.
drhayes,

Nie mam, ale tak jak powiedziałem, ledwo mam doświadczenie i jestem daleki od najlepszej oceny tego.
Midori Ryuu,

3
Głosowałem za zamknięciem tego pytania, ponieważ jest ono subiektywne i otwarte na szeroką dyskusję. Doceniam to, że został poproszony ze szczerego stanowiska - ale wybór sposobu postępowania tutaj jest całkowicie kwestią osobistej opinii. Jedynym prawdziwym minusem utrwalenia komponentów w podklasie jest to, że rozmieszczenie komponentów nie jest zmienne w czasie wykonywania - ale z pewnością musi to być dla ciebie oczywiste i jest to twój wybór.
Kylotan,

4
Głosowałem również za zamknięciem. To dobre i dobrze napisane pytanie, ale nie odpowiednie dla GDSE. Możesz spróbować opublikować to ponownie na GameDev.net.
Sean Middleditch,

Jak napisano, pytanie brzmi „Twoje opinie?”, Jest rzeczywiście otwarte i niemożliwe do odpowiedzi. Jednakże, jest odpowiedzialny, jeśli nie odpowiedzieć jak gdyby pytanie brzmiało "Czy istnieją jakieś rozwarte pułapki, że mogę wpaść bez zauważenia? (Przynajmniej jeśli uważasz, że w rzeczywistości istnieje jakakolwiek różnica empiryczna między różnymi architekturami.)
John Calsbeek

Odpowiedzi:


8

Rozważ ten przykład:

Robisz RTS. Zgodnie z kompletną logiką decydujesz się stworzyć klasę podstawową GameObject, a następnie dwie podklasy Buildingi Unit. To oczywiście działa dobrze i kończy się coś, co wygląda następująco:

  • GameObject
    • ModelComponent
    • CollisionComponent
  • Building
    • ProductionComponent
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent

Teraz każda Unittwoja podklasa zawiera już wszystkie potrzebne komponenty. I jako bonus, masz jedno miejsce, aby umieścić cały ten żmudny kod instalacyjny, który newjest WeaponComponentpołączony z AimingAIComponenti - wspaniały ParticleEmitterComponent!

I oczywiście, ponieważ nadal rozkłada logikę na komponenty, kiedy ostatecznie zdecydujesz, że chcesz dodać Towerklasę, która jest budynkiem, ale ma broń, możesz nią zarządzać. Możesz dodać an AimingAIComponent, a WeaponComponenti a ParticleEmitterComponentdo swojej podklasy Building. Oczywiście, musisz przejść i wykopać kod inicjujący z Unitklasy i umieścić go gdzie indziej, ale to nie jest wielka strata.

A teraz, w zależności od tego, ile miałeś dalekowzroczności, możesz, ale nie musi, mieć subtelne błędy. Okazuje się, że gdzieś w grze mógł być jakiś kod, który wyglądał tak:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

To po cichu nie działa w twoim Towerbudynku, nawet jeśli naprawdę chciałeś, żeby działało z czymkolwiek z bronią. Teraz musi wyglądać mniej więcej tak:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

Dużo lepiej! Po zmianie tego przychodzi Ci do głowy, że dowolna z napisanych przez ciebie metod dostępu może skończyć się w tej sytuacji. Najbezpieczniej jest więc usunąć je wszystkie i uzyskać dostęp do każdego komponentu za pomocą tej jednej getComponentmetody, która może współpracować z dowolną podklasą. (Alternatywnie możesz dodać każdy rodzaj get*Component()do nadklasy GameObjecti przesłonić je w podklasach, aby zwrócić komponent. Przyjmę jednak pierwszą.)

Teraz twoje Buildingi Unitklasy są właściwie tylko workami komponentów, nawet bez akcesoriów. Nie ma też żadnej fantazyjnej inicjalizacji, ponieważ większość z nich musiała zostać wyciągnięta, aby uniknąć powielania kodu w przypadku jednego innego obiektu specjalnego.

Jeśli podążasz za tym do skrajności, zawsze kończysz się tym, że Twoje podklasy stają się całkowicie obojętnymi workami komponentów, w którym to momencie nie ma nawet żadnego punktu, aby mieć podklasy wokół siebie. Prawie wszystkie korzyści można uzyskać dzięki hierarchii klas z hierarchią metod, która tworzy właściwe komponenty. Zamiast mieć Unitklasę, masz addUnitComponentsmetodę, która wykonuje wywołania AnimationComponenti, a także addWeaponComponentstworzy an AimingAIComponent, a WeaponComponenti a ParticleEmitterComponent. W rezultacie masz wszystkie zalety systemu encji, całe ponowne użycie kodu w hierarchii i brak pokusy, by sprawdzić instanceoflub rzucić na typ klasy.


Dobrze powiedziane. Dodając do tego, myślę, że idealnym rozwiązaniem jest zainicjowanie każdej jednostki za pomocą skryptu lub plików deskryptora. Używam plików tekstowych do inicjowania encji. Jest szybki i umożliwia programistom testowanie różnych parametrów.
Coyote

Wow, czytanie twojego postu dało mi koszmar! Oczywiście mam na myśli to w dobry sposób, widząc, jak otworzyłeś moje oczy, z jakim koszmarem może się skończyć! Chciałbym móc odpowiedzieć na tak szczegółową odpowiedź, ale tak naprawdę nie ma wiele do dodania!
Midori Ryuu,

W prostszy sposób. Kończysz używanie Dziedzictwa jako wzoru fabrycznego biedaka.
Zardoz89,

2

Składanie nad dziedziczeniem to terminologia OOP (przynajmniej sposób, w jaki się nauczyłem / uczyłem). Aby wybrać kompozycję i dziedziczenie, mówisz na głos: „Samochód jest rodzajem koła” (dziedziczenie) i „Samochód ma koła” (kompozycja). Jeden powinien brzmieć bardziej poprawnie niż drugi (w tym przypadku kompozycja), a jeśli nie możesz zdecydować, wybierz kompozycję, dopóki nie zdecydujesz inaczej.


1

Jeśli chodzi o integrację systemów opartych na komponentach z istniejącymi grami opartymi na obiektach gier, jest artykuł na temat programowania kowbojów http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/, który może dać ci wskazówki na temat sposób, w jaki można dokonać przejścia.

Jeśli chodzi o problemy, Twój model nadal będzie musiał traktować gracza inaczej niż innego wroga. Nie będziesz w stanie zmienić statusu (tj. Gracz staje się kontrolowany przez AI), gdy gracz rozłącza się lub w szczególnych przypadkach bez obchodzenia się z nim inaczej niż inne postacie.

Oprócz częstego ponownego wykorzystywania komponentów przez różne podmioty, ideą gier opartych na komponentach jest jednolitość całego systemu. Każda jednostka ma komponenty, które można dowolnie podłączać i odłączać. Wszystkie komponenty tego samego rodzaju są obsługiwane dokładnie tak samo, dzięki zestawowi wywołań utworzonych przez interfejs (komponent podstawowy) każdego rodzaju (pozycja, renderowany, skrypt ...)

Łączenie dziedziczenia obiektów gry z podejściem opartym na komponentach daje pewne zalety podejścia opartego na komponentach z wadami obu podejść.

To może Ci pomóc. Ale nie chcę mieszać obu poza okresem przejściowym z jednej architektury do drugiej.

PS napisane na telefonie, więc trudno mi połączyć się z zasobami zewnętrznymi.


Dziękuję za odpowiedź, mimo że rozmawiałeś przez telefon! Rozumiem co masz na myśli. Są rzeczy oparte na komponencie ruchu, tym większą elastyczność muszę zmieniać. To jest powód, dla którego zamierzam iść dalej i spróbować podejścia do kompozycji z minimalnym dziedzictwem. (Nawet więcej niż ten, który zaproponowałem.)
Midori Ryuu,

@MidoriRyuu unikaj wszelkiego dziedziczenia w obiektach encji. Masz szczęście, że używasz języka z wbudowanym odbiciem (i introspekcją). Możesz użyć plików (txt lub XML), aby zainicjować swoje obiekty za pomocą odpowiednich parametrów. To nie jest zbyt dużo pracy i pozwoli na lepsze dostrojenie gry bez ponownej kompilacji. Ale zachowaj klasę Entity, przynajmniej do komunikacji między komponentami i innych zadań użyteczności publicznej. PS wciąż na telefonie :(
Coyote
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.