Po zadaniu dwóch pytań na temat systemów bytów ( 1 , 2 ) i przeczytaniu kilku artykułów na ich temat, myślę, że rozumiem je znacznie lepiej niż wcześniej. Nadal mam pewne wątpliwości, głównie dotyczące budowy emitera cząstek, systemu wejściowego i kamery. Oczywiście nadal mam pewne problemy ze zrozumieniem systemów encji i mogą one dotyczyć całej innej gamy obiektów, ale wybrałem te trzy, ponieważ są to bardzo różne koncepcje, powinny obejmować dość szeroki obszar i pomóc mi zrozumieć systemy encji i jak radzę sobie z takimi problemami, jak się pojawiają.
Buduję silnik w JavaScript i zaimplementowałem większość podstawowych funkcji, w tym: obsługę danych wejściowych, elastyczny system animacji, emiter cząstek, klasy i funkcje matematyczne, obsługę scen, kamerę i rendering oraz całą masę innych rzeczy, które zazwyczaj obsługują silniki. Przeczytałem odpowiedź Byte56, która zainteresowała mnie przekształceniem silnika w system encji. Nadal pozostałby silnikiem gry HTML5, z podstawową filozofią scen, ale powinien wspierać dynamiczne tworzenie bytów z komponentów.
Problem, który mam teraz, polega na dopasowaniu mojej starej koncepcji silnika do tego nowego paradygmatu programowania. Oto niektóre definicje z poprzednich pytań, zaktualizowane:
Podmiot jest identyfikatorem. Nie ma żadnych danych, to nie jest obiekt, to prosty identyfikator reprezentujący indeks na liście scen wszystkich bytów (które faktycznie planuję zaimplementować jako macierz komponentów).
Komponent jest posiadaczem danych, ale z metodami, które mogą pracować na tych danych. Najlepszym przykładem jest
Vector2D
komponent „Pozycja”. Ma dane:x
ay
, ale także niektóre metody, które sprawiają, że działające na danych nieco łatwiejszej:add()
,normalize()
, i tak dalej.System jest czymś, co może działać na zestawie podmiotów, które spełniają określone wymagania; zwykle jednostki muszą mieć określony zestaw komponentów, aby móc nimi operować. System jest częścią „logiczną”, „algorytmową”, a wszystkie funkcje dostarczane przez komponenty służą wyłącznie do łatwiejszego zarządzania danymi.
Aparat fotograficzny
Kamera ma Vector2D
właściwość położenia, właściwość obrotu i niektóre metody centrowania jej wokół punktu. Każda klatka jest podawana do renderera wraz ze sceną, a wszystkie obiekty są tłumaczone zgodnie z jego pozycją. Scena jest następnie renderowana.
Jak mogę reprezentować tego rodzaju obiekt w systemie encji? Czy kamera byłaby bytem, komponentem lub kombinacją (zgodnie z moją odpowiedzią )?
Emiter cząstek
Problemem, jaki mam z moim emiterem cząstek, jest to, co powinno być. Jestem prawie pewien, że same cząstki nie powinny być bytami, ponieważ chcę wesprzeć ich ponad 10 000, i wierzę, że stworzenie tak wielu bytów byłoby dużym ciosem dla mojej wydajności.
Jak mogę reprezentować tego rodzaju obiekt w systemie encji?
Menedżer wprowadzania
Ostatnim, o którym chcę porozmawiać, jest sposób obsługi danych wejściowych. W mojej obecnej wersji silnika istnieje klasa o nazwie Input
. Jest to moduł obsługi, który subskrybuje zdarzenia przeglądarki, takie jak naciśnięcia klawiszy i zmiany pozycji myszy, a także utrzymuje stan wewnętrzny. Następnie klasa gracza ma react()
metodę, która akceptuje obiekt wejściowy jako argument. Zaletą tego jest to, że obiekt wejściowy można przekształcić do postaci szeregowej w .JSON, a następnie udostępnić przez sieć, co pozwala na płynne symulacje dla wielu graczy.
Jak to się przekłada na system encji?
Oto jak do tego podszedłem:
Aparat fotograficzny
Mój aparat jest bytem, jak każdy inny, który ma dołączone komponenty:
Transform
maTranslation
,Rotation
aScale
właściwości, oprócz innych do prędkości, itd.Pov
(Punkt widzenia) maFieldOfView
,AspectRatio
,Near
,Far
i wszelkie inne informacje wymagane do wytworzenia macierzy projekcji, opróczIsOrtho
flagi używane do przełączania pomiędzy perspektywy i prostopadłych występów.Pov
udostępnia takżeProjectionMatrix
właściwość leniwe obciążenia używaną przez system renderowania, która jest wewnętrznie obliczana podczas odczytu i buforowana, dopóki dowolne inne właściwości nie zostaną zmodyfikowane.Nie ma dedykowanego systemu kamer. System renderowania zachowuje listę
Pov
i zawiera logikę służącą do określania, której użyć podczas renderowania.Wkład
InputReceiver
Komponent może być dołączony do dowolnego podmiotu. Ma dołączony moduł obsługi zdarzeń (lub lambda, jeśli Twój język to obsługuje), który jest używany do przechowywania danych wejściowych specyficznych dla jednostki, który pobiera parametry dla bieżącego i poprzedniego stanu klucza, bieżącej i poprzedniej lokalizacji myszy oraz stanu przycisku itp. (W rzeczywistości, istnieją osobne programy obsługi myszy i klawiatury).Na przykład w grze testowej podobnej do asteroid, którą stworzyłem, przyzwyczajając się do Entity / Component, mam dwie wejściowe metody lambda. Jeden obsługuje nawigację statku, przetwarzając klawisze strzałek i spację (do wystrzelenia). Drugi obsługuje ogólne wprowadzanie z klawiatury - klawisze wyjścia, pauzy itp., Poziom restartu itp. Tworzę dwa komponenty, dołączam każdą lambda do własnego komponentu, a następnie przypisuję komponent odbiornika nawigacyjnego do jednostki statku, a drugą do niewidoczny obiekt procesora poleceń.
Oto procedura obsługi zdarzeń do obsługi kluczy przechowywanych między ramkami, które są dołączane do
InputReceiver
komponentu statku (C #):Jeśli twój aparat jest mobilny, daj mu swój
InputReceiver
i swójTransform
komponent, podłącz lambda lub moduł obsługi, który realizuje dowolny rodzaj kontroli, którą chcesz i gotowe.Jest to dość fajne, ponieważ można przenieść
InputReceiver
komponent z przewodnikiem nawigacyjnym przymocowanym ze statku do asteroidy lub cokolwiek innego w tym zakresie i zamiast tego latać. Lub przypisującPov
komponent do dowolnej innej części sceny - asteroidy, lampy ulicznej itp. - możesz oglądać swoją scenę z perspektywy tego bytu.InputSystem
Klasa, która utrzymuje stan wewnętrzny dla klawiatury, myszy itpInputSystem
filtruje swój wewnętrzny zbiór encji do podmiotów, które mająInputReceiver
komponent. W swojejUpdate()
metodzie iteruje tę kolekcję i wywołuje procedury obsługi danych wejściowych dołączone do każdego z tych komponentów w taki sam sposób, jak system renderujący rysuje każdą jednostkę za pomocąRenderable
komponentu.Cząsteczki
To naprawdę zależy od tego, jak planujesz oddziaływać z cząsteczkami. Jeśli potrzebujesz tylko układu cząsteczek, który zachowuje się jak jeden obiekt - powiedzmy, fajerwerki pokazują, że gracz nie może dotknąć ani uderzyć - wtedy stworzyłbym pojedynczy byt i
ParticleRenderGroup
komponent zawierający dowolne informacje potrzebne cząstkom - rozpad itp. - które nie są objęte twoimRenderable
składnikiem. Podczas renderowania system renderujący sprawdza, czy jednostka maRenderParticleGroup
dołączony element i odpowiednio go obsługuje.Jeśli potrzebujesz pojedynczych cząstek do uczestniczenia w wykrywaniu kolizji, reagowania na dane wejściowe itp., Ale chcesz po prostu renderować je jako partię, utworzę
Particle
komponent zawierający te informacje dla poszczególnych cząstek i stworzę je jako oddzielne podmioty. System renderowania nadal może je grupować, ale inne systemy będą traktować je jako osobne obiekty. (Działa to bardzo dobrze w przypadku wystąpienia).Następnie, albo w twoim
MotionSystem
(lub jakimkolwiek użyciu, który obsługuje aktualizację pozycji bytu, itp.) Lub w dedykowanymParticleSystem
, wykonaj dowolne przetwarzanie wymagane dla każdej cząstki na ramkę.RenderSystem
Byłby odpowiedzialny za budowę / dozowania i buforowanie zbiory cząstek, jak są one tworzone i niszczone, i uczynić je w razie potrzeby.Jedną fajną rzeczą w tym podejściu jest to, że nie musisz mieć żadnych specjalnych przypadków kolizji, ubijania itp. Dla cząstek; kod, który piszesz dla każdego innego rodzaju bytu, może być nadal używany.
Wniosek
Jeśli zastanawiasz się nad przejściem na wiele platform - nie ma zastosowania do JavaScript - cały kod specyficzny dla platformy (a mianowicie renderowanie i wprowadzanie danych) jest izolowany w dwóch systemach. Twoja logika gry pozostaje w klasach zależnych od platformy (ruch, kolizja itp.), Więc nie powinieneś dotykać ich podczas przenoszenia.
Rozumiem i zgadzam się ze stanowiskiem Seana, że obrzucanie butami rzeczy we wzór w celu ścisłego trzymania się wzoru, zamiast dostosowywania wzoru w celu wypełnienia potrzeb twojej aplikacji, jest złe. Po prostu nie widzę niczego w danych wejściowych, kamerze lub cząsteczkach, które wymagałyby tego rodzaju leczenia.
źródło
Logika wprowadzania i gry będzie prawdopodobnie obsługiwana w dedykowanym fragmencie kodu poza systemem komponentu encji. Technicznie możliwe jest wprowadzenie go do projektu, ale nie ma to żadnej korzyści - logika gry i interfejs użytkownika są zuchwałe i pełne nieszczelnych abstrakcji bez względu na to, co robisz, a próba wtłoczenia kwadratowego kołka w okrągły otwór tylko ze względu na czystość architektoniczną jest marnotrawstwem czasu.
Podobnie, emitery cząstek są specjalnymi zwierzętami, szczególnie jeśli zależy ci na wydajności. Komponent emitera ma sens, ale grafika będzie robiła jakąś specjalną magię z tymi komponentami, zmieszanymi z magią do końca renderowania.
Jeśli chodzi o kamerę, po prostu nadaj kamerom aktywną flagę i być może indeks „głębokości” i pozwól systemowi graficznemu renderować wszystkie z nich, które są włączone. Jest to przydatne w przypadku wielu sztuczek, w tym GUI (chcesz, aby Twój GUI był renderowany w trybie ortograficznym na szczycie świata gry? Nie ma problemu, to tylko dwie kamery z różnymi maskami obiektów i GUI ustawionymi na wyższą warstwę). Jest także przydatny w przypadku warstw efektów specjalnych i tym podobnych.
źródło
Nie jestem pewien, co tak naprawdę zadaje to pytanie. Biorąc pod uwagę, że jedynymi rzeczami, które masz w grze, są byty, to kamery muszą być bytami. Funkcjonalność kamery jest realizowana za pomocą pewnego rodzaju komponentu kamery. Nie posiadaj osobnych komponentów „Pozycja” i „Obrót” - to zdecydowanie za niski poziom. Powinny one zostać połączone w jakiś element WorldPosition, który miałby zastosowanie do każdego podmiotu zlokalizowanego na świecie. Jeśli chodzi o to, którego użyć ... musisz jakoś wprowadzić logikę do systemu. Albo zakodujesz go na stałe w systemie obsługi aparatu, albo załączysz skrypty lub coś w tym rodzaju. Możesz pomóc włączyć / wyłączyć flagę w komponencie kamery, jeśli to pomaga.
Ja też. Emiter cząstek byłby bytem, a układ cząstek śledziłby cząstki związane z danym bytem. Takie rzeczy zdają sobie sprawę, że „wszystko jest bytem” jest absurdalnie niepraktyczne. W praktyce jedynymi rzeczami, które są bytami, są względnie złożone obiekty, które korzystają z kombinacji składników.
Jeśli chodzi o dane wejściowe: dane wejściowe nie istnieją w świecie gry jako takie, więc obsługuje je system. Niekoniecznie „system komponentów”, ponieważ nie wszystko w grze będzie się obracać wokół komponentów. Ale będzie system wejściowy. Możesz chcieć oznaczyć byt, który reaguje na dane wejściowe, jakimś rodzajem komponentu Player, ale dane wejściowe będą złożone i całkowicie specyficzne dla gry, więc nie ma sensu próbować tworzyć do tego komponentów.
źródło
Oto niektóre z moich pomysłów na rozwiązanie tych problemów. Prawdopodobnie będą mieli coś z nimi nie tak i prawdopodobnie będzie lepsze podejście, więc proszę, skierujcie mnie do tych w odpowiedzi!
Kamera :
Istnieje element „Kamera”, który można dodać do dowolnego elementu. Nie jestem jednak w stanie ustalić, jakie dane powinienem umieścić w tym elemencie: mogłem mieć osobne komponenty „Pozycja” i „Obrót”!
follow
Metoda nie muszą być realizowane, ponieważ już po jednostka jest podłączona do! I mogę go swobodnie przenosić. Problem z tym systemem polegałby na wielu różnych obiektach kamery: skąd możnaRendererSystem
wiedzieć, których użyć? Poza tym zwykłem po prostu przekazywać obiekt kamery, ale teraz wydaje się, żeRendererSystem
trzeba będzie dwa razy iterować wszystkie byty: po pierwsze, aby znaleźć te, które działają jak kamery, a po drugie, aby wszystko zrenderować.ParticleEmitter :
Byłby taki,
ParticleSystem
który aktualizowałby wszystkie podmioty, które miały komponent „Emiter”. Cząstki są głupimi obiektami we względnej przestrzeni współrzędnych wewnątrz tego komponentu. Występuje tutaj problem renderowania: musiałbym albo stworzyćParticleRenderer
system, albo rozszerzyć funkcjonalność istniejącego.System wejściowy :
Główną troską była dla mnie tutaj logika lub
react()
metoda. Jedyne rozwiązanie, jakie wymyśliłem, to osobny system i komponent dla każdego systemu, który wskazywałby, którego użyć. To naprawdę wydaje się zbyt hakerskie i nie wiem, jak sobie z tym poradzić. Jedną rzeczą jest to, że, o ile się martwię,Input
mogą pozostać zaimplementowane jako klasa, ale nie wiem, jak mogę to zintegrować z resztą gry.źródło