Jak zaktualizować stany encji i animacje w grze opartej na komponentach?

10

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?

miviclin
źródło
Zakładam, że widziałeś to: zmiany stanu jednostek lub komponentów ? Czy twoje pytanie zasadniczo różni się od tego?
MichaelHouse
@ Byte56 Tak, przeczytałem to kilka godzin temu. Rozwiązanie, które zasugerowałeś, jest podobne do pomysłu, który tutaj przedstawiłem. Ale mój problem pojawia się, gdy StateComponent i AnimationComponent nie są takie same dla wszystkich podmiotów w systemie. Czy powinienem podzielić ten system na mniejsze systemy przetwarzające grupy podmiotów, które mają te same możliwe stany i animacje? (zobacz ostatnią część mojego oryginalnego postu dla lepszego wyjaśnienia)
miviclin,
1
Zrobić statecomponenti animationcomponenttyle 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.
MichaelHouse
Mówiąc o zależności, masz na myśli zależność danych lub zależność od kolejności wykonywania? Ponadto, w proponowanym przez Ciebie rozwiązaniu MovementSystem musi teraz wdrażać wszystkie różne sposoby przenoszenia czegoś? Wygląda na to, że łamie ideę systemu opartego na komponentach ...
ADB
@ ADB Mówię o zależności danych. Aby zaktualizować animację (np. Zmienić z animacji move_right na animację move_left) muszę znać aktualny stan encji i nie widzę, jak uczynić te 2 komponenty bardziej ogólnymi.
miviclin,

Odpowiedzi:

5

IMHO Movementkomponent powinien zachować bieżący stan ( Movement.state), a Animationkomponent powinien odpowiednio obserwować zmiany Movement.statei 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 to Animationbędzie zależeć od tego Movement.

Alternatywną strukturą byłoby posiadanie ogólnego Statekomponentu, który Animationobserwuje i Movementmodyfikuje, 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. Animationwysłucha tego wydarzenia i odpowiednio zaktualizuje jego animację. Eliminuje to zależność, chociaż można argumentować, że wersja zależna jest bardziej przejrzysta.

Powodzenia.

Torious
źródło
Animacja obserwuje stan, a państwo obserwuje ruch ... Zależności wciąż istnieją, ale mogę spróbować. Czy ostatnią alternatywą byłoby coś takiego: ruch powiadamia o zmianach w encji, a encja wysyła zdarzenie do stanu, a następnie ten sam proces powtarza się dla stanu i animacji? Jak to podejście może wpłynąć na wydajność?
miviclin,
Pierwszy przypadek: Movementbędzie kontrolować State (nie obserwować). Ostatni przypadek: tak, Movementzrobiłby to entity.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.
Torious
2

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:

  • zamień zmienną GameObject na słownik. Każdy składnik używa słownika do przechowywania wartości. (pamiętaj, aby odpowiednio obsługiwać kolizję ...)
  • zamiast tego zamień słownik zwykłych wartości na odwołania: class FloatVariable () {public value [...]}
  • Zamiast wielu składników stanu utwórz ogólny składnik StateComponent, w którym możesz budować maszyny o zmiennych stanach. Musisz mieć ogólny zestaw warunków, w których stan może się zmieniać: naciśnięcia klawiszy, wprowadzanie myszy, zmiany zmiennych (możesz powiązać to z FloatVariable powyżej).
ADB
źródło
To podejście działa, wdrożyłem coś podobnego rok temu, ale problem polega na tym, że prawie każdy komponent zależy od innych komponentów, więc wydaje mi się mniej elastyczny. Myślałem również o wrzuceniu najbardziej powszechnych komponentów (np. Transformacja, renderowanie, stan ...) do encji, ale myślę, że to łamie cel komponentów, ponieważ niektóre z nich są powiązane z encją, a niektóre encje mogą ich nie potrzebować. Dlatego staram się przeprojektować system, który będzie odpowiedzialny za aktualizację logiki, aby komponenty nie wiedziały o sobie, ponieważ same się nie aktualizują.
miviclin
0

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/

exDreamDuck
źródło
Mam już jeden system, w którym komponenty są od siebie całkowicie zależne. Wykorzystałem tam zastrzyk zależności i chociaż wolę to niż głębokie hierarchie, staram się stworzyć nowy, aby uniknąć sprzężenia komponentów, jeśli to możliwe. Nie dzwoniłbym do niczego statycznie. Chciałbym mieć ComponentManager, do którego każdy system ma dostęp (każdy system powinien mieć do niego odniesienie), a RendererSystem pobierałby wszystkie komponenty animacji z menedżera komponentów i renderowałby bieżący stan każdej animacji.
miviclin