Pojďme programovat elektroniku | Programování | Hry

Pojďme programovat elektroniku: Vyrobíme nový herní hit Útok ufonů

  • Už vás nudí Nintendo Switch a Xbox?
  • Máme pro vás něco mnohem lepšího
  • Útok ufonů na dvouřádkovém znakovém displeji

Haló, haló, tady protiletecká obrana České republiky. Nad Šumavou se z deštivých mraků vynořily obří levitující chobotnice a plazmovými děly zlikvidovaly Kvildu. Povolejte gripeny!

Právě tak by mohl začínat infantilní příběh nové kapesní herní konzole, která v jednom z paralelních vesmírů právě dobývá trh. Lidé dychtivě stojí fronty na malý mikropočítač s předinstalovanou hrou Útok ufonů a internetové bazary se plní konzolemi Nintendo Switch, které najednou působí tak zastarale...

LCD Keypad Shield za padesátikorunu

My si to dnes vyzkoušíme v praxi, v pokračování našeho seriálu o programování elektroniky si totiž představíme další rozšiřující desku pro Arduino Uno – LCD Keypad Shield.

V minulosti jsme si vyzkoušeli tyto rozšiřující shieldy pro Arduino Uno:

Na Aliexpressu jeho cena začíná zhruba na padesátikoruně, český Arduino-shop.cz pak nabízí totožnou desku bez čekání za dvě stovky. Její součástí je vše, co dnes budeme potřebovat, tedy displej a ovládací tlačítka.

Klepněte pro větší obrázek
LCD Keypad Shield se znakovým modrým displejem 16×2 a směrovými tlačítky

Dvouřádkový modrý znakový displej 16×2

Shieldu vévodí velký znakový displej, na který se vejde šestnáct písmen ve dvou řádcích. Displej má modrý nádech, a přestože jsou dnes jeho samostatné moduly pro Arduino často vybavené řadičem s úspornou sběrnicí I2C, tento používá přímé paralelní spojení, které Arduinu uzme 6 pinů GPIO pro samotné ovládání a volitelně ještě pin 10 pro programové nastavení podsvícení pomocí signálu PWM.

Kontrast znaků nad modrým pozadím nastavíte pomocí modrého trimru v levém horním rohu. Displej jej má od výroby často stažený na minimum, takže je třeba šroubem trimru otáčet tak dlouho, dokud nespatříte matici znaků.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Vedle displeje najdete na shieldu také několik směrových tlačítek s popisky a otvory nevyužitých pinů GPIO. Modrým trimrem nastavíte kontrast displeje.

Displej uzme 6+1 pinů GPIO, jak ale napovídá schéma níže, stále jich hromada zůstane pro vaše další využití. Dostupné jsou například všechny analogové piny vyjma A0, GPIO piny sériové linky a také sběrnice SPI a I2C.

Klepněte pro větší obrázek
Schéma desky s vyznačenými volnými piny GPIO u otvorů, ke kterým můžete přímo připájet vlastní obvod, nebo klasickou lištu,

Tlačítka jsou napojená na analogový pin A0

Fajn, ale co ta tlačítka? Arduino Uno zase nemá tolik pinů, takže jak je možné, že jich zůstane tolik volných? Odpověď je jednoduchá, všechna směrová tlačítka jsou připojená k jednomu jedinému analogovému pinu – zmíněnému A0 a to pomocí děliče napětí.

To v praxi znamená, že stiskem některého ze směrových tlačítek spojíte obvod, který na pinu A0 vytvoří odlišné napětí. V kódu pak stačí přečíst digitální hodnotu z A/D převodníku pomocí funkce analogRead(A0) a vypsat ji třeba do sériové linky.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Rozšiřující desku s displejem snadno zacvaknete do základní desky Arduino Uno 

Arduino Uno má 10bitový A/D převodník a v klidovém stavu by mělo být na pinu A0 pracovní napětí 5 V, takže A/D převodník vrátí +/- plnou hodnotu 1 023. Když stisknete některé z tlačítek, napětí klesne třeba na polovinu a A/D převodník následně vrátí celé číslo 500. 

