Kazik – czyli prosta platforma do prostych gier na Arduino

Kazik – czyli prosta platforma do prostych gier na Arduino

 

Od czasu, gdy przeczytałem artykuły kolegi “Snake na Arduino”  chodziło za mną wykonanie podobnej gry. Chciałem jednak, aby nie był to kolejny efekt kawałka dobrej, nikomu nie potrzebnej roboty – ale aby miało to jakieś rzeczywiste zastosowanie.
Tylko jakie może być zastosowanie prostej gierki, z niespecjalnie powalającą grafiką i procesorkiem, przy którym najstarsze smartfony prezentują jak Cray YMP przy ZX Spectrum? Teoretycznie starannie wykonana gra mogłaby być prezentem dla młodszego braciszka, ale przecież za chwilę powędrowałaby do rupieci z wgniecionym wyświetlaczem, nawet nie doczekawszy się wymiany baterii…
Tymczasem przecież sam proces tworzenia takiego urządzenia może być czymś ciekawym! Sam dość długo kombinowałem, jak zmieścić wszystko w za małej obudowie, nie mówiąc już o programowaniu. A dla majsterkowicza z zapędami do programowania takie małe urządzonko może być bardzo ciekawe. Bo napisanie gry wbrew pozorom wcale takim trywialnym zadaniem nie jest. Początkujący mając gotową platformę może zająć się pierwszymi próbami wyświetlania czegoś sensownego na ekranie i interakcji programu z użytkownikiem. Programista z pewnym doświadczeniem zajmie się samym algorytmem gry. A nawet stary informatyk, pamiętający jeszcze czasy Odry czy RIAD-a z chęcią zmierzy się z problemem animacji 8-pikselowego ludzika :)
Tak więc zastosowanie znalazłem, i trzeba było się wziąć do roboty.

Jak zwykle, postanowiłem zbudować grę wyłącznie z tego, co miałem w domu. Po dokładnym przeszukaniu warsztatu znalazłem:

Nie znalazłem nic co mogłoby zastąpić beeper (a dźwięk w grze by się przydał) – postanowiłem więc użyć głośniczka wymontowanego z niesprawnego tabletu Manty.
gra_p
Zacząłem od zmontowania układu na płytce doświadczalnej. Zamiast docelowego Pro Mini podłączyłem po prostu dyżurne Uno. Przyznam się, że miałem obawy co do wyświetlacza: próbowałem podłączyć go do ESP8266 i za nic przy zasilaniu 3.3V nie chciał pracować. Okazało się, że przy 5V ruszył i ma się całkiem dobrze.
Głośniczek okazał się za cichy, nawet przy próbie zastosowania tranzystora jako wzmacniacza. Ponieważ zależy mi na jak najcichszym dźwięku, stwierdziłem że mi to wystarczy i głośniczek podłączony jest bezpośrednio do pinu Arduino poprzez rezystor 150Ω (w rzeczywistości połączone w szerek 100Ω i 47Ω). Nie powinno to przeciążyć wyjścia Arduino (przynajmniej na razie nie przeciążyło).
Podświetlenie zasilane jest również bezpośrednio z pinu Arduino poprzez rezystor 330Ω. Jasność świecenia jest wystarczająca, a tyle Arduino musi wytrzymać.
Użyłem sprzętowego SPI – co prawda tracę przez to dwa piny których nie mogę użyć, ale i tak pozostała ilość aż nadto wystarczy do gry.

Projektowanie

Od początku chciałem zrobić coś w rodzaju uniwersalnej (w miarę) platformy, z której można korzystać pisząc po prostu moduły do konkretnych gier. Taka platforma powinna zawierać wszystkie fragmenty wspólne dla gier – tak, aby w konkretnym module nie trzeba było przejmować się dawno opracowanymi szczegółami. Postanowiłem, że będzie to po prostu zestaw często wykorzystywanych funkcji.

UWAGA!

