Obsługa danych wejściowych w projektowaniu opartym na komponentach

12

Wiem, że to pytanie zostało zadane kilka razy, ale nadal nie jestem pewien, jak zaimplementować obsługę danych wejściowych w silniku opartym na komponentach.

Projekt oparty na komponentach, którego użyłem, był oparty na serii blogów T = Machine i na Artemis, w których jednostki to tylko identyfikatory.

Istnieją trzy główne pomysły, które mam przy wdrażaniu obsługi danych wejściowych:

  1. Komponent wejściowy przechowuje zdarzenia, które są zainteresowane. System wejściowy przełoży zdarzenia kluczowe i myszy na zdarzenia gry i będzie przechodził przez jednostki z komponentem wejściowym, a jeśli są one zainteresowane zdarzeniem, system wejściowy podejmie odpowiednie działania. Ta akcja byłaby zakodowana na stałe w systemie wejściowym.
  2. Brak elementu wejściowego. Zarejestrowałbyś podmioty z określonymi zdarzeniami w systemie wejściowym. System wejściowy wysyła następnie wiadomości (z identyfikatorem encji i typem zdarzenia) do innych systemów, aby mogły one podjąć odpowiednie działanie. Lub jak w pierwszym przypadku, działania byłyby zakodowane na stałe w systemie wejściowym.
  3. Podobnie jak w pierwszej metodzie, ale zamiast na stałe kodować akcję do systemu wejściowego, komponent zawierałby mapę zdarzeń do funkcji (tj. std::map<std::function>), Które byłyby wywoływane przez system wejściowy. Daje to dodatkowy efekt polegający na powiązaniu tego samego zdarzenia z różnymi działaniami.

Czy poleciłbyś którąś z powyższych metod, czy masz jakieś sugestie, które pomogłyby mi wdrożyć elastyczny system obsługi danych wejściowych? Ponadto nie jestem jeszcze zaznajomiony z wielowątkowością, ale wszelkie sugestie, które uczynią implementację przyjazną dla wątków, są również mile widziane.

Uwaga: Jednym z dodatkowych wymagań, które chciałbym, aby implementacja była spełniona, jest to, że będę w stanie przekazać te same dane wejściowe do wielu jednostek, takich jak na przykład przeniesienie jednostki kamery i odtwarzacza w tym samym czasie.

Grieverheart
źródło
2
Zwykle (gdy kamera podąża za odtwarzaczem) nie chcesz odbierać sygnału wejściowego do kamery, zamiast tego sprawisz, że kamera sprawdzi pozycję gracza i podąży za nią.
Łukasz B.
1
Nie ma to żadnego znaczenia koncepcyjnego, jeśli kamera podąża za odtwarzaczem lub „sama”. Niemniej jednak nie jestem pewien, w jaki sposób twoja sugestia mogłaby zostać zaimplementowana w projekcie opartym na komponentach bez naruszenia zasad projektowania.
Grieverheart,
1
@Luke B .: Po zastanowieniu się, widzę, że możesz też zrobić kamerę jako osobną klasę, biorąc wskaźnik do bytu.
Grieverheart,

Odpowiedzi:

8

Myślę, że podobnie jak moja odpowiedź dotycząca materiałów w systemie komponentów , napotykasz problem polegający na tym, że próbujesz wszystko przełożyć na „komponent”. Nie musisz tego robić, dlatego prawdopodobnie tworzysz naprawdę niewygodny interfejs, próbując dopasować kilka kwadratowych kołków do okrągłych otworów.

Wygląda na to, że masz już system, który obsługuje pozyskiwanie danych wejściowych z odtwarzacza. Zdecydowałbym się na podejście, które następnie przekłada te dane wejściowe na działania („przejdź do przodu” lub „wstecz”) lub zdarzenia i przekazuje je zainteresowanym stronom. W przeszłości nie pozwalałem komponentom rejestrować się na te zdarzenia, wolałem podejście, w którym system wyższego poziomu wyraźnie wybrał „kontrolowaną jednostkę”. Ale może to działać w inny sposób, jeśli wolisz, zwłaszcza jeśli zamierzasz ponownie użyć tych samych komunikatów do podjęcia działań, które nie zostały bezpośrednio pobudzone przez dane wejściowe.

Jednak niekoniecznie sugerowałbym wdrożenie zachowania polegającego na podążaniu za kamerą, ponieważ zarówno jednostka kamery, jak i jednostka gracza reagują na komunikat „posunąć się do przodu” (i tak dalej). Tworzy to niezwykle sztywne połączenie między dwoma obiektami, które prawdopodobnie nie będą się dobrze czuć dla gracza, a także sprawia, że ​​nieco trudniej jest poradzić sobie z takimi rzeczami, jak kamera na orbicie odtwarzacza, gdy gracz obraca się w lewo lub w prawo: masz byt reagowanie na „obrót w lewo”, zakładając, że jest on niewolnikiem gracza, ale to oznacza, że ​​nie może poprawnie odpowiedzieć, jeśli kiedykolwiek był niewolnikiem ... chyba że wprowadzisz tę koncepcję jako pewien stan, który możesz sprawdzić. A jeśli masz zamiar to zrobić, równie dobrze możesz wdrożyć odpowiedni system do niewolnictwa dwóch fizycznych obiektów razem, wraz z odpowiednimi modyfikacjami elastyczności i tak dalej.