Tímto způsobem snadno zjistíte, jakou hodnotu vrací A/D převodník při stisku každého z tlačítek, a jelikož jsou tyto hodnoty poměrně stabilní a dostatečně odlišné, můžeme je použít v kódu pro identifikaci stisku konkrétního tlačítka.

Displej oživíme pomocí knihovny LiquidCrystal

Tak, tlačítka, kterými budeme ovládat naši kapesní herní konzoli, bychom měli, tak teď ještě ten displej. Bude to poměrně jednoduché, podobné znakové LCD totiž vývojové prostředí Arduino podporuje pomocí předinstalované knihovny LiquidCrystal.

Klepněte pro větší obrázek
16×2 znakový LCD. Všimněte si tmavší oblasti pro každý ze znaků.

Nejprve tedy v kódu vytvoříme objekt displeje a řekneme našemu hlavnímu čipu, jaké piny má použít k paralelní komunikaci s displejem. Poté displej nastartujeme a ovládáme pomocí několika málo metod pro nastavení kurzoru, psaní textu nebo třeba smazání displeje.

Klepněte pro větší obrázek
Test displeje a vypsání základní abecedy a několika číslic

Docela stručný kód níže pomocí knihovny LiquidCrystal a jejího fontu vykreslí na displej znaky anglické abecedy jako na fotografii výše a program skončí.

#include <LiquidCrystal.h>

// Displej je k Arduinu pripojeny paralelne pomoci techto pinu
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
  // Nastartovani displeje s rozlisenim 16x2 znaku
  lcd.begin(16, 2);
  // Nastaveni kurzoru na zacatek prvniho radku
  lcd.setCursor(0, 0);
  // Vypsani 16 znaku abecedy
  lcd.print("ABCDEFGHIJKLMNOP");
  // Nastaveni kurzoru na zacatek druheho radku
  lcd.setCursor(0, 1);
  // Vypsani zbytku abecedy a nekolika cislic
  lcd.print("QRSTUVWXYZ012345");
}

Pro pořádek se ještě podívejme, jak na displeji vypadá nějaký smysluplnější text. Většina knihoven pro displeje pracuje jen se základními anglickými znaky ASCII a nepodporuje rozšířené znaky s diakritikou. Ty bychom si museli nakreslit sami.

Klepněte pro větší obrázek
O něco smysluplnější text na našem znakovém displeji

Každý znak se skládá z 5×8 pixelů

Jak je patrné na snímcích, každý ze znaků se dále skládá z matice zřetelných čtvercových pixelů oddělených drobnou mezerou. Vedle základních písmen a číslic, které nabízí knihovna, si z těchto pixelů můžeme poskládat i vlastní grafiku. Slouží k tomu metoda createChar.

Klepněte pro větší obrázek
Vlevo makrosnímek jednoho znaku displeje a vpravo schéma jeho matice pixelů

Jak ukazuje obrázek písmene A, každý znakový blok představuje matici o osmi řádcích a pěti sloupcích.

Knihovna LiquidCrystal s touto maticí pracuje jako s polem osmi bajtů pro každý z řádků. Prvních pět bitů každého bajtů pak představuje jednotlivé sloupce. Jedničkový bit pixel rozsvítí, nulový analogicky zhasne.

Písmeno A bychom tedy mohli v našem kódu vytvořit i tímto způsobem, přičemž hodnotu bajtů pro větší názornost napíšeme ve dvojkovém zápisu (uvedeným znakem B):

uint8_t A[8] = {
  B01110,
  B10001,
  B10001,
  B10001,
  B11111,
  B10001,
  B10001,
  B00000
};

Kód níže pomocí metody createChar promění pole pixelů ve znak, který konečně můžeme použít a bude dostupný na speciálním indexu 1 (vlastních znaků můžeme vytvořit osm a číslovat je 0 až 7). Znak definitivně vykreslíme na displej pomocí funkce write.

lcd.createChar(1, A);
lcd.setCursor(0,0);
lcd.write(uint8_t(1));

