2011-02-09 15 views
16

Pracuję nad małą grą napisaną w Javie (ale pytanie jest agnostyczne). Ponieważ chciałem zbadać różne wzorce projektowe, zostałem zawieszony na systemie Composite pattern/Entity (który pierwotnie czytałem o here i here) jako alternatywie dla typowego dziedziczenia o głębokiej hierarchii.Złożony układ wzorca/podmiotu i tradycyjny OOP

Teraz, po napisaniu kilku tysięcy linii kodu, jestem nieco zdezorientowany. Myślę, że rozumiem ten wzór i lubię go używać. Myślę, że to bardzo fajne i Starbucks-owski, ale wydaje się, że korzyść, którą zapewnia, jest nieco krótkotrwała i (co mnie irytuje) w dużym stopniu zależna od twojej ziarnistości.

Oto obraz z drugiego artykułu powyżej: enter image description here

Uwielbiam sposób obiekty (podmioty grze, czy jakkolwiek to nazwać) mają minimalny zestaw komponentów i wywnioskować, pomysł jest, że można napisać kod, który wygląda mniej więcej tak:

BaseEntity Alien = new BaseEntity(); 
BaseEntity Player = new BaseEntity(); 

Alien.addComponent(new Position(), new Movement(), new Render(), new Script(), new Target()); 
Player.addComponent(new Position(), new Movement(), new Render(), new Script(), new Physics()); 

.. co byłoby naprawdę miło ... ale w rzeczywistości, kod kończy się szuka czegoś takiego

BaseEntity Alien = new BaseEntity(); 
BaseEntity Player = new BaseEntity(); 

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer()); 

Wygląda na to, że mam kilka bardzo wyspecjalizowanych komponentów, które składają się z mniejszych komponentów. Często muszę tworzyć komponenty, które mają zależności z innymi komponentami. W końcu, jak możesz renderować, jeśli nie masz pozycji? Nie tylko to, ale sposób w jaki grasz przeciw obcemu kontra granat może być zasadniczo różny. Nie możesz mieć JEDNEGO komponentu, który dyktuje wszystkie trzy, chyba że stworzysz bardzo duży komponent (w takim przypadku ... dlaczego i tak używasz złożonego wzoru?)

Podaj kolejny przykład z życia wzięty. Mam w mojej grze postacie, które mogą wyposażyć różne przedmioty. Gdy sprzęt jest wyposażony, niektóre statystyki są zmieniane, a także wyświetlane na ekranie. Oto co mój kod wygląda teraz:

billy.addControllers(new Movement(), new Position(), new CharacterAnimationRender(), new KeyboardCharacterInput()); 

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY); 
billy.get(CharacterAnimationRender.class).setFace(FACE.BLUSH_FACE); 
billy.get(CharacterAnimationRender.class).setHair(HAIR.RED_HAIR); 
billy.get(CharacterAnimationRender.class).setDress(DRESS.DRAGON_PLATE_ARMOR); 

Powyższy CharacterAnimationRender.class wpływa tylko to, co wyświetlane wizualnie. Tak więc wyraźnie potrzebuję stworzyć kolejny komponent, który będzie obsługiwał statystyki biegów. Jednak dlaczego miałbym zrobić coś takiego:

billy.addControllers(new CharacterStatistics()); 

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY); 
billy.get(CharacterStatistics.class).setBodyStats(BODY_STATS.NORMAL_BODY); 

Kiedy mogę po prostu zrobić CharacterGearStuff kontrolera/komponent, który obsługuje zarówno dystrybucję statystykach, a także zmiany wizualne?

Podsumowując, nie jestem pewna, w jaki sposób ma to pomóc w zwiększaniu produktywności, ponieważ jeśli nie chcesz zajmować się wszystkim ręcznie, musisz nadal tworzyć "meta-komponenty", które zależą od 2 składników (i modyfikują/krzyżują zmodyfikuj wszystkie ich podkomponenty - przywracając nas do OOP). A może myślę o tym zupełnie nie tak. Czy ja jestem?

Odpowiedz

5

Wygląda na to, że nieco źle zrozumiałeś wzór komponentu.

Komponenty to TYLKO dane, bez kodu. Jeśli masz kod w swoim komponencie, to już nie jest komponent - jest to coś bardziej złożonego.

Tak więc, na przykład, powinny być w stanie podzielić się trywialnie CharacterAnimationRender i CharacterStatistics, np

CharacterStats { int BODY } 
CharacterGameStats { ...not sure what data you have that affects gameplay, but NOT the rendering... } 
CharacterVisualDetails { int FACE, int HAIR } 

... ale jest ma potrzeby dla nich, aby zdawać sobie sprawę z istnienia każdego inny. W chwili, gdy mówisz o "zależnościach" między komponentami, podejrzewam, że się zgubiłeś.W jaki sposób jedna struktura int "zależy od" innej struktury ints? Nie mogą. To tylko porcje danych.

...

Wracając do twoich obaw na początku, że możesz skończyć z:

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer()); 

... to jest doskonały. PONIEWAŻ, że poprawnie napisałeś te komponenty, podzieliłeś dane na małe fragmenty, które są łatwe do odczytania/debugowania/edycji/kodowania.