Jeśli chodzi o wielowątkowość, tak naprawdę nie widzę potrzeby stosowania jej tutaj, ponieważ prawdopodobnie spowodowałoby to więcej komplikacji niż jest to warte, a masz do czynienia z nieodłącznym problemem szeregowym, więc musisz po prostu zaangażować dużo wątku operacje podstawowe synchronizacji.

Społeczność
źródło
Zastanawiałem się nad moim pytaniem i już miałem na nie odpowiedzieć. Doszedłem również do wniosku, że powinienem być lepszy w oddzielaniu obsługi danych wejściowych od systemu EC, więc miło jest to potwierdzić. Myślałem o zrobieniu tego za pomocą sygnałów i możliwości powiązania kilku bytów z typem zdarzenia. Postanowiłem także oddzielić kamerę, chociaż nie jest to tak naprawdę konieczne, a posiadanie jej jako bytu byłoby równie opłacalne. Myślę, że będąc jeszcze nowicjuszem w EC, naprawdę musisz pomyśleć, jakie są korzyści z uczynienia czegoś składnikiem lub bytem.
Grieverheart
4

Moje doświadczenie może być stronnicze, ale w projektach wieloplatformowych urządzenia wejściowe nie są bezpośrednio narażone na system encji.

Urządzenia wejściowe są obsługiwane przez system niższego poziomu, który odbiera zdarzenia z klawiszy, przycisków, osi, myszy, powierzchni dotykowych, akcelerometrów ...

Te zdarzenia są następnie wysyłane przez warstwę zależnych od kontekstu generatorów intencji.

Każdy generator rejestruje zmiany stanu z komponentów, jednostek i systemów, które są istotne dla jego funkcji.

Generatory te wysyłają następnie wiadomości / zamiary dotyczące trasowania do systemu intencji, w którym podmioty mają komponent lub bezpośrednio do odpowiednich komponentów.

W ten sposób możesz po prostu polegać na tym, że „zawsze” ma to samo wejście, tj. JUMP_INTENT (1), JUMP_INTENT (0), AIM_INTENT (1) ...

I „cała” praca związana z brudną platformą pozostaje poza systemem twojego bytu.


Jeśli chodzi o kamerę, jeśli chcesz ją przenosić wokół odtwarzacza, może zarejestrować własny komponent zamiaru i słuchać zamiarów, które wyślesz.

W przeciwnym razie, jeśli podąża za odtwarzaczem, nigdy nie powinien słuchać sygnałów przeznaczonych dla odtwarzacza. Powinien słuchać zmian stanu emitowanych przez odtwarzacz (ENTITY_MOVED (transform)) ... i odpowiednio się poruszać. Jeśli korzystasz z systemu fizyki, możesz nawet podłączyć kamerę do odtwarzacza za pomocą jednego z różnych złączy.

Kojot
źródło
Kojot, dziękuję za odpowiedź. Przeczytałem również twój drugi post tutaj . Moją największą troską nie jest to, jak wyekstrahować dane wejściowe. Mam już konstrukcję niższego poziomu, która obsługuje naciśnięcia klawiszy i tym podobne, a dodanie jeszcze jednego poziomu pośrednictwa nie byłoby trudne. Mój problem polega na obsłudze zdarzeń generowanych na przykład przez Twój system zamiarów. Jeśli dobrze rozumiem, w metodzie nie ma żadnych elementów wejściowych. Skąd wiesz, które podmioty potrzebują danych wejściowych i jak sobie z nimi poradzić? Czy możesz podać bardziej konkretne przykłady?
Grieverheart,
2

Jakie korzyści przynosi InputComponent? Z pewnością do komendy wejściowej należy decyzja, na jakich jednostkach wykonuje akcję. Klasycznym przykładem jest zmuszanie gracza do skoku. Zamiast mieć InputComponent na każdej encji, która nasłuchuje zdarzeń „Jump”, dlaczego nie polecić komendzie skoku odszukać encję oznaczoną jako „player” i wykonać samą niezbędną logikę?

Action jump = () =>
{
    entities["player"].Transform.Velocity.Y += 5;
};

Kolejny przykład z PO:

Action moveRight = () =>
{
    foreach (var entity in entities.Tagged("player", "camera"))
        entity.Transform.Position.X += 5;
};
AlexFoxGill
źródło