Namísto vlastních písmen vykreslíme raketoplán, ufony a střely

Jelikož chceme proměnit Arduino Uno a shield s displejem a tlačítky v kapesní herní konzoli, postup výše nepoužijeme k vykreslování vlastních písmen, ale primitivní bodové grafiky. 

Pro kresbu složitějších tvarů a jejich převod do C/C++ pole skvěle poslouží webová stránka LCD Custom Character Generator, na které klikáním na matici pixelů vytvoříte, co potřebujete a jen zkopírujete a upravíte hotový kód.

Klepněte pro větší obrázek
Webový generátor znaků pro LCD tohoto typu

Tímto způsobem jsem postupně vytvořil z pouhých 40 pixelů nádherný raketoplán vybavený raketometem, efekt letící střely, která zlikviduje zlé emzáky a konečně ošklivou chobotnici. Vše završuje fotorealistický efekt jaderného hřibu, který se zobrazí při úspěšném zásahu chobotnice.

No dobrá, uznávám, že má grafika oproti současnému ray tracingu aspol. své mezery, ale více toho prostě z dvouřádkového znakového displeje nevytáhneme.

Klepněte pro větší obrázek
Raketoplán: předloha a verze pro displej
Klepněte pro větší obrázek
Letící střela s jadernou hlavicí proti emzákům
Klepněte pro větší obrázek
Ošklivá chobotnice z planety Octopus
Klepněte pro větší obrázek
Zásah chobotnice střelou a termonukleární exploze

Herní logika Útok ufonů

Jádrem každé počítačové hry nicméně není počet polygonů a fotorealistické textury, ale vnitřní logika a příběh. Aby byla kapesní konzole Útok ufonů dostatečně zábavná i pro jedenáctihodinový let do Kalifornie, potřebuje prostě zaujmout.

Klepněte pro větší obrázek
Hra Útok ufonů startuje. Vyrobily Warpug Studios.

Využijeme toho, že má displej dva řádky a rozdělíme do nich herní scénu. Zatímco v horním řádku se bude na náhodné pozici (0 až 15) v určitém časovém intervalu zobrazovat útočící chobotnice, ve spodním řádku se bude pohybovat ve stejném rozsahu doleva a doprava náš bojový raketoplán.

Klepněte pro větší obrázek
V horním řádku se pohybuje mimozemská chobotnice a ve spodním náš raketoplán

K samotnému pohybu použijeme směrová tlačítka LEFT a RIGHT. Stisk a následné uvolnění tlačítka posune raketoplán o jednu pozici. Cílem hráče bude to, aby se v jeden okamžik ocitly raketoplán i chobotnice na stejné pozici. Pokud v takovém případě stiskne tlačítko UP, střela zasáhne cíl, hráč získá bod a celou obrazovku vyplní termonukleární výbuch.

Klepněte pro větší obrázek
Vystřelil jsem na chobotnici, ale minul jsem. To je zlé, emzáci dále obsazují Šumavu.

Pokud však cíl mineme, zobrazí se jen obraz letící střely a o bod naopak přijdeme – ano, skóre pak může být i záporné.

Kdykoliv v průběhu si můžeme nechat zobrazit herní statistiku – skóre a délku v minutách – klepnutím na tlačítko DOWN. Vše pak anulujeme stisknutím tlačítka RST, které, jak už název napovídá, vede na systémový reset řídícího čipu ATmega328P. 

Klepněte pro větší obrázek
Klepněte pro větší obrázek
Přímý zásah! Chobotnici sežehl termonukleární žár a já získal první bod.

Aby měla hra nějaký vývoj, každých deset získaných (nebo i ztracených) bodů chobotnička zrychlí. Přesněji řečeno, jakmile bude skóre dělitelné deseti, zkrátí se prodleva mezi změnou polohy chobotnice o 0,5 s. Ve výchozím stavu to budou 3 s, aby měl zelenáč dostatek času přejít na správnou pozici a zaútočit, ale když prodleva klesne na 1,5 s, dají to jen profesionální hráči.

Video: Útok ufonů

Co bychom mohli dále dodělat?

