Próbuję zaprojektować oparty na komponentach system encji do celów uczenia się (a później używać go w niektórych grach) i mam problemy z aktualizacją stanów encji.
Nie chcę mieć metody update () wewnątrz komponentu, aby zapobiec zależnościom między komponentami.
Mam na myśli to, że komponenty przechowują dane i aktualizują systemy.
Tak więc, jeśli mam prostą grę 2D z niektórymi bytami (np. Gracz, wróg1, wróg2), które mają komponenty transformacji, ruchu, stanu, animacji i renderowania, myślę, że powinienem:
- MovementSystem, który przenosi wszystkie komponenty Movement i aktualizuje komponenty State
- A RenderSystem, który aktualizuje komponenty animacji (komponent animacji powinien mieć jedną animację (tj. Zestaw ramek / tekstur) dla każdego stanu, a jej aktualizacja oznacza wybranie animacji odpowiadającej bieżącemu stanowi (np. Skakanie, poruszanie się w lewo itp.), Oraz aktualizacja indeksu ramki). Następnie RenderSystem aktualizuje komponenty Renderowania teksturą odpowiadającą bieżącej ramce animacji każdego elementu i renderuje wszystko na ekranie.
Widziałem niektóre implementacje, takie jak framework Artemis, ale nie wiem, jak rozwiązać tę sytuację:
Powiedzmy, że moja gra ma następujące byty. Każda jednostka ma zestaw stanów i jedną animację dla każdego stanu:
- gracz: „bezczynny”, „moving_right”, „jumping”
- wroga1: „moving_up”, „moving_down”
- wroga2: „moving_left”, „moving_right”
Jakie są najbardziej akceptowane metody aktualizacji aktualnego stanu każdego podmiotu? Jedyne, co mogę wymyślić, to mieć osobne systemy dla każdej grupy podmiotów oraz osobne komponenty stanu i animacji, aby mieć PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... ale myślę, że to źle rozwiązanie i łamie cel posiadania systemu opartego na komponentach.
Jak widać, jestem tu całkiem zagubiony, więc bardzo doceniłbym każdą pomoc.
EDYCJA: Myślę, że rozwiązaniem, które sprawi, że będzie działać tak, jak zamierzam, jest to:
Sprawiasz, że statecomponent i animationcomponent są wystarczająco ogólne, aby można je było stosować dla wszystkich jednostek. Zawarte w nich dane będą modyfikatorem do zmiany rzeczy, takich jak odtwarzane animacje lub dostępne stany. - Bajt56
Teraz próbuję wymyślić, jak zaprojektować te 2 komponenty na tyle ogólne, aby móc je ponownie wykorzystać. Czy posiadanie identyfikatora UID dla każdego stanu (np. Chodzenie, bieganie ...) i przechowywanie animacji na mapie w komponencie animacji kluczowanym przez ten identyfikator może być dobrym rozwiązaniem?
źródło
statecomponent
ianimationcomponent
tyle ogólne, które mają być stosowane do wszystkich podmiotów. Zawarte w nich dane będą modyfikatorem do zmiany rzeczy, takich jak odtwarzane animacje lub dostępne stany.Odpowiedzi:
IMHO
Movement
komponent powinien zachować bieżący stan (Movement.state
), aAnimation
komponent powinien odpowiednio obserwować zmianyMovement.state
i aktualizować swoją bieżącą animację (Animation.animation
), korzystając z prostego wyszukiwania identyfikatora stanu do animacji (zgodnie z sugestią na końcu OP). Oczywiście toAnimation
będzie zależeć od tegoMovement
.Alternatywną strukturą byłoby posiadanie ogólnego
State
komponentu, któryAnimation
obserwuje iMovement
modyfikuje, który jest w zasadzie kontrolerem widoku modelu (w tym przypadku ruch animacji stanu).Inną alternatywą byłoby wysłanie przez jednostkę zdarzenia do jej składników, gdy zmieni się jej stan.
Animation
wysłucha tego wydarzenia i odpowiednio zaktualizuje jego animację. Eliminuje to zależność, chociaż można argumentować, że wersja zależna jest bardziej przejrzysta.Powodzenia.
źródło
Movement
będzie kontrolowaćState
(nie obserwować). Ostatni przypadek: tak,Movement
zrobiłby toentity.dispatchEvent(...);
mniej więcej, a wszystkie inne komponenty nasłuchujące tego typu zdarzenia otrzymają to. Wydajność jest oczywiście gorsza niż zwykłe wywołania metod, ale niewiele. Możesz na przykład łączyć obiekty zdarzeń. Przy okazji, nie musisz używać encji jako „węzła zdarzeń”, możesz również użyć dedykowanej „magistrali zdarzeń”, pozostawiając całkowicie klasę encji.Jeśli chodzi o twój problem, jeśli STAN jest używany tylko w animacjach, nie musisz nawet ujawniać tego innym komponentom. Jeśli ma więcej niż jedno zastosowanie, musisz je ujawnić.
Opisany przez ciebie system Komponentów / Podsystemów jest bardziej oparty na hierarchii niż na komponentach. W końcu to, co opisujesz jako komponenty, to w rzeczywistości struktury danych. Nie oznacza to, że jest to zły system, po prostu myślę, że nie pasuje on zbyt dobrze do podejścia opartego na komponentach.
Jak zauważyłeś, zależności są dużym problemem w systemach opartych na komponentach. Istnieją różne sposoby radzenia sobie z tym. Niektóre wymagają, aby każdy składnik deklarował swoje zależności i przeprowadzał dokładną kontrolę. Inni pytają o komponenty implementujące określony interfejs. Jeszcze inni przekazują odniesienia do zależnych komponentów, gdy tworzą one każdą z nich.
Niezależnie od używanej metody, będziesz potrzebować pewnego rodzaju GameObject, który będzie działał jako zbiór Komponentów. To, co zapewnia GameObject, może się bardzo różnić i możesz uprościć zależności między komponentami, przenosząc często używane dane na poziom GameObject. Unity robi to np. Z transformacją, zmuszając wszystkie obiekty gry do posiadania jednego.
Jeśli chodzi o problem poprosić o różnych stanach / animacji dla różnych obiektów do gier, oto co mi zrobi. Po pierwsze, nie byłbym zbyt fantazyjny na tym etapie wdrażania: wdrażaj tylko to, czego potrzebujesz teraz, aby działało, a następnie dodawaj dzwonki i gwizdy, gdy są potrzebne.
Zacznę od komponentu „State”: PlayerStateComponent, Enemy1State, Enemy2State. Komponent stanu zająłby się zmianą stanu w odpowiednim czasie. Stan to coś, co będą miały wszystkie twoje obiekty, dzięki czemu może znajdować się w GameObject.
Potem byłby AnimationCompoment. Miałoby to słownik animacji wpisanych do stanu. W update () zmień animację, jeśli zmieni się stan.
Jest świetny artykuł o budowaniu frameworka, którego nie mogę znaleźć. Mówi się, że jeśli nie masz doświadczenia w domenie, powinieneś wybrać jeden problem i wykonać najprostszą implementację, która rozwiązuje bieżący problem . Następnie dodajesz kolejny problem / przypadek użycia i rozwijasz frameworki, dzięki czemu rośnie ono organicznie. Bardzo podoba mi się to podejście, szczególnie podczas pracy z nową koncepcją.
Implementacja, którą zaproponowałem, jest dość naiwna, ale oto kilka możliwych ulepszeń w miarę dodawania kolejnych przypadków użycia:
źródło
Oprócz odpowiedzi ADB możesz użyć http://en.wikipedia.org/wiki/Dependency_injection , co pomaga, gdy trzeba zbudować wiele komponentów, przekazując je jako odniesienia do ich konstruktorów. Oczywiście nadal będą od siebie zależeć (jeśli jest to wymagane w bazie kodu), ale całą tę zależność można umieścić w jednym miejscu, w którym konfigurowane są zależności, a reszta kodu nie musi wiedzieć o zależności.
Takie podejście działa również dobrze, jeśli używasz interfejsów, ponieważ każda klasa komponentów żąda tylko tego, czego potrzebuje lub gdzie musi zostać zarejestrowana, a tylko struktura wstrzykiwania zależności (lub miejsce, w którym skonfigurowałeś wszystko, zwykle aplikacja) wie, kto potrzebuje .
W przypadku prostych systemów, w których możesz uciec bez użycia DI lub czystego kodu, Twoje klasy RenderingSystem brzmią tak, jakbyś musiał wywoływać je statycznie lub przynajmniej mieć je dostępne w każdym komponencie, co właściwie czyni je zależnymi i trudnymi do zmiany. Jeśli jesteś zainteresowany bardziej czystym podejściem, sprawdź powyższe linki DI wiki link i przeczytaj o Clean Code: http://clean-code-developer.com/
źródło