Jak uniknąć „systemów Blob” w systemie komponentów bytu?

10

Obecnie mam do czynienia z następującym problemem:

Usiłuję napisać klon ponga przy użyciu systemu komponentu jednostki (ECS). „Schemat” napisałem sam. Jest więc klasa, która zarządza bytami wszystkimi składnikami. Są też same klasy komponentów. Na koniec są moje systemy, które po prostu pobierają wszystkie byty, które mają komponenty, których potrzebuje system.

Na przykład mój system ruchu szuka wszystkich bytów, które mają komponent pozycji i komponent ruchu. Komponent pozycji po prostu utrzymuje pozycję, a komponent ruchu utrzymuje prędkość.

Ale rzeczywistym problemem jest mój system kolizji. Ta klasa jest jak logiczny obiekt blob. W tej klasie mam tak wiele specjalnych przypadków.

Na przykład: Moje wiosła mogą kolidować z granicami. Jeśli tak się stanie, ich prędkość zostanie ustawiona na zero. Moja piłka równie dobrze może zderzyć się z granicami. Ale w tym przypadku jego prędkość jest tylko odzwierciedlona na normalnej granicy, więc jest odzwierciedlona. Aby to zrobić, nadałem piłce dodatkowy element fizyki, który mówi tylko: „Hej, to się nie kończy, odbija się”. Tak więc element fizyki nie ma rzeczywistych danych. Jest to pusta klasa, która jest po to, aby poinformować system, czy obiekt odbija się, czy zatrzymuje.

Potem pojawia się następująca zasada: chcę wyrenderować cząsteczki, gdy piłka zderzy się z łopatkami lub brzegami. Sądzę więc, że kula musi uzyskać inny element, który każe systemowi kolizji stworzyć cząstkę podczas zderzenia.
Następnie chcę mieć ulepszenia, które mogą kolidować z wiosłami, ale nie z granicami. Jeśli tak się stanie, ulepszenia muszą zniknąć. Potrzebowałbym więc znacznie więcej przypadków i komponentów (aby powiedzieć systemowi, że niektóre byty mogą kolidować tylko z niektórymi innymi, bot nie ze wszystkimi, nawet jeśli inne faktycznie są w stanie zderzyć się, ponadto system kolizji musiał zastosować ulepszenia do wiosła itp. itd. itd.).

Widzę, że system komponentu encji jest dobry, ponieważ jest elastyczny i nie masz problemów z dziedziczeniem. Ale obecnie utknąłem całkowicie.

Czy myślę zbyt skomplikowany? Jak mam poradzić sobie z tym problemem?

Jasne, muszę stworzyć systemy, które faktycznie są odpowiedzialne za „postkolizję”, więc system kolizji mówi tylko „Tak, mamy kolizję w ostatniej ramce”, a potem jest kilka systemów „po kolizji”, które wszystkie wymagają różnych (kombinacji) komponentów, a następnie zmieniają komponenty. Na przykład istniałby ruch po zderzeniu, który zatrzymuje rzeczy, które muszą się zatrzymać, gdy dojdzie do kolizji. Następnie fizyka-system po zderzeniu, który odbija rzeczy itp.

Ale nie wydaje mi się to również właściwym rozwiązaniem, ponieważ na przykład:

  1. Mój ruch po kolizji wymagałby bytów, które mają komponent pozycji, komponent ruchu i komponent kolizji. Wtedy ustawiłoby prędkość bytu na zero.
  2. System fizyki postkolizyjnej wymagałby bytów, które mają komponent pozycji, komponent ruchu, komponent zderzenia i komponent fizyki. Wtedy odzwierciedlałby wektor prędkości.

Problem jest oczywisty: ruch po zderzeniu wymaga bytów, które są podzbiorem bytów w fizycznym systemie po zderzeniu. Tak więc dwa systemy po zderzeniu działałyby na tych samych danych, czego efektem jest: Chociaż istota ma element fizyczny, po zderzeniu prędkość byłaby zerowa.

Jak te problemy są ogólnie rozwiązywane w systemie komponentów bytu? Czy te problemy są w ogóle zwykłe, czy robię coś złego? Jeśli tak, co i jak należy to zrobić?