Z uwagi na objętość kodu wraz z programami pomocniczymi postanowiłem nie zamieszczać go bezpośrednio na forum, ale udostępnić na githubie. Zainteresowanych zapraszam: https://github.com/ethanak/Kazik
Jeśli nie wiesz jak ściągnąć program, kliknij na “Clone or download”, a potem na “Download ZIP”. Co zrobić z zipem chyba nie muszę tłumaczyć :)

Na pierwszy ogień poszła obsługa joysticka. Założyłem od razu użycie analogowego joysticka, tak więc musiałem zasymulować działanie cyfrowego. W rezultacie powstała funkcja realizująca odczyt z joysticka (i ustawiająca odpowiednio bity w zmiennej stanu (tzn. dla każdego kierunku dostępne są informacje zarówno o aktualnym położeniu, jak i zdarzeniach “przesunięty/puszczony”). Dodatkowo dwie zmienne przechowują aktualne wartości analogowe:

Niestety – analogowy joystick ma jedną poważną wadę: nie da się wcisnąć przycisku jeśli joystick jest wychylony. Dlatego odpadło użycie przycisku jako “fire”. Zamiast tego postanowiłem, że wszystkie gry będą korzystać wyłącznie z kierunków, przycisk realizuje funkcję pauzy lub zapalenia/zgaszenie podświetlenia. Stąd następna funkcja, która w sposób ukryty dla programu realizuje funkcję pauzy, wyjścia z gry i włączania podświetlenia:

Funkcja ta zwraca -1 jeśli poleceniem jest “zakończ grę” lub nieujemną wartość (ilość milisekund spędzonych w funkcji, np. w czasie pauzy lub włączania podświetlenia). Wciśnięcie przycisku powoduje wejście w tryb pauzy i wyświetlenie ekranu wyboru “wróć/wyjdź”. Dłuższe przytrzymanie zapala/gasi podświetlenie.

Ponieważ użyłem biblioteki Adafruit, która zawiera większość potrzebnych funkcji związanych z wyświetlaniem, dodałem tylko dwie funkcje pomocnicze (aby uniknąć kosztownego printf):

Funkcja displayText przyjmuje jako parametr adres stringu z pamięci Flash.

Dwie następne funkcje (victory i defeat) wywoływane są odpowiednio przy wygranej lub przegranej:

Parametr ng to numer gry (jeden program może zawierać więcej niż jedną grę), potrzebny przede wszystkim do obsługi tablicy wyników, score to aktualny wynik przy wygranej. Funkcja victory zapisuje aktualny rezultat w EEPROM-ie jeśli zmieścił się w pierwszej piątce.
Ostatnie funkcje służą do obsługi dźwięku:

Jeśli w danej chwili odtwarzany jest efekt lub muzyka, funkcja playEffect przerwie odtwarzanie poprzedniego dźwięku tylko wtedy, jeśli parametr force będzie ustawiony na true.
Parametr loop funkcji playMusic określa, czy będzie ona odgrywana w kółko (true) czy zostanie zakończona po jednym odtworzeniu.
Obie funkcje przyjmują wskaźniki do danych umieszczonych w pamięci Flash. Bliższe informacje na temat formatów w komentarzach w kodzie.

Teraz mogłem zabrać się za pisanie właściwych gier.

Szybko okazało się, że w szkicu Arduino zmieści się więcej niż jedna gra. Dlatego postanowiłem odkurzyć bohatera moich starych gier sprzed trzydziestu lat – niejakiego Kazika :)
Opisy konkretnych przygód Kazika podam na koniec, na razie tylko jeden techniczny szczegół.
“Labirynt” to gra statyczna (jeśli chodzi o wyświetlanie), nie ma tu więc konieczności zajmowania się animacją. Dwie następne gry wymagają niestety animacji, a zastosowany wyświetlacz nie bardzo na to pozwala. O ile sam czas operacji rysowania i wyświetlania ekranu jest wystarczający (z pomiarów wynika, że 9 najeźdźców na drabinach w “Zamku” to ok. 10 msec), o tyle przeszkadza potężna bezwładność wyświetlacza LCD. Po wielu próbach ustaliłem, że kompromisowa wartość odświeżania ekranu to 10 Hz: przy wolniejszym traci się wrażenie ciągłości ruchu, przy szybszym bezwładność wyświetlacza powoduje znikanie coraz większej ilości szczegółów w czasie ruchu. Stąd właśnie taka a nie inna wartość w delay() w “Zamku” i “Warsztacie”.