Przeprowadza się jednak domysły, ponieważ nie określono składników wewnątrz tych elementów ... np. AlienAIMovement - co jest w tym? Zwykle oczekuję, że będziesz miał "AIMovement()", a następnie zmodyfikujesz go tak, by stał się wersją obcego, np. zmień niektóre flagi wewnętrzne w tym komponencie, aby wskazać, że używają funkcji "Obcy" w twoim systemie sztucznej inteligencji.

+0

Cześć Adam, dziękuję za odpowiedź na to pytanie: D - właściwie podzieliłem dane i logikę mojego kodu (tak jak wspominasz na swoim blogu). W rzeczywistości, 'Position()', 'Movement()', itp. To to, co nazywam kontrolerami, które "zależą" od modeli danych (które można nazwać komponentami, jak sądzę, które są prostymi klasami, które składają się TYLKO z prywatnych elementy danych (w przypadku pozycji, 'private double x;' i 'private double y;') - wszystkie te powiązania wykonuję wewnętrznie dla kontrolerów. –

1

Myślę, że używasz tutaj niewłaściwego podejścia. Wzory powinny być dostosowane do twoich potrzeb, a nie odwrotnie. Prostota jest zawsze lepsza niż złożona, a jeśli czujesz, że coś nie działa poprawnie, oznacza to, że powinieneś odejść kilka kroków i być może zacząć od samego początku.

Dla mnie to ma zapach kodowej już:

BaseEntity Alien = new BaseEntity(); 
Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 

spodziewałbym kodu obiektowego na tym, by wyglądać tak:

Alien alien = new AlienBuilder() 
    .withPosition(10, 248) 
    .withTargetStrategy(TargetStrategy.CLOSEST) 
    .build(); 

//somewhere in the main loop 
Renderer renderer = getRenderer(); 
renderer.render(alien); 

Podczas korzystania z klas generycznych dla wszystkich encje, będziesz miał bardzo ogólny i trudny w użyciu interfejs API do obsługi twoich obiektów.

Poza tym, czuje się źle, że takie rzeczy jak Pozycja, Ruch i Renderer pod tym samym parasolem Komponent. Pozycja nie jest składnikiem, jest atrybutem. Ruch to zachowanie, a Renderer to coś, co w ogóle nie jest związane z twoim modelem domeny, jest częścią podsystemu graficznego. Komponentem może być koło do samochodu, części karoserii i karabinów dla kosmitów.

Tworzenie gier jest bardzo wyrafinowaną techniką i naprawdę ciężko jest ją wykonać od razu. Przepisz swój kod od zera, ucz się na błędach i poczuj, co robisz, a nie tylko próbuj dopasować wzór z jakiegoś artykułu. A jeśli chcesz uzyskać lepsze wzorce, powinieneś spróbować czegoś innego niż tworzenie gier. Napisz na przykład prosty edytor tekstu.

+5

Nie sądzę, że elementy/elementy gry mają być obiektowo zorientowane per se, mają one być oparte na danych. Przynajmniej z tego, co zebrałem czytając pierwszy artykuł powyżej. Czytałem również wiele kodu open-source, który implementował wzorzec systemowy/komponentowy system (http://gamadu.com/temp/es.zip, http://code.google.com/p/spartanframework/ i więcej) i wydawało się, że wdrażam to w ten sam sposób. –

+0

"Przepisz swój kod od zera" to prawie zawsze straszna rada. Jest także jasne, że nie znasz schematu ECP. – vaughandroid

2

David,

Najpierw dziękuję za doskonałe pytanie.

Rozumiem Twój problem i myślę, że nie użyłeś poprawnie wzoru. Przeczytaj ten artykuł: http://en.wikipedia.org/wiki/Composite_pattern

Jeśli na przykład nie możesz wdrożyć ogólnego ruchu klasowego i potrzebujesz AlienAIMovement i KeyboardMovement, prawdopodobnie powinieneś użyć wzorca Odwiedzającego. Ale zanim zaczniesz refaktoryzować tysiące linii kodu, sprawdź, czy możesz wykonać następujące czynności.

Czy jest szansa napisania ruchu klasowego, który akceptuje parametr typu BaseEntity? Prawdopodobnie różnica między wszystkimi implementacjami Ruchu jest tylko parametrem, flagą lub tak? W tym przypadku kod będzie wyglądać następująco:

Alien.addComponent(new Position(), new Movement(Alien), new Render(Alien), new Script(Alien), new Target());

myślę, że nie jest tak źle.

Jeśli nie jest to możliwe, staramy się tworzyć instancje użyciu fabryki, więc

Alien.addComponent(f.createPosition(), f.createMovement(Alien), f.createRender(Alien), f.createRenderScript(Alien), f.createTarget());

Mam nadzieję, że moje sugestie pomóc.

+0

Podoba mi się idea "nowego ruchu (Alien)", ale myślę, że robienie tego w ten sposób łamie wzór systemu podmiotowego, przynajmniej czytając pierwszy artykuł i komentarze powyżej. –

2

Ents wydaje się być zaprojektowany, aby zrobić dokładnie to, co chcesz. Jeśli nadal chcesz mieć własną bibliotekę, możesz przynajmniej uczyć się od jej projektu.

Poprzednie odpowiedzi wydają się nieporęczne i tworzą mnóstwo niepotrzebnych obiektów, IMO.