M0rgenstern
źródło

Odpowiedzi:

11

Tak, myślisz zbyt skomplikowany.

Wygląda na to, że wiele problemów można rozwiązać za pomocą systemu przesyłania wiadomości i niektórych dodatkowych atrybutów, które pozwalają określić niektóre filtry , i wreszcie nie martwić się o to, że są tak rygorystyczni wobec podmiotów / komponentów.

Wiadomości pomogą ci w niektórych aspektach, takich jak wyzwalanie cząstek, ulepszenia i tak dalej. Na przykład, możesz mieć obiekt świata, który subskrybuje zdarzenia cząsteczkowe i tworzy cząstki w pozycji opisanej w zdarzeniu.

Filtry bardzo pomogą w zderzeniach. Filtry mogą określać, czy obiekt koliduje z innym, i jaką będzie mieć odpowiedź. Dodajesz pewne atrybuty do swojego komponentu fizyki, który określa, jaki to typ ciała fizyki, z jakim innym typem ciała fizyki koliduje i jaka powinna być odpowiedź. Na przykład obiekt fizyki piłki zderza się z obiektem fizyki łopatki i reaguje odbiciem i cząsteczkami.

Wreszcie, nie bądź tak rygorystyczny wobec swojej implementacji. Jeśli potrafisz znaleźć sposób, aby to działało, ale tak naprawdę nie jest to system EC, zrób to. Tak jak w moim przykładzie powyżej, cząstkami wcale nie trzeba zarządzać przez system lub część układu EC. Ważniejsze jest, aby ukończyć grę, niż ściśle przestrzegać metody, która jest już dość słabo zdefiniowana.

MichaelHouse
źródło
Dziękuję Ci bardzo. Chciałem zbudować grę za pomocą ECS, aby po prostu zobaczyć, jak się skaluje i czy naprawdę jest tak przyjemnie używać, jak czytam w artykułach i samouczkach. Mój problem polegał na tym, że pomyślałem: „Mam teraz ECS i tym wszystkim trzeba zarządzać”. Więc planowałem również napisać układ cząstek w związku z ECS. W niektórych artykułach czytałem również, że każdy element powinien naprawdę zawierać tylko podstawowe dane i nic więcej. To często mój problem ... Myślę, że to zbyt skomplikowane.
M0rgenstern
Chciałem zbudować grę za pomocą ECS, aby po prostu zobaczyć, jak się skaluje i czy naprawdę jest tak przyjemnie używać, jak czytam w artykułach i samouczkach . Jeśli to jest twój cel, zalecam przyjrzenie się istniejącym systemom Component / Entity zamiast budowania własnych. Pobierz Unity3D, który jest prawdopodobnie „tak czystym komponentem, jak to możliwe” i graj tam. Znacznie szybszy wgląd, IMHO.
Imi
3
@lmi: Unity nie jest systemem elementów encji, chociaż jest oparta na komponentach. ECS ma pewne bardziej rygorystyczne wytyczne ( nigdy nie traktuj wzorca jako reguł) niż po prostu posiadanie i używanie komponentów obiektów gry. Ze względu na serię artykułów ECS jest obecnie popularny wśród niektórych segmentów twórców gier, więc jest wiele pytań dotyczących ECS, a nie ogólnie projektowania opartego na komponentach.
Sean Middleditch,
12

Nadmiernie komplikujesz rzeczy. Posunąłbym się nawet do stwierdzenia, że ​​nawet używanie projektowania opartego na komponentach jest po prostu przesadą w przypadku tak prostej gry. Rób rzeczy tak, aby Twoja gra była szybka i łatwa w rozwoju. Komponenty pomagają w iteracji w większych projektach z dużą różnorodnością zachowań i konfiguracji obiektów gry, ale ich zalety w tak prostej, dobrze zdefiniowanej grze są bardziej wątpliwe. Rozmawiałem o tym w zeszłym roku: możesz zbudować fajne małe gry w ciągu kilku godzin, jeśli skupisz się na tworzeniu gry zamiast na architekturze . Dziedziczenie psuje się, gdy masz 100 lub nawet 20 różnych rodzajów obiektów, ale działa dobrze, jeśli masz tylko garstkę.