Základní hra funguje, její princip je jednoduchý a opravdu to na chvíli zabaví. Zároveň je snadno rozšiřitelná. Volných pinů GPIO bychom mohli využít třeba k připojení primitivního bzučáku, který pípne třeba při každém pohybu raketoplánu, při zásahu, případně zahraje pomocí několika tónů úvodní intro.

Kdybychom zároveň namísto základní desky Arduino Uno použili její modifikaci se sekundárním Wi-Fi čipem ESP8266, mohli bychom si napsat jednoduchý a rychlý multiplayer – v rámci sítě LAN ideálně skrze protokol UDP. Jeden hráč by pak byl chobotnička, zatímco druhý raketoplán. 

To si ale vyzkoušíme třeba zase někdy příště. Naše kapesní herní konzole může na trh!

Zdrojový kód

Na úplný závěr nesmí jako vždy chybět komentovaný kód celého firmwaru, který pohání hru Útok ufonů.

// Knihovna pro ovladnuti displeje. Je soucasti Arduina.
// Dokumentace: https://www.arduino.cc/en/Reference/LiquidCrystal
#include <LiquidCrystal.h>

// Pomocna makra pro identifikaci stisknutych tlacitek
#define DOPRAVA 0
#define DOLEVA  1
#define NAHORU  2
#define DOLU    3
#define ZADNE   4

// Pomocne makro pro delku uvodniho intra
#define INTRO   5000

// Nastartovani displeje. Tyto piny GPIO nesmite pozuivat k nicemu jinemu
// Stejně tak pin A0 (tlacitka) a 10 (podsviceni)
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Vychozi prodleva mezi zmenou polohy chobotncie v ms
uint16_t prodlevaUfona = 3000;
// Pomocna promenna pro nacasovani zmeny polohy ufona
uint32_t casUfona = 0;
// Aktualni pozice chobotnice
int8_t ufonX = 0;
// Aktualni pozice raketoplanu
int8_t raketoplanX = 8;
// Aktualni skore
int32_t skore = 0;
// Pomocna promenna, zda-li je zobrazena statistika hry, aby se zbytek hry pozasatavil
bool statistika = false;

// Bitmapa raketoplanu
// Lze vytvorit ve webovem generatoru: https://maxpromer.github.io/LCD-Character-Creator/
uint8_t raketoplan[] = {
  B00100,
  B00100,
  B01110,
  B01110,
  B01110,
  B11111,
  B11111,
  B00100
};

// Bitmapa chobotnice
uint8_t hruzostrasnyUfon[] = {
  B01110,
  B11111,
  B11111,
  B01110,
  B10101,
  B10101,
  B10101,
  B10101
};

// Bitmapa strely
uint8_t vystrel[] = {
  B00100,
  B00000,
  B00100,
  B00000,
  B00100,
  B00000,
  B00100,
  B00000
};

// Bitmapa vybuchu
uint8_t vybuch[] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};

// Hlavni funkce setup se spusti po startu
void setup() {
  // Start displeje o 16 znacich a dvou radcich
  lcd.begin(16, 2);

  // Vytvoreni vlastnich znaku 0 az 3 s grafickymi prvky
  lcd.createChar(0, raketoplan);
  lcd.createChar(1, vystrel);
  lcd.createChar(2, vybuch);
  lcd.createChar(3, hruzostrasnyUfon);

  // Nastaveni kurzoru na zacatek prvniho radku
  lcd.setCursor(0, 0);
  // Vypsani nazvu hry do prvniho radku
  lcd.print("-= Utok ufonu =-");
  // Nastaveni kurzoru na zacatek druheho radku
  lcd.setCursor(0, 1);
  // Vypsani vyrobce hry
  lcd.print(" Warpug Studios");

  // Pockej nekolik sekund
  delay(INTRO);
  // Smaz displej a zacni hrat
  lcd.clear();
}

