Gameduino – czyli zróbmy fajną grę
W zasadzie od momentu kiedy w ofercie Nettigo pojawił się pierwszy czujnik przyspieszenia miałem ochotę zrobić pewien hack i wykorzystać go jako główny czujnik do jakiegoś urządzenia sterującego. Myślałem o grze sportowej i podłączeniu czujnika do komputera. Ale, czasu ciągle brakowało, a przynajmniej odnosiłem wrażenie, że hack będzie długi i skomplikowany. Odczyt z czujnika przetworzyć na ciąg wciśnięć klawiszy w komputerze. Arduino, serial, jakaś biblioteka odczytująca serial, potem emulacja wciśnięcia klawisza. Może to wszystko okaże się prostsze niż wygląda, ale sprawiało wrażenie dużej liczby ruchomych części, a co za tym idzie większą szansę niepowodzenia. No cóż, po prostu nie chciało mi się :)
Gdy pojawiło się Arduino UNO, przez chwilę pojawiła się chęć przetestowania klawiaturowego firmware dla ATmega8u2 w ramach tego właśnie hacku, ale projekt nie wystartował. Co UNO zmieniało? Otóż z nowym firmware, podłączone do komputera widziane jest jako klawiatura więc odpada cała komplikacja z odczytem seriala i wysyłania komunikatów do gry. Po prostu Arduino może 'wciskać klawisze’ przez port USB i całość wydaje się znacznie prostsza.
Ale gdy tylko zobaczyłem Gameduino wiedziałem że to jest to czego potrzebowałem. Ostatnie dni deszczowe były, tak więc – do dzieła!
Najpierw – jedno z dem dołączonych do biblioteki to Asteroids – lecisz statkiem przez pole asteroidów i strzelasz do nich. Proste. Rozpakować bibliotekę do sketchbook/libraries
, uruchomić Arduino IDE, menu File/Examples/Gameduino/Demos/asteroids. Kompilacja, upload i mamy. Tyle, że nie mamy żadnego kontrolera do sterowania statkiem. Ja do pierwszych testów użyłem Nettigo Keypada, bo go pod ręką miałem.
Wystarczy zmienić kod w controller_sense
i zamiast domyślnego kodu przeznaczonego dla game controllera od SparkFun użyć swojego. Ale to mało pasjonujące naciskać na klawisze, byle konsola to ma :)
Czas na Level 2
Podłączyć czujnik przyspieszenia i zrobić jakąś platformę, na której stojąc będzie się sterować statkiem. Przechył w lewo – obrót w lewo. Przechył w prawo – obrót w prawo. Przechył do przodu – lecimy. W tył – strzelamy. Taki Segway. No może bez tego strzelania :)
Pierwsza rzecz jak zrobić samą platformę? Miałem pod ręką kawałek panela podłogowego. Do niego przyklejona płytka stykowa 170 (dzięki swojej taśmie samoprzylepnej). Na niej czujnik przyspieszenia, kabelki zgodnie z opisem w dokumentacji czujnika i możemy ustalić położenie panela. Interesują nas dwie osie – X i Y, Z na razie nam niepotrzebne :)
Pozostaje teraz rozwiązać konstrukcję modułu wychylnego. Musi to być coś co pozwoli naszemu panelowi kiwać się na wszystkie strony, jednocześnie nie za bardzo, tak aby gracz nie spadał z niego. Po wielu testach różnych materiałów i różnych rozwiązań mocowań konsultowanych z NASA nasze finalne rozwiązanie wygląda tak:
Jak widać wybór padł na starą poduszkę :) Panel położony na niej ma dość swobody aby poruszać się w dwóch osiach na tyle aby czujnik mógł odczytać położenie.
Hard za nami, teraz soft
Teraz trochę kodu, czyli w zasadzie jedyna zmiana w kodzie dema, to nowa funkcja controller_sense
:
#define LR_NEUTRAL 330 #define UD_NEUTRAL 345 #define LR_DELTA 8 #define UD_DELTA 7 static byte controller_sense(uint16_t clock) { int rd,a1,a2; rd = 0; a1 = analogRead(3); a2 = analogRead(4); if (a1 < LR_NEUTRAL - LR_DELTA){ // Serial.println("Lewo"); rd = rd | CONTROL_LEFT; } else if (a1 > LR_NEUTRAL + LR_DELTA) { rd = rd | CONTROL_RIGHT; // Serial.println("Prawo"); } if (a2 < UD_NEUTRAL - UD_DELTA){ rd = rd | CONTROL_DOWN; // Serial.println("Jazda"); } else if (a2 > UD_NEUTRAL + UD_DELTA) { rd = rd | CONTROL_UP; // Serial.println("Ognia"); } return rd;
Teraz kilka słów wyjaśnienia. LR_NEUTRAL
to wartość jaka jest odczytywana w osi X w pozycji neutralnej. LR jest od Left Right, bo przechył w osi X w naszym ułożeniu czujnika oznacza przechył w lewo/prawo. LR_DELTA to jest margines o jaki może się w każdą stronę zmienić odczyt od pozycji neutralnej zanim zostanie uznany za wciśnięcie lewo lub prawo.
Podobnie jest z UD (od up i down). Oznaczenia te pochodzą od oznaczeń używanych w kodzie dema, nie od kierunku ruchu panela :)
rd
jest na początku ustawiane na zero i jeżeli wychył od pozycji neutralnej jest dostatecznie duży odpowiednia wartość CONTROL_ jest ORowana. O co chodzi? Jeśli spojrzeć na definicję wartości CONTROL_ :
#define CONTROL_LEFT 1 #define CONTROL_RIGHT 2 #define CONTROL_UP 4 #define CONTROL_DOWN 8
są to kolejne potęgi 2. W zapisie binarnym odpowiada to kolejnym bitom. 1 to 0001, 2 to 0010, 4 to 0100, itd. Logiczne OR dwóch liczb (w C to symbol tzw pałki | ) zwraca wartość gdzie bity są ustawione na 1 wszędzie tam gdzie choć w jednej z liczb była jedynka. Na przykład:
- 0000 OR 0001 = 0001
- 0000 OR 0010 = 0010
- 0100 OR 0010 = 0110
- 0100 OR 0100 = 0100
W ten sposób wiedząc że 1 na ostatnim bicie to CONTROL_LEFT a 1 na pierwszym to CONTROL_DOWN, można w jednej liczbie całkowitej przekazać informację, że wciśnięte zostały dwa klawisze jednocześnie: 1001 czyli 9 dziesiętnie. Bardzo często stosowana metoda przekazywania stanu różnych rzeczy przez jedną liczbę całkowitą.
A jak potem łatwo można odczytać czy bit jest ustawiony? Ano z wykorzystaniem AND (znak &):
- 1001 AND 1000 = 1000
- 1001 AND 0100 = 0000
- 1001 AND 0010 = 0000
- 1001 AND 0001 = 0001
Teraz już chyba jest jasne w jaki sposób przez rd
jest zwracana informacja o równocześnie naciśniętych przyciskach.
Co do naszego czujnika – wartości _NEUTRAL oraz _DELTA zostały dobrane eksperymentalnie – obserwując odczyty z czujnika, które na ten czas wysyłane były na Serial. Po ich ustaleniu komendy Serial.print
zostały usunięte, bo zajmują cenne takty procesora ;)
Jak to wychodzi w praktyce można zobaczyć na tym krótkim filmiku (nie wiem jak go odwrócić, aparat było mi wygodniej trzymać w pionie):
Ze względu na dynamikę całości :) niezbędne jest osłonięcie czujnika aby zabezpieczyć przed przypadkowym zgnieceniem:
Raport Millera – czyli co można poprawić
Położenie neutralne zmienia się nieco (tzn odczyt z czujnika), w zależności od zasilania Arduino (np mój notebook zamiast 5V na USB ma 4.9, co przy niewielkich zmianach napięcia na wyjściu czujnika może mieć znaczenie – punkt odniesienia się zmienia to i odczyt zwracany przez analogRead
się zmienia). Również z czasem czujnik może minimalnie zmienić odczyt. Wówczas zostaje ręczna zmiana wartości w kodzie, kompilacja i wgranie do Arduino.
Lepszym rozwiązaniem byłaby kalibracja – otóż specjalny tryb, zamiast grania wyświetli polecenie: Wychyl się w lewo. Potem odczytuje wartość z czujnika. Następnie Wychyl się w prawo i znowu. W ten sposób szkic jest sam w stanie wyznaczyć sobie wartości _NEUTRAL i _DELTA.
Druga uwaga – dynamika gry jest duża i chyba taki kontroler byłby lepszy dla jakiejś sportowej niż strzelanki. Ale to już lepiej byłoby jednak użyć klawiaturowego firmware i komputera, może innym razem…