Zakładając, że chcesz nadal używać komponentów do celów uczenia się, istnieją pewne oczywiste problemy z twoim podejściem, które się wyróżniają.

Po pierwsze, nie rób tak małych komponentów. Nie ma powodu, aby mieć drobnoziarniste elementy, takie jak „ruch”. W twojej grze nie ma ruchów ogólnych. Masz wiosła, których ruch jest ściśle powiązany z wejściem lub AI (i tak naprawdę nie używasz prędkości, przyspieszenia, restytucji itp.), I masz piłkę, która ma dobrze zdefiniowany algorytm ruchu. Wystarczy mieć komponent PaddleController i BouncingBall lub coś w tym stylu. Jeśli / kiedy otrzymasz bardziej skomplikowaną grę, możesz się martwić o bardziej ogólny komponent PhysicsBody (który w „prawdziwych” silnikach jest w zasadzie tylko połączeniem między obiektem gry a dowolnym wewnętrznym obiektem API używanym przez Havok / PhysX / Bullet / Box2D / etc.), Który obsługuje szerszy zakres sytuacji.

Nawet element „pozycji” jest wątpliwy, ale z pewnością nie jest rzadki. Silniki fizyki zazwyczaj mają własne wyobrażenie o tym, gdzie znajduje się obiekt, grafika może mieć interpolowaną reprezentację, a AI może mieć jeszcze jedną reprezentację tych samych danych w innym stanie. Korzystne może być po prostu pozwolić każdemu systemowi zarządzać własnym pomysłem transformacji we własnych komponentach systemu, a następnie zapewnić płynną komunikację między systemem. Zobacz post na blogu BitSquid na temat strumieni zdarzeń .

W przypadku niestandardowych silników fizyki pamiętaj, że możesz mieć dane dotyczące swoich komponentów. Być może ogólny komponent fizyki Ponga zawiera dane wskazujące, na których osiach może się poruszać (powiedzmy, vec2(0,1)jako mnożnik dla łopatek, które mogą poruszać się tylko na osi Y, a vec2(1,1)dla piłki wskazującej, że może się jednak poruszać), flagę lub pływak wskazujące na odbijanie ( piłka zwykle jest w, 1.0a wiosła w0.0), charakterystyki przyspieszenia, prędkość itd. Próba podzielenia tego na bazillion różnych mikrokomponentów dla każdego kawałka wysoce powiązanych danych jest sprzeczna z tym, co pierwotnie miało robić ECS. Tam, gdzie to możliwe, przechowuj rzeczy używane razem w tym samym komponencie i dziel je tylko wtedy, gdy istnieje duża różnica w sposobie wykorzystania tych danych przez każdy obiekt gry. Trzeba argumentować, że dla Ponga fizyka między piłką a łopatkami jest na tyle inna, że ​​stanowią osobne elementy, ale w przypadku większej gry nie ma powodu, aby próbować stworzyć 20 elementów, aby działały dobrze w 1-3.

Pamiętaj, że jeśli / kiedy twoja wersja ECS przeszkadza, zrób to, czego potrzebujesz, aby faktycznie stworzyć grę i zapomnij o upartym przestrzeganiu wzorca / architektury.