// Smycka loop se spusti po skonceni funkce setup a opakuje se stale dokola
void loop() {
  // Zjisti, jestli jsem nestiskl nejake tlacitko
  uint8_t tlacitko;
  if ((tlacitko = jakeTlacitko()) != ZADNE) {

    // Pokud jsem stiskl tlacitko LEFT
    if (tlacitko == DOLEVA) {
      // Posun polohu raketoplanu o znak vlevo
      raketoplanX--;
      // Pokud raketoplan zajede za levy okraj, objevi se u praveho
      if (raketoplanX < 0) {
        raketoplanX = 15;
      }
      // Smaz displej
      lcd.clear();
    }

    // Opacna akce, pokud jsem stiskl tlacitko RIGHT
    if (tlacitko == DOPRAVA) {
      raketoplanX++;
      if (raketoplanX > 15) {
        raketoplanX = 0;
      }
      lcd.clear();
    }

    // Pokud jsem stiskl tlacitko UP pro strelbu
    if (tlacitko == NAHORU) {
      // Nakresli vystrel
      nakresliVystrel();
      // Pokud jsou raketoplan i chobotnice na stejne poloze,
      // jedna se o primy zasah!
      if (raketoplanX == ufonX) {
        // Pripocitej skore
        skore++;
        // Pokud je skore delitelne deseti, zrychli pohyb chobotnice o 500 ms
        if ((skore % 10) == 0) {
          prodlevaUfona -= 500;
        }
        // Nakresli zasah - vybuch atomove bomby
        nakresliZasah();
      }
      // Pokud jsem minul, odecti mi skore
      else {
        skore--;
      }
    }

    // Pokud jsem stiskl tlacitko DOWN pro statistiku
    if (tlacitko == DOLU) {
      // Pokud neni statistika zobrazena, zobraz ji
      // Vypise aktualni skore a dobu hry v minutach
      if (!statistika) {
        statistika = true;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Skore: ");
        lcd.print(skore);
        lcd.setCursor(0, 1);
        lcd.print("Cas: ");
        lcd.print((millis() - INTRO) / 60000.0, 1);
        lcd.print(" min");
      }
      // Pokud je statistika zobrazena, smaz ji a pokracuj ve hre
      else {
        statistika = false;
        lcd.clear();
      }
    }
  }

  // Pokud neni zobrazena statistika
  if (!statistika) {
    // Jednou za cas zobraz na nahodne pozici chobotnici
    // Timto casem je pocet milisekund v promenne prodlevaUfona
    if (abs(millis() - casUfona) > prodlevaUfona) {
      ufonX = random(0, 15);
      casUfona = millis();
      // Smaz displej
      lcd.clear();
    }

    // Nakresli raketoplan
    nakresliRaketoplan();
    // Nakresli chobotnici
    nakresliUfona();
  }
}

// Funkce pro nakresleni raketoplanu
void nakresliRaketoplan() {
  lcd.setCursor(raketoplanX, 1);
  lcd.write(uint8_t(0));
}

// Funkce pro nakresleni vystrelu
void nakresliVystrel() {
  lcd.setCursor(raketoplanX, 0);
  lcd.write(uint8_t(1));
}

// Funcke pro nakresleni chobotnice
void nakresliUfona() {
  lcd.setCursor(ufonX, 0);
  lcd.write(uint8_t(3));
}

// Funkce pro nakresleni vybuchu
// Cely displej zaplni znaky vybuchu
// Po 500 ms se smazou a zobrazi aktualni skore
// Po 1 sekunde zmizi a hra pokracuje
void nakresliZasah() {
  for (uint8_t x = 0; x < 16; x++) {
    for (uint8_t y = 0; y < 2; y++) {
      lcd.setCursor(x, y);
      lcd.write(uint8_t(2));
    }
  }
  delay(500);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Skvely zasah");
  lcd.setCursor(0, 1);
  lcd.print("Skore: ");
  lcd.print(skore);
  delay(1000);
  lcd.clear();
}

