Pracowałem nad niektórymi projektami hobbystycznymi przez ostatnie 3-4 lata. Po prostu proste gry 2D i 3D. Ale ostatnio zacząłem większy projekt. W ciągu ostatnich kilku miesięcy próbowałem zaprojektować klasę obiektów gry, która może być podstawą wszystkich moich obiektów gry. Po wielu testach typu try & die zwróciłem się do Google'a, który szybko wskazał mi kilka plików PDF i PowerPoint w GDC. A teraz staram się zrozumieć obiekty gier oparte na komponentach.
Rozumiem, że silnik tworzy obiekt gry, a następnie dołącza różne komponenty, które obsługują takie rzeczy jak zdrowie, fizyka, tworzenie sieci i wszystko, co do nich zmuszasz. Ale nie rozumiem, skąd składnik X wie, czy Y zmienił stan obiektu. Na przykład, skąd PhysicsComponent wie, czy gracz żyje, ponieważ zdrowie kontrolowane jest przez HealthComponent ...? I w jaki sposób HealthComponent odgrywa „animację śmierci gracza”?
Miałem wrażenie, że było to coś takiego (w HealthComponent):
if(Health < 0) {
AnimationComponent.PlayAnimation("played-died-animation")
}
Ale z drugiej strony, skąd HealthComponent wie, że obiekt gry, do którego jest dołączony, ma dołączony AnimationComponent? Jedyne rozwiązanie, które tu widzę, to
Sprawdź, czy AnimationComponent jest dołączony, czy nie (w kodzie komponentu lub po stronie silnika)
Składniki wymagają innych elementów, ale wydaje się, że walczą z całym projektem elementów.
Napisz jak HealthWithAnimationComponent, HealthNoAnimationComponent i tak dalej, co znów wydaje się walczyć z całym pomysłem na komponent.
Odpowiedzi:
We wszystkich twoich przykładach jest okropny problem. Komponent zdrowia musi wiedzieć o każdym typie komponentu, który może wymagać reakcji na śmierć istoty. Dlatego żaden z twoich scenariuszy nie jest odpowiedni. Twoja istota ma składnik zdrowia. Ma komponent animacji. Ani nie polegaj na innych, ani o nich nie wiedz. Komunikują się za pośrednictwem systemu wiadomości.
Kiedy komponent zdrowia wykryje, że jednostka „umarła”, wysyła komunikat „umarłem”. Komponent animacji odpowiada za odpowiedź na ten komunikat, odtwarzając odpowiednią animację.
Składnik kondycji nie wysyła wiadomości bezpośrednio do komponentu animacji. Może transmituje go do każdego elementu w tym bycie, może do całego systemu; być może komponent animacji musi poinformować system przesyłania wiadomości, że jest zainteresowany komunikatami „Umarłem”. Istnieje wiele sposobów wdrożenia systemu przesyłania wiadomości. Bez względu na to, jak go implementujesz, chodzi o to, że komponent kondycji i komponent animacji nigdy nie muszą wiedzieć ani dbać o to, czy ten drugi jest obecny, a dodawanie nowych komponentów nigdy nie będzie wymagało modyfikacji istniejących, aby wysłać im odpowiednie wiadomości.
źródło
Sposób, w jaki Artemis rozwiązuje problem, polega na tym, aby nie przetwarzać elementów w Elementach. Komponenty zawierają tylko potrzebne dane. Systemy odczytują wiele typów komponentów i wykonują wszelkie niezbędne operacje.
Tak więc, w twoim przypadku, możesz mieć RenderSystem, który odczytuje w HealthComponent (i innych) i odtwarza kolejki odpowiednich animacji. Oddzielenie danych od funkcji w ten sposób ułatwia prawidłowe zarządzanie zależnościami.
źródło
W twoim kodzie możesz znaleźć sposoby (użyłem ich, być może istnieją inne sposoby), aby wiedzieć, czy obiekt zmienił stan:
W tym celu użyłem: 1. HasComponent funkcji GameObject, lub 2. kiedy dołączasz komponent, możesz sprawdzić zależności w jakiejś funkcji konstruującej, lub 3. Jeśli wiem na pewno, że obiekt ma ten komponent, po prostu go używam.
W niektórych artykułach przeczytałem, że w systemie Ideal komponenty nie zależą od siebie, ale w rzeczywistości tak nie jest.
Pisanie takich komponentów jest złym pomysłem. W mojej aplikacji stworzyłem komponent Zdrowie najbardziej niezależny. Teraz myślę o pewnym wzorcu obserwatora, który powiadamia subskrybentów o określonym wydarzeniu (np. „Hit”, „heal” itp.). Tak więc AnimationComponent musi sam decydować, kiedy odtwarzać animację.
Ale kiedy przeczytałem artykuł o CBES, zrobiło to na mnie wrażenie, więc jestem bardzo szczęśliwy, kiedy korzystam z CBES i odkrywam nowe możliwości.
źródło
Tak jak mówi Michael, Patrick Hughes i Blecki. Rozwiązaniem pozwalającym uniknąć po prostu przenoszenia problemu jest porzucenie ideologii, która powoduje problem.
To mniej OOD, a bardziej programowanie funkcjonalne. Kiedy zacząłem eksperymentować z projektowaniem opartym na komponentach, zauważyłem ten problem. Poszukałem jeszcze trochę i znalazłem „Functive Reactive Programming” jako rozwiązanie.
Teraz moje komponenty to nic innego jak zbiór zmiennych i pól opisujących jego obecny stan. Następnie mam kilka klas „System”, które aktualizują wszystkie istotne dla nich komponenty. Reaktywną część osiąga się, uruchamiając systemy w ściśle określonej kolejności. Zapewnia to, że niezależnie od tego, jaki System będzie następny w kolejce do przetwarzania i aktualizacji, oraz niezależnie od komponentów i podmiotów, które zamierza czytać i aktualizować, zawsze działa na aktualnych danych.
Jednak w pewnym sensie nadal można twierdzić, że problem znów się przesunął. Bo co, jeśli nie jest łatwo, w której kolejności muszą działać Twoje Systemy? Co się stanie, jeśli istnieją cykliczne relacje i to tylko kwestia czasu, zanim zaczniesz gapić się na bałagan instrukcji if-else i zamiany? To ukryta forma przesyłania wiadomości, nie? Na pierwszy rzut oka myślę, że to niewielkie ryzyko. Zwykle rzeczy są przetwarzane po kolei. Coś w stylu: Wejście gracza -> Pozycje jednostki -> Wykrywanie kolizji -> Logika gry -> Renderowanie -> Zacznij od nowa. W takim przypadku będziesz mieć jeden System dla każdego, zapewnisz każdemu Systemowi metodę update (), a następnie uruchomisz je kolejno w gameloopie.
źródło