Wykonanie

Po pierwszych próbach postanowiłem zmieścić to jednak w obudowie.
Niestety – okazało się, że obudowa jest dosłownie o parę milimetrów za mała. W szczególności nie chciała się w niej zmieścić bateria wraz z konektorem. Postanowiłem więc zrobić go sam. W tym celu wyciąłem z kawałka gumy piankowej (z opakowania od e-papierosa) cienki pasek odpowiadający szerokości baterii, dokleiłem do niego Cyjanopanem E dwie miedziane blaszki z dolutowanymi przewodami i sam pasek przykleiłem (również cyjanopanem) do bocznej ścianki obudowy.

gra_2
Oczywiście musiałem popełnić jakiś błąd – przykleiłem gumkę po niewłaściwej stronie (tam, gdzie obudowa jest niższa), przez co bateria nie kontaktuje dokładnie przy otwartej obudowie. Gdyby ktoś chciał zbudować grę umieszczając ją w tej obudowie musi pamiętać, aby konektor przykleić z lewej strony!
Następnie wyciąłem z obu części obudowy środkowy “wichajster” przeznaczony do skręcenia (jak by nie mierzyć akurat w tym miejscu wypadał joystick). W ich miejsce przykleiłem do górnej części obudowy dwa klocki wycięte z kawałka spienionego PCV 10mm. Klocki te mają jeszcze jedną funkcję: nie pozwalają przesunąć się wyświetlaczowi – tak że powinny być przyklejone w miarę precyzyjnie.
Jeśli chodzi o wyświetlacz – po zmierzeniu okazało się, że nie muszę wycinać wielkiej dziury na cały wyświetlacz, a wystarczy okienko nieco większe od części roboczej. Oprócz pewnych walorów estetycznych ma to jeszcze jedną zaletę: częściowo osłania diody oświetlające. Okienko zostało wycięte tarczą na miniwiertarce i wyrównane (hm… można to tak nazwać) nożem.
Najwięcej problemów stworzyło mi zamocowanie Arduino. Z uwagi na niewielką ilość miejsca nie mogłem sobie pozwolić na pozostawienie go luzem, a mocowanie na stałe też nie wchodziło w grę – jako że potrzebny był dostęp do pinów UART do zaprogramowania. W końcu jednak wpadłem na pomysł.
Do Arduino wlutowałem od spodu kątowe piny od strony interfejsu UART. Z drugiej strony miałem nieużywane GND, A6 i A7. Do nich też przylutowałem kawałek kątowej listwy. Tak potraktowany Arduino był nieco za szeroki, tak więc musiałem przyciąć końcówki od strony nieużywanych pinów tak, aby w miarę dokładnie zmieścił się na szerokość w obudowie.
Następnie z kawałka płyty PCV 4mm wyciąłem dwa paski o długości odpowiadającej długości wyświetlacza. Ponieważ odległość płytki wyświetlacza od dna obudowy wynosi 12mm, paski zrobiłem nieco szersze, z wcięciem na szerokości 12mm. W ten sposób wyświetlacz nie jest zamocowany do górnej części obudowy, a przed przemieszczaniem chronią go wycięcia na paskach oraz klocki mocujące.
W obu paskach wywierciłem otwory pozwalające na włożenie do nich pinów Arduino. W ten sposób Arduino po skręceniu obudowy jest zablokowany mniej więcej w połowie wysokości bez możliwości przesunięcia.

gra_3

Pozostało tylko wymierzyć i wywiercić w dnie dwa otwory na wkręty i nawiercić otwory w klockach mocujących.
Joystick został po prostu przykręcony czterema śrubkami M2 do dolnej części obudowy. Wyłącznik zasilania przykręciłem do bocznej ścianki. Głośniczek w ogóle nie jest przymocowany – jest tam na tyle mało miejsca, że trzymają go po prostu przewody łączące Arduino z wyświetlaczem. Wszystkie elementy zostały połączone bezpośrednio kawałkami cienkiego przewodu.
Wszystko najlepiej dokumentują zdjęcia.