// Funkce pro zjisteni stisku tlacitka
// Vsechna tlacitka jsou pripojena jako delic napeti na analogovy pin A0
// Kazde tlacitko vytvori na pinu A0 ruzne napeti
// Podle celociselnych hodnot z A/D prevodniku zjistim, ktere je ktere
// Hodnoty si clovek musi zjistit sam treba vypisovanim do seriove linky
uint8_t jakeTlacitko() {
  uint8_t tlacitko = ZADNE;
  uint16_t adc = analogRead(A0);
  if (adc < 50) tlacitko = DOPRAVA;
  else if (adc > 50 && adc < 150) tlacitko = NAHORU;
  else if (adc > 200 && adc < 350) tlacitko = DOLU;
  else if (adc > 400 && adc < 600) tlacitko = DOLEVA;

  // Funkce nevrati hodnotu, dokud stisk tlacitka neuvolnim
  // Pokud ani jedno tacitko neni stisknute, na pinu A0 je cca 5V (10bit A/D prevodnik vraci cislo cca 1 023)
  while (adc < 1000) {
    adc = analogRead(A0);
  }
  return tlacitko;
}
Diskuze (18) Další článek: Máme Android Q, podívejte se na největší novinky

Témata článku: Pojďme programovat elektroniku, Programování, Wi-Fi, Hry, Xbox, Arduino, Stavebnice, C++, Pixel, Bastlení, Modulární PC, ASCII, Void, Kalifornie, Chobotnice, LAN, ADC, Nintendo Switch, Důl, Kazde, Tlačítko, Šumava, RST, #define, Kvilda


Určitě si přečtěte

Kde se bere elektřina v zásuvce? Poznejte 10 tajemství venkovních stožárů s dráty

Kde se bere elektřina v zásuvce? Poznejte 10 tajemství venkovních stožárů s dráty

Elektřina se vyrábí v elektrárnách, ale do zásuvek v našich domovech to pak má ještě hodně daleko. Dnes se na tuhle dlouhou cestu podíváme.

David Polesný | 91

Byli tam! Důkazy o přistání na Měsíci, Lunochody i čínská sonda jsou vidět z vesmíru

Byli tam! Důkazy o přistání na Měsíci, Lunochody i čínská sonda jsou vidět z vesmíru

** Sonda LRO pořídila z oběžné dráhy Měsíce zajímavé snímky ** Jsou na nich vidět artefakty všech misí programu Apolla, které přistály na povrchu Měsíce ** Jde například o části lunárních modulů, rovery a dokonce i vlajky

Petr Kubala | 80

Prohnali jsme prohlížeče benchmarky. Nový Edge je opravdu Chrome a je rychlý!

Prohnali jsme prohlížeče benchmarky. Nový Edge je opravdu Chrome a je rychlý!

** Prohnali jsme nový Edge webovými benchmarky ** Potvrzují, že je to Chrome ** V animacích a vektorech je možná nejlepší

Jakub Čížek | 44

Evoluce stále pokračuje, lidem se do kolen vrací kost navíc

Evoluce stále pokračuje, lidem se do kolen vrací kost navíc

** O kost zvanou fabella nás evoluce připravila už před miliony let ** V posledních desetiletích se však „ztracená kost“ znovu objevuje ** Anatomové nabádají ortopedy, aby brali fabellu v potaz

Jaroslav Petr | 31

Dubnové aktualizace Windows se nepovedly. Způsobují zamrzání systému

Dubnové aktualizace Windows se nepovedly. Způsobují zamrzání systému

** V úterý začal Microsoft uvolňovat dubnové kumulativní aktualizace ** Netrvalo dlouho a uživatelé začali hlásit první potíže ** Nejčastěji jde o zpomalení a zamrzání systému

Karel Kilián | 135

K čemu je dobré programování a jak si postavit vlastní chytrou domácnost

K čemu je dobré programování a jak si postavit vlastní chytrou domácnost

** Proč umět alespoň trošku programovat, i když vás IT nebude živit? ** Potřebujete k programování čipů diplom z MIT? ** Jak může vypadat chytrá domácnost, kterou si postavíte sami?

Jakub Čížek | 27



Aktuální číslo časopisu Computer

Velký test androidů do 6 500 Kč

Tipy na starší foťáky za super cenu

Důkladný test sportovních kamer

Dárek pro každého: první vydání Computeru