Jest to kontynuacja tego pytania, na które odpowiedziałem, ale to dotyczy bardziej szczegółowego tematu.
Ta odpowiedź pomogła mi zrozumieć Entity Systems nawet lepiej niż ten artykuł.
Przeczytałem (tak,) artykuł o systemach Entity Systems i powiedział mi, co następuje:
Encje to tylko identyfikator i tablica komponentów (artykuły mówią, że przechowywanie encji w komponentach nie jest dobrym sposobem robienia rzeczy, ale nie stanowi alternatywy).
Składniki to fragmenty danych, które wskazują, co można zrobić z określonym bytem.
Systemy są „metodami”, które manipulują danymi na bytach.
Wydaje się to bardzo praktyczne w wielu sytuacjach, ale niepokoi mnie to, że komponenty są tylko klasami danych. Na przykład, jak mogę zaimplementować moją klasę Vector2D (Pozycja) w systemie Entity?
Klasa Vector2D przechowuje współrzędne data: xiy, ale ma także metody , które są kluczowe dla jej użyteczności i odróżniają klasę od tablicy tylko dwóch elementów. Przykładowe metody są: add()
, rotate(point, r, angle)
, substract()
, normalize()
i wszystkie inne standardowe, użyteczny i nie jest to absolutnie konieczne metody, które pozycje (które są instancje klasy Vector2D) powinna posiadać.
Gdyby komponent był tylko posiadaczem danych, nie byłby w stanie mieć tych metod!
Jednym z rozwiązań, które prawdopodobnie mogłoby się pojawić, byłoby wdrożenie ich w systemach, ale wydaje się to bardzo sprzeczne z intuicją. Te metody to rzeczy, które chcę teraz wykonać, aby były kompletne i gotowe do użycia. Nie chcę czekać na MovementSystem
przeczytanie jakiegoś drogiego zestawu komunikatów, które nakazują mu wykonać obliczenia dotyczące pozycji bytu!
I artykuł bardzo wyraźnie stwierdza, że tylko systemy powinny mieć jakąkolwiek funkcjonalność, a jedynym wyjaśnieniem tego, co mogłem znaleźć, było „unikanie OOP”. Przede wszystkim nie rozumiem, dlaczego powinienem powstrzymywać się od stosowania metod w jednostkach i komponentach. Narzut pamięci jest praktycznie taki sam, a w połączeniu z systemami powinny być bardzo łatwe do wdrożenia i łączenia w interesujący sposób. Na przykład systemy mogą zapewniać podstawową logikę tylko jednostkom / komponentom, które same znają implementację. Jeśli mnie o to zapytasz - to w zasadzie pobieranie korzyści zarówno z ES, jak i OOP, czego według autora artykułu nie można zrobić, ale wydaje mi się, że to dobra praktyka.
Pomyśl o tym w ten sposób; w grze istnieje wiele różnych typów obiektów do rysowania. Plain Old obrazy, animacje ( update()
, getCurrentFrame()
itp), kombinacje tych typów pierwotnych, a wszystkie z nich może po prostu stanowić draw()
metodę do systemu render, który wtedy nie trzeba się martwić o jak sprite podmiotu jest realizowany tylko na temat interfejsu (rysowanie) i pozycji. A potem potrzebowałbym tylko systemu animacji, który nazwałby metody specyficzne dla animacji, które nie mają nic wspólnego z renderowaniem.
I jeszcze jedna rzecz ... Czy naprawdę istnieje alternatywa dla tablic, jeśli chodzi o przechowywanie komponentów? Nie widzę innego miejsca do przechowywania komponentów niż tablice wewnątrz klasy Entity ...
Być może jest to lepsze podejście: przechowuj komponenty jako proste właściwości jednostek. Na przykład element pozycji byłby przyklejony entity.position
.
Tylko inny sposób byłoby mieć jakąś dziwną tabeli odnośników wewnątrz systemów, która odwołuje różne podmioty. Ale wydaje się to bardzo nieefektywne i bardziej skomplikowane do opracowania niż zwykłe przechowywanie komponentów w bycie.
Odpowiedzi:
Myślę, że w porządku jest mieć proste metody uzyskiwania dostępu do danych w komponentach, ich aktualizowania lub manipulowania nimi. Myślę, że funkcjonalność, która powinna pozostać poza komponentami, to logiczna funkcjonalność. Funkcje narzędziowe są w porządku. Pamiętaj, że system element-jednostka jest jedynie wytyczną, a nie ścisłymi zasadami, których musisz przestrzegać. Nie staraj się ich przestrzegać. Jeśli uważasz, że bardziej sensowne jest to zrobić w jedną stronę, zrób to w ten sposób :)
EDYTOWAĆ
Aby to wyjaśnić, Twoim celem nie jest unikanie OOP . Byłoby to dość trudne w większości popularnych języków używanych obecnie. Próbujesz zminimalizować dziedziczenie , które jest dużym aspektem OOP, ale nie jest wymagane. Chcesz się pozbyć dziedziczenia typu Object-> MobileObject-> Creature-> Bipedal-> Human.
Jednak dziedziczenie jest w porządku! Masz do czynienia z językiem, na który ma duży wpływ dziedziczenie, bardzo trudno go nie używać. Na przykład możesz mieć
Component
klasę lub interfejs, który wszystkie inne komponenty rozszerzają lub implementują. To samo dotyczy twojejSystem
klasy. To sprawia, że jest o wiele łatwiej. Zdecydowanie polecam przyjrzeć się ramom Artemis . Jest open source i ma kilka przykładowych projektów. Otwórz te rzeczy i zobacz, jak to działa.W przypadku Artemidy byty są przechowywane w tablicy, proste. Jednak ich komponenty są przechowywane w tablicy lub tablicach (oddzielnie od encji). Tablica najwyższego poziomu grupuje tablicę niższego poziomu według typu komponentu. Dlatego każdy typ komponentu ma własną tablicę. Tablica niższego poziomu jest indeksowana według identyfikatora jednostki. (Teraz nie jestem pewien, czy zrobiłbym to w ten sposób, ale tak to się tutaj robi). Artemis ponownie wykorzystuje identyfikatory encji, więc maksymalny identyfikator encji nie będzie większy niż bieżąca liczba encji, ale nadal możesz mieć rzadkie tablice, jeśli komponent nie jest często używanym komponentem. W każdym razie nie będę tego zbytnio rozbierał. Ta metoda przechowywania bytów i ich komponentów wydaje się działać. Myślę, że byłby to świetny pierwszy krok przy wdrażaniu własnego systemu.
Jednostki i komponenty są przechowywane w osobnym menedżerze.
Wspomniana strategia polegająca na tym, że byty przechowują własne komponenty (
entity.position
), jest w pewnym sensie sprzeczna z kompozycją komponentu encji, ale jest całkowicie akceptowalna, jeśli uważasz, że jest to najbardziej sensowne.źródło
EntityManager
jak tam są przechowywane rzeczy.Ten „artykuł” nie jest tym, z którym się szczególnie zgadzam, więc myślę, że moja odpowiedź będzie nieco krytyczna.
Pomysł nie polega na tym, aby upewnić się, że nic w twoim programie nie jest niczym innym jak identyfikatorem encji, komponentem lub systemem - ma na celu zapewnienie, że dane encji i zachowanie są tworzone przez komponowanie obiektów, a nie przez użycie złożonego drzewa dziedziczenia lub gorzej umieść wszystkie możliwe funkcje w jednym obiekcie. Aby wdrożyć te komponenty i systemy, na pewno będziesz mieć normalne dane, takie jak wektory, które w większości języków najlepiej reprezentowane są jako klasa.
Zignoruj ten fragment w artykule, który sugeruje, że to nie jest OOP - jest tak samo OOP, jak każde inne podejście. Kiedy większość kompilatorów lub środowisk wykonawczych języka implementuje metody obiektowe, jest to w zasadzie jak każda inna funkcja, z wyjątkiem tego, że istnieje ukryty argument o nazwie
this
lubself
, który jest wskaźnikiem do miejsca w pamięci, w którym przechowywane są dane tego obiektu. W systemie opartym na komponentach można użyć identyfikatora encji, aby znaleźć, gdzie odpowiednie komponenty (a tym samym dane) są dla danej encji. Zatem identyfikator jednostki jest równoważny wskaźnikowi to / ja, a koncepcje są w zasadzie takie same, tylko nieco zmienione.Dobry. Metody to skuteczny sposób organizowania kodu. Ważną rzeczą, którą należy odrzucić od idei „unikaj OOP”, jest unikanie wszędzie używania dziedziczenia w celu rozszerzenia funkcjonalności. Zamiast tego podziel funkcjonalność na komponenty, które można łączyć, aby zrobić to samo.
Idea systemu opartego na komponentach polega na tym, że nie miałbyś dla nich osobnych klas, ale miałbyś pojedynczą klasę Object / Entity, a obraz byłby Object / Entity, który ma ImageRenderer, Animacje byłyby Object / Podmiot, który ma AnimationRenderer itp. Odpowiednie systemy będą wiedziały, jak renderować te komponenty, a więc nie będzie żadnej klasy bazowej z metodą Draw ().
Jasne, ale to nie działa dobrze z komponentami. Masz 3 możliwości:
Możesz przechowywać komponenty w systemie. Tablica nie jest problemem, ale tam, gdzie jest przechowywany komponent.
źródło
Wektor to dane. Funkcje są bardziej podobne do funkcji narzędziowych - nie są specyficzne dla tego wystąpienia danych, mogą być stosowane niezależnie do wszystkich wektorów. Dobrym sposobem myślenia jest: czy te funkcje można przepisać jako metody statyczne? Jeśli tak, to tylko narzędzie.
źródło