Tworzenie encji jako agregacji

10

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?

Jamie Dixon
źródło
Jeśli nadal jesteś zainteresowany, mogę wysłać ci mój system małych jednostek w języku C #. To nie jest niesamowite , ale działa.
Kaczka komunistyczna

Odpowiedzi:

6

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
3

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 przypadku Component. 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 (tj Update.). 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 Transformkomponentu obiektu gry (w celu kontrolowania położenia / obrotu / skali obiektu gry), zwykle musisz zrobić coś takiego this.GetComponent<Transform>().position, ale zawijają to w this.transform.positionwywołanie pomocnika . Innym częstym wzorem jest dostęp do Rendererkomponentu 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ś takiego this.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ą Transformkomponent, ale mogą także zawierać wbudowane typy, takie jak AudioListenerlub Renderer, 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ąć.

Tetrad
źródło