Czy istnieje standardowa technika zarządzania danymi wejściowymi w dużych grach. Obecnie w moim projekcie cała obsługa danych wejściowych odbywa się w pętli gry, podobnie jak:
while(SDL_PollEvent(&event)){
switch(event.type){
case SDL_QUIT:
exit = 1;
break;
case SDL_KEYDOWN:
switch(event.key.keysym.sym){
case SDLK_c:
//do stuff
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
switch(event.button.button){
case SDL_BUTTON_MIDDLE:
//do stuff
break;
}
}
break;
}
(Używam SDL, ale spodziewam się, że główna praktyka stosuje również biblioteki i frameworki). W przypadku dużego projektu nie wydaje się to najlepszym rozwiązaniem. Mogę mieć kilka obiektów, które chcą wiedzieć, co nacisnął użytkownik, więc byłoby bardziej sensowne, gdyby te obiekty obsługiwały dane wejściowe. Jednak nie wszystkie obsługują dane wejściowe, ponieważ po otrzymaniu zdarzenia zostanie ono wypchnięte z bufora zdarzeń, więc inny obiekt nie otrzyma danych wejściowych. Jaką metodę najczęściej stosuje się, aby temu przeciwdziałać?
Odpowiedzi:
Zapytany przez osobę rozpoczynającą wątek, opracowuję menedżerów wydarzeń. Myślę, że to dobry sposób na radzenie sobie z danymi wejściowymi w grze.
Menedżer zdarzeń to klasa globalna, która pozwala zarówno rejestrować funkcje oddzwaniania do klawiszy, jak i odpalać te oddzwaniania. Menedżer zdarzeń przechowuje zarejestrowane funkcje na prywatnej liście pogrupowanej według ich klucza. Za każdym razem, gdy klucz jest uruchamiany, wszystkie zarejestrowane połączenia zwrotne są wykonywane.
Oddzwonienia mogą być
std::function
obiektami, które mogą pomieścić lambdas. Klucze mogą być ciągami znaków. Ponieważ menedżer ma charakter globalny, komponenty aplikacji mogą rejestrować się na klucze uruchamiane z innych komponentów.Można nawet rozszerzyć tego menedżera zdarzeń, aby umożliwić przekazywanie wartości jako dodatkowych argumentów. Świetnie nadają się do tego szablony C ++. Można użyć takiego systemu, aby, powiedzmy, dla
"WindowResize"
zdarzenia przekazać nowy rozmiar okna, tak aby komponenty nasłuchujące nie musiały go pobierać samodzielnie. Może to znacznie zmniejszyć zależności kodu.W swojej grze zaimplementowałem taki menedżer zdarzeń. Jeśli jesteś zainteresowany, opublikuję link do kodu tutaj.
Za pomocą menedżera zdarzeń możesz łatwo transmitować informacje wejściowe w aplikacji. Co więcej, pozwala to na miły sposób pozwalający użytkownikowi dostosować powiązania klawiszy. Komponenty słuchają zdarzeń semantycznych zamiast kluczy bezpośrednio (
"PlayerJump"
zamiast"KeyPressedSpace"
). Następnie możesz mieć komponent odwzorowania danych wejściowych, który nasłuchuje"KeyPressedSpace"
i wyzwala dowolne działanie użytkownika związane z tym kluczem.źródło
[=]
a odniesienia do wszystkich zmiennych lokalnych dostępnych z lambda zostaną skopiowane. Więc nie musisz przekazywać tego wskaźnika ani czegoś takiego. Jednak pamiętać, że nie można przechowywać lambdy z klauzulą przechwytywania w starych wskaźników funkcji C . Jednak C ++std::function
działa dobrze.Podziel to na kilka warstw.
Na najniższej warstwie masz nieprzetworzone zdarzenia wejściowe z systemu operacyjnego. Wprowadzanie za pomocą klawiatury SDL, wprowadzanie za pomocą myszy, wprowadzanie za pomocą joysticka itp. Możesz mieć kilka platform (SDL jest najmniej powszechnym mianownikiem pozbawionym kilku formularzy wprowadzania, na przykład, o które możesz później dbać).
Możesz je streścić za pomocą niestandardowego typu zdarzenia na bardzo niskim poziomie, takiego jak „przycisk klawiatury w dół” lub podobny. Gdy warstwa platformy (pętla gry SDL) otrzymuje dane wejściowe, powinna utworzyć zdarzenia niskiego poziomu, a następnie przesłać je do menedżera danych wejściowych. Może to robić za pomocą prostych wywołań metod, funkcji zwrotnych, skomplikowanego systemu zdarzeń, co tylko zechcesz.
System wprowadzania ma teraz za zadanie przekształcanie danych wejściowych niskiego poziomu na zdarzenia logiczne wysokiego poziomu. Logika gry wcale nie dba o to, by nacisnąć SPACJĘ. Dba o to, aby JUMP został wciśnięty. Zadaniem menedżera wprowadzania danych jest zbieranie tych zdarzeń wejściowych niskiego poziomu i generowanie zdarzeń wejściowych wysokiego poziomu. Odpowiada za to, że spacja i przycisk pada „A” są odwzorowane na logiczne polecenie Skok. Zajmuje się kontrolkami gamepad vs mysz i tak dalej. Emituje zdarzenia logiczne wysokiego poziomu, które są możliwie jak najbardziej abstrakcyjne z poziomu kontrolek niskiego poziomu (są tu pewne ograniczenia, ale w zwykłym przypadku można całkowicie oderwać rzeczy).
Następnie kontroler postaci odbiera te zdarzenia i przetwarza te wejściowe zdarzenia wysokiego poziomu, aby faktycznie zareagować. Warstwa platformy wysłała zdarzenie „Klawisz spacji w dół”. System wejściowy odebrał to, przegląda tabele mapowania / logikę, a następnie wysyła zdarzenie „Pressed jump”. Logika gry / kontroler postaci odbiera to zdarzenie, sprawdza, czy gracz może faktycznie skoczyć, a następnie emituje zdarzenie „Gracz skoczył” (lub po prostu bezpośrednio powoduje skok), którego reszta logiki używa do robienia czegokolwiek .
Wszystko zależne od logiki gry trafia do kontrolera gracza. Wszystko zależne od systemu operacyjnego przechodzi w warstwę platformy. Cała reszta przechodzi do warstwy zarządzania danymi wejściowymi.
Oto kilka amatorskich dzieł ASCII, aby to opisać:
źródło