Instalacja bibliotek

Musimy zaopatrzyć się w następujące biblioteki:

Zanim zaczniemy zabawę, musimy nieco przerobić obie biblioteki Adafruit.
W folderze biblioteki Adafruit_GFX znajduje się plik glcdfont.c, odpowiadający za wyświetlanie tekstów. Niestety – dla polskiego użytkownika kody są raczej mało przydatne (brak polskich liter), toteż przygotowałem własny odpowiednik tego pliku, w folderze Kazik/patched. Po podmienieniu oryginalnego pliku na mój zyskujemy wszystkie polskie literki, znak stopnia oraz kilka znaków dodatkowych potrzebnych do gry na pozycjach 1..31. Dodatkowo obsługiwane są jedynie siedmiobitowe kody, co pozwala zmniejszyć wielkość szkicu o ponad pół kilobajta.
Przeróbka drugiej biblioteki nie jest bezwzględnie konieczna, ale pozwala oszczędzić następne kilkaset bajtów szkicu.
W folderze biblioteki Adafruit_PCD8544 znajdujemy plik Adafruit_PCD8544.cpp, a w nim deklarację zmiennej pcd8544_buffer. Bufor jest oryginalnie zainicjalizowany splash screenem, który w założeniach miał być prawdopodobnie logo Adafruit, a w rzeczywistości wygląda jakby się wyświetlacz popsuł. Wygląda to tak:

Ponieważ logo wyświetlać nie będziemy (licencja nakazuje pozostawienie go tylko przy redystrybucji) – możemy spokojnie zakomentować inicjalizator. Tak więc wstawiamy znaki komentarza we właściwe miejsca, i cała deklaracja powinna wyglądać mniej więcej tak:

Zaznaczam, że do działania programu nie jest to niezbędne, ale czasem te paręset bajtów to kwestia zmieszczenia programu w pamięci Arduino…

Konfiguracja programu

Zanim wgramy program do Arduino, musimy najpierw go skonfigurować. W tym celu otwieramy plik Game.h – na początku pliku są zdefiniowane stałe odpowiadające m.in. za podłączenie joysticka i wyświetlacza. W zależności od potrzeb zakomentowujemy/odkomentowujemy następujące linie:

Dodatkowo mamy do dyspozycji dwie stałe:

W moim przypadku ustawiłem na stałe wyłączenie zrzutów dla Arduino Pro i zablokowałem nagrywanie filmów, ale w przypadku pisania własnych modułów gier można je odkomentować. Zrzuty ekranu będą wykonywane za każdym wejściem w tryb pauzy. O nagrywaniu filmików napiszę później.

Po takich przygotowaniach możemy wgrać program do naszego Arduino i cieszyć się grą :)

A teraz obiecane przygody Kazika

Labirynt

maze1maze2maze3maze4

Od czasu, kiedy Trzecia Ziemska Ekspedycja odkryła prastare ruiny na planecie R’hat Wook’M wielu poszukiwaczy przygód przylatywało na planetę, aby znaleźć skarby pozostawione przez dawno wymarłą cywilizację. Niektórym się to udawało, inni znikali w tajemniczy sposób. Również nasz dzielny kosmonauta Kazik zapragnął spróbować swych sił w poszukiwaniu.
Niestety – nie wszystko poszło dobrze. Zamiast znaleźć właściwe ruiny, wpadł przypadkiem do przeszukanego już dawno Labiryntu. Na domiar złego uszkodził aparat oddechowy, awaryjny zapas tlenu starczał na krótko, a z każdym krokiem i oddechem zmniejszał się jeszcze bardziej. Jedynym ratunkiem dla dzielnego Kazika byłoby znalezienie jak najszybciej wyjścia z labiryntu.
Gdyby tylko… ale brama wyjściowa była zamknięta, a otwarcie jej wymagało aż trzech kluczy, których Kazik oczywiście nie miał.
Na szczęście poprzednie ekspedycje pozostawiły po sobie porzucone przedmioty. Były tam mapy labiryntu, klucze, kompasy, a przy każdym niewielki zapas tlenu na czarną godzinę.
Czy Kazik zdąży znaleźć wyjście i przejść przez bramę zanim skończy mu się tlen?

