Obsługa systemu wprowadzania z klawiatury

13

Uwaga: muszę sondować, a nie wywoływać oddzwanianie z powodu ograniczeń API (SFML). Przepraszam również za brak „przyzwoitego” tytułu.

Myślę, że mam tutaj dwa pytania; jak zarejestrować otrzymywane informacje i co z nimi zrobić.

Obsługa danych wejściowych

Mówię o tym po tym, jak zarejestrowałeś, że klawisz „A” został naciśnięty, na przykład, i jak to zrobić od tego momentu.

Widziałem tablicę całej klawiatury, coś w stylu:

bool keyboard[256]; //And each input loop check the state of every key on the keyboard

Ale to wydaje się nieefektywne. Nie tylko przykładasz klawisz „A” do „gracza poruszającego się w lewo”, ale sprawdza on każdy klawisz 30–60 razy na sekundę.

Następnie wypróbowałem inny system, który szukał tylko pożądanych kluczy.

std::map< unsigned char, Key> keyMap; //Key stores the keycode, and whether it's been pressed. Then, I declare a load of const unsigned char called 'Quit' or 'PlayerLeft'.
input->BindKey(Keys::PlayerLeft, KeyCode::A); //so now you can check if PlayerLeft, rather than if A.

Jednak problem polega na tym, że nie mogę teraz wpisać nazwy bez konieczności wiązania każdego klucza.

Następnie mam drugi problem, który nie jest w stanie wymyślić dobrego rozwiązania dla:

Wysyłanie danych wejściowych

Wiem teraz, że klawisz A został naciśnięty lub że playerLeft jest prawdziwy. Ale jak mam stąd wyjść?

