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:
- 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.
- 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ć?
źródło
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, avec2(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.0
a 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.
źródło
Ball
który zawiera całą logikę dla piłki, taką jak ruch, odbijanie się itp. OrazPaddle
komponent, 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.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ą:
Invalidate()
lubSetDirty()
połączeniach z innymi komponentami.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
źródło