Sean Middleditch
źródło
1
Naprawdę dziękuję! Wiem, że ECS nie skaluje się zbyt dobrze w przypadku małej gry, takiej jak pong. Ale użyłem go tylko po to, aby zobaczyć, jak taka rzecz jest faktycznie wdrażana i jak działa. Komponowałem tak małe elementy, ponieważ to właśnie czytałem głównie w niektórych artykułach. Aby mieć wiele składników, a każdy składnik zawiera tylko elementarne dane. Czy więc rozumiem, że masz rację, sugerujesz stosowanie kombinacji dziedziczenia z ECS? Jak mówisz „kula i łopatki są wystarczająco różne, by stanowić oddzielne elementy”. Na przykład daję im zarówno komponent Ruch / Pozycja (może jako jeden komponent), jak i
M0rgenstern
1
Cokolwiek działa. Skoncentruj się na tworzeniu gry. Chciałbym dosłownie mieć komponent o nazwie, Ballktóry zawiera całą logikę dla piłki, taką jak ruch, odbijanie się itp. Oraz Paddlekomponent, który pobiera dane wejściowe, ale to ja. Wszystko, co ma dla ciebie największy sens, zejdzie ci z drogi i pozwoli ci sprawić, że gra jest „właściwym sposobem” na robienie rzeczy.
Sean Middleditch,
3
Miałbym dosłownie po prostu komponent o nazwie Piłka, który zawiera całą logikę dla piłki, taką jak ruch, odbijanie itp., Oraz komponent Paddle, który pobiera dane wejściowe, ale to ja. I dlatego dla każdego programisty jest jedna opinia na temat „czym jest system składowy”. Radzę NIE robić tego w ten sposób, z tym wyjątkiem, że całkowicie myślisz w klasycznych systemach Entity i jesteś zmuszony używać systemu Component, ale nie chcesz patrzeć, jakie są różnice.
Imi
2
@lmi: pracując nad kilkoma dużymi grami / silnikami z komponentami i widząc z pierwszej ręki, dlaczego używamy komponentów, nie, zbyt szczegółowe komponenty są tylko większym problemem niż są warte. Komponenty nie są magiczną kulą; są jednym z wielu narzędzi w zestawie narzędzi twórców gier. Używaj ich w sposób i miejsce, w którym pomagają, a nie w taki sposób, że po prostu dodają więcej obciążenia umysłowego i czasu wykonywania do systemu. Jeśli jedyną rzeczą, która ma fizykę piłki, jest piłka, nie ma żadnej przewagi nad oddzieleniem jej od innych właściwości piłki. Jeśli i kiedy to się zmieni, podziel to na później.
Sean Middleditch,
1
Zgadzam się z zasadą bycia pragmatycznym i niedopuszczania, by jakiś tupot pojawiał się na ich drodze. ALE jeśli ECS nie jest w stanie poradzić sobie z tą trywialną grą bez odchyleń, jaka jest nadzieja na dużą grę. Ja również próbuję nauczyć się efektywnie korzystać z ECS, ale staram się być jak najbliżej filozofii ECS, w przeciwnym razie, kiedy zacznę robić wyjątki i wyjątkowe przypadki, wiem, że skończę z nie do utrzymania.
Ken
-2

Moim zdaniem *), twoim największym problemem ze składnikami jest: Składniki NIE są tutaj, aby nikomu powiedzieć , co robić. Komponenty są po to, by ZROBIĆ różne rzeczy. Nie masz komponentu, który po prostu przechowywałby pamięć jakiejś rzeczy, a następnie inne komponenty działały na tym. Chcesz komponentów, które robią rzeczy z danymi, które otrzymały.

Jeśli widzisz siebie testującego obecność innych komponentów (a następnie wywołujesz tam funkcje), oznacza to wyraźny znak, że jedna z dwóch rzeczy jest prawdą:

  • Naprawdę chcesz odwrócić zależność: Drugi komponent powinien nasłuchiwać zdarzeń / komunikatów / emisji / haków / każdego, aby wykonać ich logikę w odpowiedzi na bieżący komponent. Obecny komponent nie musi nawet wiedzieć, że istnieje „inny” komponent. Jest tak najczęściej w przypadku, gdy wywołujesz różne komponenty (nawet w różnych blokach else / case) z funkcjonalnością, która tak naprawdę nie jest powiązana z twoją bieżącą metodą. Pomyśl o tych wszystkich Invalidate()lub SetDirty()połączeniach z innymi komponentami.
  • Być może masz za dużo komponentów. Jeśli dwa składniki po prostu nie mogą żyć bez siebie i stale muszą pobierać dane i wywoływać metody, po prostu je scal. Oczywiście funkcje, które zapewniają są tak splątane, że tak naprawdę to tylko jedna rzecz.

Nawiasem mówiąc, dotyczy to wszystkich rodzajów systemów, nie tylko systemów Entity / Component, ale także klasycznego dziedziczenia z prostymi „GameObject”, a nawet funkcjami bibliotecznymi.

*) Naprawdę tylko moje . Opinie na temat Whats Da Real Component Systemz (TM) są bardzo zróżnicowane

Imi
źródło