Pomyślałem o sprawdzeniu if(input->IsKeyDown(Key::PlayerLeft) { player.MoveLeft(); }
To bardzo łączy dane wejściowe z istotami i uważam, że jest to dość bałagan. Wolałbym, żeby gracz sam sobie poradził, gdy zostanie zaktualizowany. Myślałem, że jakiś system wydarzeń może działać, ale nie wiem, jak się z tym pogodzić. (Słyszałem, że sygnały i automaty były dobre do tego rodzaju pracy, ale najwyraźniej są bardzo powolne i nie widzę, jak by to pasowało).

Dzięki.

Kaczka komunistyczna
źródło

Odpowiedzi:

7

Chciałbym użyć wzorca obserwatora i mieć klasę wejściową, która utrzymuje listę wywołań zwrotnych lub obiektów obsługi danych wejściowych. Inne obiekty mogą zarejestrować się w systemie wejściowym, aby otrzymywać powiadomienia o określonych zdarzeniach. Istnieją różne rodzaje wywołań zwrotnych, które można zarejestrować, w zależności od rodzaju zdarzeń wejściowych, o których obserwatorzy chcieliby zostać powiadomieni. Może to być tak niski poziom, jak „klawisz x jest w dół / w górę” lub bardziej specyficzny dla gry, taki jak „zdarzenie skoku / strzelania / ruchu”.

Zasadniczo obiekty gry mają komponent obsługi danych wejściowych , który jest odpowiedzialny za rejestrowanie się w klasie danych wejściowych i zapewnianie metod obsługi zdarzeń, które są zainteresowane. Do ruchu mam komponent przemieszczania danych, który ma metodę ruchu (x, y). Rejestruje się w systemie wejściowym i jest powiadamiany o zdarzeniach ruchu (gdzie xiy wynosiłyby -1, a 0 oznaczałoby ruch w lewo), składnik poruszający się następnie zmienia współrzędne obiektu gry macierzystej na podstawie kierunku ruchu i prędkości obiektu.

Nie wiem, czy to dobre rozwiązanie, ale jak dotąd działa dla mnie. W szczególności pozwala mi zapewnić różne rodzaje systemu wprowadzania, który odwzorowuje te same logiczne zdarzenia w grze, dzięki czemu przycisk x kontrolera i przestrzeń klawiatury generują na przykład zdarzenie skoku (jest to nieco bardziej skomplikowane, pozwalając użytkownikowi na ponowne przypisanie klawisza mapowania).

Firas Assaad
źródło
To zdecydowanie mnie interesuje; czy komponent wejściowy zarejestruje się dla niektórych zdarzeń (coś takiego jak playerInput rejestruje „lewy”, „prawy” itd.) czy każdy zarejestrowany komponent wejściowy odbierze wszystkie wiadomości?
Kaczka komunistyczna
To naprawdę zależy od twoich wymagań i tego, jak lubisz je wdrażać. W moim przypadku detektory rejestrują się dla określonych zdarzeń i implementują odpowiedni interfejs procedury obsługi danych wejściowych. Po otrzymaniu każdego komponentu wejściowego wszystkie wiadomości powinny działać, ale format wiadomości musi być bardziej ogólny niż ten, który mam.
Firas Assaad,
6

Myślę, że dyskusja PO łączy wiele rzeczy i że problem można znacznie uprościć, rozbijając go nieco.

Moja sugestia:

Zachowaj tablicę stanów kluczy. Czy nie ma wywołania API, aby uzyskać jednocześnie cały stan klawiatury? (Większość mojej pracy dotyczy konsoli, a nawet w systemie Windows dla podłączonego kontrolera uzyskujesz cały stan kontrolera naraz.) Twoje stany klawiszy są „twardą” mapą na klawiszach klawiatury. Nieprzetłumaczone jest najlepsze, ale najłatwiej uzyskać je z interfejsów API.

Następnie zachowaj kilka równoległych tablic wskazujących, czy klucz poszedł w górę tej ramki, inny wskazujący, że spadł, a trzeci, aby wskazać po prostu, że klucz jest „opuszczony”. Może wrzuć licznik czasu, abyś mógł śledzić, jak długo klucz był w obecnym stanie.

Następnie utwórz metodę tworzenia mapowania kluczy do akcji. Będziesz chciał to zrobić kilka razy. Raz, aby uzyskać informacje o interfejsie użytkownika (i staraj się przeszukiwać mapowania Windows, ponieważ nie możesz założyć, że scancode po naciśnięciu „A” da A na komputerze użytkownika). Kolejny dla każdego „trybu” w grze. Są to odwzorowania kodów klawiszy na działania. Możesz to zrobić na kilka sposobów, jednym z nich byłoby zachowanie wyliczenia używanego przez system odbierający, innym byłby wskaźnik funkcji wskazujący funkcję, która ma zostać wywołana przy zmianie stanu. Może nawet hybryda tych dwóch, w zależności od twoich celów i potrzeb. Za pomocą tych „trybów” możesz przesuwać je i umieszczać na stosie trybu kontrolera, z flagami, które pozwalają na ignorowanie danych wejściowych, aby nadal zmniejszać stos lub cokolwiek innego.

Wreszcie, jakoś poradzić sobie z tymi kluczowymi działaniami. W przypadku ruchu możesz chcieć zrobić coś trudnego, przetłumaczyć „W” w dół, co oznacza „posunąć się do przodu”, ale nie musi to być rozwiązanie binarne; możesz mieć „W jest w dół” oznacza „zwiększać prędkość o X, aż do wartości maksymalnej Y”, a „W jest w górę”, aby być „zmniejszać prędkość o Z, aż osiągnie zero”. Mówiąc ogólnie - chcesz, aby Twój „interfejs obsługi kontrolerów w systemach gier” był dość wąski; wykonaj wszystkie kluczowe tłumaczenia w jednym miejscu, a następnie użyj ich wyników wszędzie indziej. Jest to w przeciwieństwie do bezpośredniego sprawdzania, czy klawisz spacji jest naciskany w dowolnym miejscu w kodzie, ponieważ jeśli jest w losowym miejscu, prawdopodobnie zostanie trafiony w dowolnym momencie, gdy nie chcesz, a po prostu nie chcę sobie z tym poradzić ...

Naprawdę nie znoszę projektowania wzorów i myślę, że komponenty przynoszą więcej kosztów rozwoju niż pożytku, dlatego nie wspomniałem o żadnym z nich. Wzory pojawią się, jeśli są przeznaczone, podobnie jak komponenty, ale wyruszenie do zrobienia jednego z nich od samego początku tylko skomplikuje twoje życie.

dash-tom-bang
źródło
Nie sądzisz, że wiązanie zdarzeń z ramkami graficznymi jest trochę brudne? Myślę, że należy to oddzielić.
Jo So
Rozumiem, że w prawie wszystkich przypadkach gracz będzie wolniej wprowadzać przyciski niż karta graficzna będzie emitować ramki, ale tak naprawdę powinny one być niezależne, aw rzeczywistości są przeznaczone dla innych typów zdarzeń, takich jak te zebrane z sieci .
Jo So
W rzeczy samej; nie ma powodu, dla którego odpytywanie klawiatury (lub innego urządzenia wejściowego) nie może działać z częstotliwością inną niż ekran. Rzeczywiście, jeśli sondujesz wejścia przy 100 Hz, możesz zapewnić naprawdę spójną kontrolę użytkownika, niezależnie od częstotliwości aktualizacji sprzętu wideo. Będziesz musiał być nieco bardziej sprytny w tym, jak obchodzisz się z danymi (w zakresie blokowania itp.), Ale ułatwi to spójność w gestach.
dash-tom-bang
3

W SFML masz klucze w kolejności, w jakiej zostały naciśnięte z typem zdarzenia KeyPressed. Przetwarzasz każdy klucz na chwilę (getNextEvent ()).

Więc jeśli wciśnięty klawisz znajduje się na twojej mapie Klucz-> Akcja, uruchom odpowiednią akcję, a jeśli nie, przekaż ją do dowolnego widżetu / rzeczy, które mogą jej potrzebować.

Jeśli chodzi o twoje drugie pytanie, polecam zachować je w ten sposób, ponieważ ułatwia to ulepszenie rozgrywki. Jeśli używasz takich sygnałów, musisz utworzyć miejsce dla „RunKeyDown” i drugie dla „JumpKeySlot”. Ale co się stanie, jeśli w grze zostanie wykonana specjalna akcja po naciśnięciu obu przycisków?

Jeśli chcesz zdekorelować dane wejściowe i byty, możesz ustawić dane wejściowe jako Stan (RunKey = true, FireKey = false, ...) i wysłać je do odtwarzacza, tak jak wysyłasz dowolne inne zdarzenie do AI.

Calvin1602
źródło
Nie obsługuję zdarzeń klawiatury za pomocą sf :: Event, a raczej sf :: Input, ponieważ jest to IIRC z wykrywaniem w czasie rzeczywistym. Próbowałem zachować jak najbardziej agnostyk API. : P (Pytanie, to jest)
Kaczka komunistyczna
1
Ale myślę, że chodzi o to, że Calvin1602 ma na myśli to, że twoje oryginalne stwierdzenie w pytaniu „muszę odpytywać, a nie robić oddzwaniania z powodu ograniczeń API (SFML)”, nie jest prawdziwe; masz zdarzenia, więc możesz obsłużyć wywołanie zwrotne podczas obsługi zdarzenia.
Kylotan,
3

Prawidłowe rozwiązanie jest często kombinacją dwóch omawianych metod:

W przypadku funkcji rozgrywki z predefiniowanym zestawem kluczy odpytywanie każdej klatki jest całkowicie odpowiednie i należy sprawdzać tylko te klucze, które są rzeczywiście powiązane z czymś. Jest to w rzeczywistości analogiczne do zapytania o dane wejściowe kontrolera w grze na konsolę. Będzie to całkowicie odpytywanie, szczególnie jeśli chodzi o analogowe urządzenia wejściowe. Podczas ogólnej gry prawdopodobnie chcesz zignorować zdarzenia naciśnięcia klawisza i po prostu użyć odpytywania.

Jeśli chodzi o wpisywanie danych wejściowych takich jak nazwy, powinieneś przełączyć grę w inny tryb obsługi danych wejściowych. Na przykład, gdy tylko pojawi się monit „wpisz swoje imię”, możesz zmodyfikować kod wejściowy. Może nasłuchiwać zdarzeń naciśnięcia klawisza za pośrednictwem interfejsu oddzwaniania lub w razie potrzeby można rozpocząć odpytywanie każdego klawisza. Okazuje się, że zazwyczaj podczas pisania na klawiaturze i tak nie zależy ci na wydajności, więc odpytywanie nie będzie takie złe.

Tak więc chcesz używać selektywnego odpytywania do wprowadzania danych do gry i obsługi zdarzeń do wprowadzania nazw (lub całego odpytywania klawiatury, jeśli obsługa zdarzeń nie jest dostępna).

Ben Zeigler
źródło