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.
źródło
Odpowiedzi:
Rozważ ten przykład:
Robisz RTS. Zgodnie z kompletną logiką decydujesz się stworzyć klasę podstawową
GameObject
, a następnie dwie podklasyBuilding
iUnit
. 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
Unit
twoja podklasa zawiera już wszystkie potrzebne komponenty. I jako bonus, masz jedno miejsce, aby umieścić cały ten żmudny kod instalacyjny, którynew
jestWeaponComponent
połączony zAimingAIComponent
i - wspaniałyParticleEmitterComponent
!I oczywiście, ponieważ nadal rozkłada logikę na komponenty, kiedy ostatecznie zdecydujesz, że chcesz dodać
Tower
klasę, która jest budynkiem, ale ma broń, możesz nią zarządzać. Możesz dodać anAimingAIComponent
, aWeaponComponent
i aParticleEmitterComponent
do swojej podklasyBuilding
. Oczywiście, musisz przejść i wykopać kod inicjujący zUnit
klasy 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:
To po cichu nie działa w twoim
Tower
budynku, nawet jeśli naprawdę chciałeś, żeby działało z czymkolwiek z bronią. Teraz musi wyglądać mniej więcej tak: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
getComponent
metody, która może współpracować z dowolną podklasą. (Alternatywnie możesz dodać każdy rodzajget*Component()
do nadklasyGameObject
i przesłonić je w podklasach, aby zwrócić komponent. Przyjmę jednak pierwszą.)Teraz twoje
Building
iUnit
klasy 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ć
Unit
klasę, maszaddUnitComponents
metodę, która wykonuje wywołaniaAnimationComponent
i, a takżeaddWeaponComponents
tworzy anAimingAIComponent
, aWeaponComponent
i aParticleEmitterComponent
. W rezultacie masz wszystkie zalety systemu encji, całe ponowne użycie kodu w hierarchii i brak pokusy, by sprawdzićinstanceof
lub rzucić na typ klasy.źródło
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.
źródło
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.
źródło