Piszę dalej kod mappera, tym razem właściwą funkcję pozwalającą wykryć moment odpalenia danej akcji (jeden z wielu momentów tak naprawdę). Czyli po stronie kodu obsługi logiki gry mam do dyspozycji wysoce abstrakcyją metodę do sprawdzania czy gracz wykonał konkretną akcję. Przykład wywołania akcji wejścia do menu:
if Game_InputIsPerformed(GAME_INPUT_PLAYER_1, GAME_INPUT_ACTION_MENU, GAME_INPUT_MOMENT_STARTED, nil) then
// hura.
Powyższy kod testuje czy konkretny gracz (tutaj: pierwszy) wykonał konkretną akcję (tutaj: wejścia do menu), sprawdzając również moment wykonania akcji (tutaj: świeże odpalenie akcji). Natomiast po drugiej stronie, maskowane przez tę abstrakcyjną funkcję, mamy kupę urządzeń wejścia, trybów ich używania, różnych manipulatorów (przyciski, rolki, osie, drążki i inne c**** muje). To z którego urządzenia, jakim i którym manipulatorem się odpala akcję i w jaki sposób, determinuje konfiguracja ”obiektu” danej akcji.
Przed chwilą skończyłem programować ostatni sposób aktywowania akcji za pomocą kontrolera, czyli za pomocą gałki analogowej. Natrafiłem tutaj na ciekawy problem — czasem wychylenie drążka analogowego nie jest wykrywane.
Sprawa wygląda tak — każda oś analogowa reprezentowana jest przez obiekt stanu (w formie struktury z kilkoma polami), dzięki czemu zawsze mam dostęp do wartości poprzedniej i aktualnej. W każdej klatce zasysam dane ze zdarzeń SDL_JOYAXISMOTION
i wpisuje je do odpowiednich obiektów ze stanem osi. Aby sprawdzić, czy dana oś jest odpowiednio mocno wychylona (czyli aby sprawdzić, czy akcja jest świeżo aktywowana), należy:
Jest to proste i logiczne, nic nadzwyczajnego. Jednak tutaj jest właśnie problem — dość regularnie świeże aktywowanie akcji nie jest wykrywane. Wszystko dlatego, że ruszanie gałką analogową emituję ogromne ilości zdarzeń, przez co podczas pobierania zdarzeń z kolejki w ramach danej klatki, w kolejce znajdują dziesiątki zdarzeń SDL_JOYAXISMOTION
. Nie można ot tak przepisywać z nich dane, bo obiekt stanu osi zapamiętuje tylko dwie wartości (poprzednią i aktualną), a więc z tych dziesiątek zdarzeń zassanych z kolejki, zapamięta dane ostatnich dwóch. Wartości drążków z tych dwóch ostatnich zdarzeń mogą być większe niż threshold i dupa — świeże odpalenie akcji nie ma prawa być wykryte.
Rozwiązałem to w ten sposób, że do obiektu stanu dodałem sobie opcjonalny akumulator. Służy on jako tymczasowy bufor, do którego można pakować dane bez opamiętania — zapamiętywana jest tylko ostatnio dodana wartość (poprzednia się nadpisuje). Ten akumulator gromadzi dane niezależnie od stanu poprzedniego i aktualnego. Teraz, kiedy przetwarzane są zdarzenia danej klatki, zamiast przepisywać wartości bezpośrednio do pola current
(co najpierw przenosi dane z current
do previous
), ładuję je do akumulatora. W ten sposób usuwane są z kolejki wszystkie nieaktualne w danej klatce wartości drążka, a zostaje tylko ostatnia z kolejki zdarzeń, czyli ta ”najaktualniejsza”. Po przetworzeniu wszystkich zdarzeń z kolejki, dane z akumulatora używane są do aktualizacji stanu osi (current
do previous
, a następnie accumulator
do current
).
Od teraz świeże aktywowanie akcji typu włącz/wyłącz za pomocą gałki analogowej jest zawsze poprawnie wykrywane, bez względu na to ile zdarzeń ruchu gałki zostało w danej klatce wygenerowanych. Jest to niezwykle istotne, dlatego że to uniezależnia poprawność działania od framerate'u, jak i od lagów.