Niedawno zapytałem o to, jak oddzielić byty od ich zachowania i główną odpowiedź związaną z tym artykułem: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Ostateczna koncepcja opisana tutaj jest następująca: OBIEKT JAKO CZYSTA AGREGACJA.
Zastanawiam się, jak mogę przejść do tworzenia jednostek gry jako czystej agregacji przy użyciu C #. Nie do końca zrozumiałem, jak to może działać. (Być może encja jest tablicą obiektów implementujących określony interfejs lub typ podstawowy?)
Moje obecne myślenie nadal wymaga posiadania konkretnej klasy dla każdego typu bytu, która następnie implementuje odpowiednie interfejsy (IMoveable, ICollectable, ISpeakable itp.).
Jak mogę przejść do tworzenia encji wyłącznie jako agregacji bez posiadania konkretnego typu dla tej encji?
źródło
Odpowiedzi:
Podejście „czystej agregacji” opisane przez Westa w tym powiązanym artykule całkowicie unika obiektu „bytu”. Istnieją komponenty, które unoszą się w pamięci, ale są ze sobą powiązane tylko, jeśli w ogóle, ukrytymi relacjami.
Jednym ze sposobów na to jest tak zwane podejście zaburtowe . W takim systemie komponenty znajdują się w posiadaniu systemów, które nimi zarządzają lub w inny sposób nimi kontrolują (używam tutaj terminu „zarządzaj”, ale nie należy tego rozumieć w ten sposób, że sugeruję, abyś miał kilka * klas menedżerów do przechowywania typy komponentów). Na przykład twój system fizyki może trzymać się kilku rzeczy reprezentujących każde sztywne ciało w jego świecie symulacji i może wystawiać te rzeczy jako PhysicsComponents. Komponenty mogą być rzeczywistymi obiektami obsługiwanymi przez dany podsystem lub mogą być serwerami proxy dla tych obiektów, zależnie od potrzeb.
W takim systemie klasa niekoniecznie musi przechowywać kolekcję odwołań do składników, które ją tworzą; zamiast tego pojawia się powiadomienie dotyczące utworzenia lub zniszczenia „jednostki”, a każdy podsystem, który obsługuje komponenty, przegląda opis utworzonej / zniszczonej jednostki (który zazwyczaj jest ładowany z niektórych danych) i określa, czy komponent jest do tego niezbędny.
Jedną z zalet tego podejścia jest to, że masz naprawdę dobrą lokalizację odniesienia dla każdego komponentu. Niestety jest to trochę dziwne, ogólnie rzecz biorąc, i nie jest to najbardziej przyjazny smak jednostek opartych na komponentach, jakie spotkałem. Czasami naprawdę wygodnie jest mieć prawdziwy obiekt, który reprezentuje byt, nawet jeśli obiekt ten robi niewiele więcej niż agreguje słabe odwołania do komponentów, które są nadal w posiadaniu innych podsystemów (jeśli nic innego nie zapewnia łatwego sposobu kierowania komunikatów między komponentami) .
Istnieje kilka dobrych sposobów wdrażania systemów obiektowych gier zorientowanych na komponenty; to naprawdę, naprawdę, naprawdę pomaga, jeśli masz solidne pojęcie o wymaganiach, które chcesz od swojego systemu - na przykład możesz zobaczyć, co robią popularne frameworki, takie jak Unity. Bez stawiania sobie surowych wymagań możesz napotkać problem niekończącego się „projektowania” systemu, nigdy tak naprawdę go nie budując, na próżno próbując trafić na idealną implementację. Z jakiegokolwiek powodu widziałem to bardzo często w systemach komponentów.
źródło
Sposób, w jaki robi to Unity, polega na tym, że wszystkie skrypty (w twoim przypadku, kod specyficzny dla typu obiektu gry) pochodzą z klasy podstawowej
MonoBehaviour
, która sama pochodzi z klasy bardziej dopasowanej do twojego przypadkuComponent
. Nigdy nie edytujesz (i nie masz dostępu do kodu) klasy GameObject.Obiekt gry odpowiada za przechowywanie wszystkich tych elementów
Component
. Jest również rzekomo odpowiedzialny za wywoływanie odpowiednich funkcji na nich (tjUpdate
.). Unity używa refleksji, aby określić, które funkcje wywołać (spójrz do sekcji „Funkcje zastępowalne” na tej stronie ), ale prawdopodobnie chciałbyś, aby były wirtualne.Jednym z mechanizmów używanych w Unity jest pobieranie komponentów do bieżącego obiektu gry (lub jego dzieci) według typu. Istnieje kilka właściwości pomocnika, które obejmują niektóre z typowych rzeczy. Na przykład, jeśli chcesz uzyskać dostęp do
Transform
komponentu obiektu gry (w celu kontrolowania położenia / obrotu / skali obiektu gry), zwykle musisz zrobić coś takiegothis.GetComponent<Transform>().position
, ale zawijają to wthis.transform.position
wywołanie pomocnika . Innym częstym wzorem jest dostęp doRenderer
komponentu bieżącego obiektu gry . Więc jeśli chcesz zrobić coś takiego jak zmienić materiał bieżącego obiektu gry, z innego skryptu możesz zrobić coś takiegothis.renderer.material = someOtherMaterial
, a Twój obiekt gry odpowiednio się zaktualizuje.Jednym ze sposobów, w jaki działa to w Unity, jest skonfigurowanie edytora w taki sposób, aby można było tworzyć obiekty gry w scenie, które mają już podłączone elementy. W przypadku Unity wszystkie obiekty w grze mają
Transform
komponent, ale mogą także zawierać wbudowane typy, takie jakAudioListener
lubRenderer
, które działają zgodnie z oczekiwaniami. Możesz też dodać własne komponenty, które robią wszystko, co chcesz. Edytor odsłania także publiczne / szeregowalne pola w twoich komponentach, więc nie musisz tworzyć różnych skryptów, jeśli chcesz używać tego samego podstawowego kodu, ale zmieniasz kilka magicznych liczb.Podsumowując, jest to całkiem sprytne i sugeruję pobranie bezpłatnej wersji Unity i zabawę się tym, jak skonfigurowany jest ich system skryptowy, jako całkiem niezły dowód koncepcji tego, co chcesz osiągnąć.
źródło
Podoba mi się podejście bardziej bliskie wizji Adama z bloga t-machine. Komponenty powinny być tylko danymi, a systemy wykonują z nimi całą robotę.
Zobacz przykład implementacji ES w C #: https://github.com/thelinuxlich/artemis_CSharp
I przykładowa gra z niego korzystająca (XNA 4): https://github.com/thelinuxlich/starwarrior_CSharp
źródło