Sterowanie:
Lewo/Prawo – obrót
Góra – krok
Dół – widok mapy (trzeba ją oczywiście najpierw znaleźć)

Zamek

zamekKliknij aby obejrzeć film
(oryginalnie stworzony na C16/C+4, jak to przed 30 laty wyglądało można zobaczyć – a nawet zagrać – na stronie archiwum gier)

Gdzieś tak pod koniec średniowiecza rycerze mocno podupadli;
Taki na przykład Kazik Dreptak to nie miał nawet zwykłej szabli!
Więc gdy Dreptaka zamku mury oblegać jęli Tatarzyni
Kazik zachodzić zaczął w głowę: co też mu teraz przyjdzie czynić?
A tu Tatarzy przystawiają do ścian zamkowych swe drabiny,
Już po drabinach się wspinają i bardzo tęgie mają miny…
Więc Dreptak z góry kamieniami rzuca po minach i drabinach,
Lecz co się jeden Tatar zwali następny za nim już się wspina!
I choć z odwagi Dreptak słynął i wokół miał kamieni wiele
W końcu Tatarzy go dopadli i wnet go do niewoli wzięli.

Sterowanie:
Lewo/Prawo – ruch
Góra – zrzucenie kamienia
Nowy kamień należy zabrać dochodząć do końca muru z dowolnej strony.

Warsztat

warsztatKliknij aby obejrzeć film

Tym razem czasy współczesne. Majsterkowicz Kazik znany jest przede wszystkim z dwóch rzeczy: niechęci do sprzątania warsztatu oraz panicznego lęku przed pająkami. Tymczasem w warsztacie Kazika coraz trudniej znaleźć jakiekolwiek narzędzia, a w dodatku gdzieś w zakamarkach gnieżdżą się wielkie, czarne i jadowite pająki. Próby posprzątana warsztatu utrudniają jeszcze porozrzucane kiedyś kosze na śmieci, które w każdej chwili mogą się przewrócić i spaść Kazikowi na głowę…

Ciekawe, ile narzędzi Kazik znajdzie przy okazji sprzątania wszystkich trzech pomieszczeń warsztatu?

Sterowanie:
Joystick – kierunek ruchu
Aby przejść na następny poziom, należy zebrać odpowiednią ilość narzędzi. Możliwość przejścia sygnalizowana jest wyświetlanym symbolem klucza. Zatrzymanie się w drzwiach spowoduje przejście do następnego warsztatu lub (po przejściu trzech) zakończenie gry.

A jeśli będziesz chciał zrobić taką grę…

Przede wszystkim: użycie Arduino Pro Mini wynikało jedynie z faktu, że taki miałem. Jeśli gra miałaby służyć do tego, co miałem w zamyśle tworząc ten projekt (tzn. do nauki programowania) – należałoby użyć którejkolwiek płytki wyposażonej w USB, najlepiej wersji Pro Micro i udostępnić z boku gniazdo USB. W takiej sytuacji można wszystko ładnie przymocować do dna obudowy, jako że nie ma potrzeby dostępu do samej płytki (koniecznego w przypadku wersji Pro Mini). Pamiętać jedynie należy o prawidłowym podłączeniu wyświetlacza – piny SPI są inne w werjach Mini i Micro!

Należy zastosować normalny beeper a nie kombinować z jakimiś szemranymi głośniczkami. Dodatkowo można zastosować jakiś prosty wzmacniacz z regulacją siły głosu – wtedy w miejsce wyłącznika można zastosować miniaturowy potencjometr z wyłącznikiem.

Pierwszą wersję gry warto zmontować na płytce doświadczalnej – choćby po to aby ocenić, czy to coś do czegokolwiek się nadaje bez konieczności bawienia się w dopasowywanie obudowy.

Przy tworzeniu nowych modułów pomocne mogą być programy zawarte w folderze utils:

  • logo.py – przykład tworzenia bitmapy
  • scr2img.py – zamiana zrzutu ekranu na obrazek (wymagana biblioteka PIL)
  • serrecord.py – zapis do pliku danych otrzymanych z seriala
  • ser2mov.py – stworzenie filmu z zapisanych danych (wymagana biblioteka PIL oraz libav-tools)

Programy są przeznaczone dla Linuksa, jednak po małej przeróbce powinny uruchomić się pod Windows. Jedyny wyjątek to ser2mov.py, korzystający z funkcji fork() (której dla Windowsa jakoś jeszcze nie wymyślili): pod Windows należy zamiast na pipę wysłać dane PPM do pliku, a następnie za pomocą dowolnego narzędzia przerobić strumień obrazków na film.

Jeśli włączony jest SERIAL_DEBUG, zrzuty ekranu robione są automatycznie przy każdym włączeniu pauzy. Funkcja wyrzuca na wyjście serial zawartość ostatnio wyświetlonego w grze bufora wyświetlacza. Aby otrzymać obrazek, należy wypisane linie skopiować i wkleić we właściwe miejsce do kodu w programie scr2img.py. Po wywołaniu:

w pliku “obrazek.png” znajdzie się obrazek przedstawiający zrzut ekranu w powiększeniu 2x z 5-pikselowymi marginesami.
I tu uwaga: program przystosowany jest do odwróconego wyświetlacza (ak mi akurat pasowało zarówno na płytce doświadczalnej, jak i w obudowie). Jeśli jest to potrzebne, należy zamienić współrzędne w pętli, np. zamiast

dać po prostu:

Trochę więcej komplikacji jest z tworzeniem filmików.
Przy włączeniu jednocześnie SERIAL_DEBUG i RECORD_MOVIE prędkość Serial ustawiana jest na 115200, aby uniknąć opóźnień w reakcji gry. Jednocześnie w krytycznym miejscu gry – tam, gdzie wyświetlana jest przygotowana zawartość bufora – należy zastosować konstrukcję:

Spowoduje to wysłanie na port screenshota z każdej wyświetlanej ramki w grze. Grę uruchamiamy przy wyłączonym monitorze Serial i włączonym serrecord.py (powinien sam znaleźć port Arduino), a następnoie po zatrzymaniu zapisu traktujemy powstały plik poleceniem:

(gdzie film.mkv to nazwa pliku wynikowego). Plik filmu utworzony zostanie z 10 fps, zgubione klatki (to się może zdarzyć przy tego rodzaju transmisji) zostaną po prostu pominięte.

Zachęcam  więc do prób zmierzenia się ze światem prostych gier :)

Pliki załączone do artykułu:

Ocena: 4.93/5 (głosów: 15)

Podobne posty

3 komentarzy do “Kazik – czyli prosta platforma do prostych gier na Arduino

  • Można tu pokusić się o zaimplementowanie czytnika kart sd gdyż można by było wtedy uruchamiać więcej gier prze arduino.
    To przyszłościowe rozwiązanie może przysporzyć tobie trochę fatygi ale dzięki temu platforma stanie się z ubogiej doskonalsza.

    Odpowiedz
  • Wydaje się to technicznie trudne.
    Możliwe jest oczywiście ładowanie z karty SD pewnych reguł, czy plansz. W pamięci rezydowałaby gra, która potrafiłaby je interpretować. Informacje umieszczone na karcie rozszerzałyby treść tej gry, ale nie zmieniłyby jej diametralnie, główna logika musiałaby pozostać taka sama.
    Ładowanie całych gier z różną logiką jest większym wyzwaniem. Na karcie umieścić można by umieścić skompilowane sketche Arduino. Zmodyfikowany bootloader wyświetlałby ich listę i wgrywał plik binarny do pamięci płytki.
    Idealnym rozwiązaniem byłoby dynamiczne ładowanie fragmentów programu gier (bez flashowania pamięci), coś podobne w kształcie do działania bibliotek DLL. Nie jest mi niestety znany sposób na osiągnięcie tego.

    Odpowiedz

Odpowiedz

anuluj

Masz uwagi?