[Из песочницы] Простейшая игра на Ardruino с дисплеем 1602 — Часть #1

adt5fhpeoqsk4ulxwnrx-lihi60.gif


Вот что у нас должно получиться, ну он еще умеет прыгать, ходить и бить злые кактусы, которые на него нападают, но к этому придем поэтапно :)
Я заказал себе arduino, «так себе игрушка» подумал я, комплект маленький (для пробы) о чем в последствии пожалел. Хотелось раскрыть потенциал, но из-за отсутствия дополнительных модулей этого не выходило, пришлось экспериментировать, подрубал ardruino к системе безопасности и смотрел как датчики делают свою работу, потом решил сделать звуковую сигнализацию (используя пративную пищалку из комплекта), так сказать отучивал собак громко или неожиданно лаять :) и тут мои руки дошли до дисплея 1602. «Хм… это же настоящий дисплей» подумал я, но тут же разочаровался узнав что он сжирает почти половину всех контактов на самой ardruino. Покопавшись я нашел странную плату в комплекте «i2C» и очень подозрительно было ТО! Что количество дырдочек совпало с количеством пимпочек на дисплее. «Хм, не с проста…» подумал я, и решил их спаять. Чуть позже я понял что сделал верную штуку и теперь мой дисплей съедает всего два канала. Начал изучать, что же это за дисплей и что он умеет. Изучив достаточное количество материала я узнал, что дисплей чисто текстовый, но о чудо! Он может обработать 8 новых символов, габаритами 5×8 пикселей. Ну что же, давайте начнем писать игру! Первое, это надо придумать что за игра будет, решил сделать подобие динозаврика гуугл хром, но с парочкой фишек так сказать, для начала я думаю сойдет :), но ведь надо еще чем-то управлять, причем многокнопочным, долго думать не пришлось. Взял ИК пульт из комплекта.

image

«Вот и джойстик» подозрительно пробормотал я себе под нос, думая о задержке от пульта, четкости работы ИК датчика да и вообще об адекватности данной идеи, но делать было нечего, я мог бы обучить ardruino работать с клавиатурой для компа, но было действительно лень это делать. Так что приступил я записывать коды пульта, что бы в дальнейшем с ними работать. Код для микроконтролера тут простейший:

"--------------------------------------------------------------------------"
// качаем библиотеку IRremote
#include
IRrecv irrecv (A0) // включаем аналоговый порт для датчика
Void setup ()
{
     Serial.begin(9600); // настраиваем скорость com порта
     Irrecv.enableIRIn(); // запускаем сам сенсор
}
Void loop ()
{
     If (irrecv.decode( &result )) // если датчик видит любой ИК сигнал, то условие выполнено
     {
          Serial.printIn (result.value, HEX); //считываем код с пульта и выводим его в логи порта
     }
}
"--------------------------------------------------------------------------"


После заливки сего в ardruino и подключив его как надо, мы можем начать записывать с лога порта цифорки, после нажатия на кнопки ИК устройства. Но тут как раз я хочу вам уточнить про то, как надо подключать датчик ИК.

Если мы смотрим на датчик, мы видим три ножки, левая (аналоговый сигнал), средняя (масса), правая (плюс 5V).

image

Так как я еще мало представлял как это будет вообще работать, я начал эксперименты. Сначала делал код скетча шаговый, через (delay (time)) сначала я не подозревал что это плохая идея :)
В чем главный косяк. Данный микроконтроллер не умеет делать мультизадачность. Он считает код сверху вниз, проходя по всем веткам и функциям и после завершения, он начинает заново. И вот, когда у нас этих «delay» в коде становиться очень много, мы начинаем замечать явные задержки. Кстати да, зачем нам много «delay» вообще нужно. Когда мы делаем игру, у нас начинает расти количество проверок и взаимодействий. Например к нам движется враг, а я хочу его перепрыгнуть, я нажимаю «прыжок», а по плану, я должен зависнуть в воздухе к примеру на 0.8f секунд в воздухе, вот и вся игра и зависает на эти 0.8f секунды. «Косяк» подумал я и начал думать над решением. Решение было найдено быстро. Сам микроконтроллер умеет достаточно быстро читать код от начала до конца, (если ему не мешать) и еще он умеет считать время с начала его включения. Вот это то нам и надо. Теперь мы всего лишь создаем переменные которые будут хранить время на то или иное действие и переменную которая сверяет разницу от того сколько сейчас время и во сколько надо активировать код. Ardruino за секунду, берет 1000 миллисекунд, достаточно удобно. Вот фрагмент когда что бы стало понятнее:

"--------------------------------------------------------------------------"
// данный пример фрагмента кода, очищает экран, грубо говоря это наша частота обновления кадров
// переменные
long ClearTime = 150; // 150 = 0.15f секунды или ~6 кадров в секунду
long ClearTimeCheck = 0; // проверка, будет меняться в процессе работы кода
long currentMillis = 0; // переменная таймера

void loop ()
{
     currentMillis = millis(); // переменная таймера = время в миллисекундах
}
void clearscreen () //функция обновления экрана
{ //
     if (currentMillis - ClearTimeCheck >= ClearTime) // если (время работы - проверка больше или равно 0.15f то условие выполнено
     { 
         ClearTimeCheck = currentMillis; // выравниваем проверку для обнуления нашего счетчика
         lcd.clear(); // выполняем само действие, а именно очистку экрана
     }
}
"--------------------------------------------------------------------------"


Не трудно, правда?

После переписывания всего кода на новый лад, игра стала работать быстрео и четко, симулировать мультизадачные действия :) Я что-то далеко зашел. Ведь нам надо еще сделать персонажа, подобие интерфейса и анимации. Так как мы можем создавать всего восемь новых символов, нам надо как-то это все промутить по умному. На дисплее много объектов делать я пока что не планирую, следовательно, можно сделать так что бы у меня было как раз восемь активных объектов на экране за одну обработку кода. Что же это будет? Ну естественно главный герой, удар, злодей, сердечко и индикатор здоровья. Для начала этого с головой хватит. Да и у меня еще три уникальных объекта в запасе.

Главный герой будет у нас выглядеть так:

earkhaarqwcke8t6qmgvuhtxhhw.jpeg

Процесс вписывания нового символа, я произвожу двоичным кодом (мне так удобно)
выглядеть он будет так:

01110
01110
00100
01110
10101
00100
01110
01010

Если всмотреться, то из единичек, мы увидим нашего персонажа, но что бы он без дела не стоял, давайте сделаем ему анимацию.

lmzys-m1civhaydvisjrsomhtiy.jpeg

Теперь к нашему коду, добавиться еще один набор двоичных цифорок, а именно такой:

00000
01110
01110
00100
11111
00100
01110
01010

Как сделать анимацию на этом дисплее, логику я указал выше, а теперь перейдем к практике, в данный момент, расположим его на центр экрана, и заставим просто стоять на месте, и помните, наша задача использовать только одну ячейку памяти на два спрайта анимации. Это легче чем кажется:

"--------------------------------------------------------------------------"
#include  
#include 
LiquidCrystal_I2C lcd(0x3F,16,2);  // Устанавливаем дисплей

long AnimatedTime = 300; // скорость анимации главного герое
long AnimatedTimeCheck = 0; // проверка скорости (как и в прошлом примере)
int AnimPlayer = 1; // проверка состояния анимации 
int GGpozX = 8; // положение горизонталь
int GGpozY = 1; // положение вертикаль 1 это 2я строка а 0 это первая строка
long currentMillis = 0; // переменная таймера

//Создаем переменные наших объектов, их может быть сколько угодно, они же переменные :)
enum { SYMBOL_HEIGHT = 8 };
byte Player_1[SYMBOL_HEIGHT] = {B01110,B01110,B00100,B01110,B10101,B00100,B01110,B01010,}; // спрайт 1
byte Player_2[SYMBOL_HEIGHT] = {B00000,B01110,B01110,B00100,B11111,B00100,B01110,B01010,}; // спрайт 2

void setup() 
{
  lcd.init();                     
  lcd.backlight();// Включаем подсветку дисплея
  loop();
  PlAn();
}

void loop() 
{
     if (AnimPlayer != 50)
     { // это проверка смерти персонажа, пока что не забивайте себе голову :)
     // --------------------------- Animated ->
     // -------------------- Player ->
     if (AnimPlayer == 1){lcd.createChar(0, Player_1);} //если состояние 1 то спрайт 1
     //(lcd.createChar(номер ячейки памяти от 0 до 7, название переменной спрайта))
     if (AnimPlayer == 2){lcd.createChar(0, Player_2);} //если состояние 2 то спрайт 2
     }
     // --------------------------- <- Animated
     currentMillis = millis(); // переменная таймера = время в миллисекундах
     PlAn();
}
void PlAn ()
{
     if (AnimPlayer == 1) // если состояние 1 то
     {
          lcd.setCursor(GGpozX, GGpozY); // ставим "курсор" на точку координат нашего героя
          lcd.write(0); // рисуем спрайт из ячейки памяти на то место где "курсор"
     }
     if (AnimPlayer == 2) // аналогично №1
     {
          lcd.setCursor(GGpozX, GGpozY);
          lcd.write(0);
     }
     if (currentMillis - AnimatedTimeCheck >= AnimatedTime) // проверка времени как и до этого
     {
          AnimatedTimeCheck = currentMillis; // ну тут уже понятно
          if (AnimPlayer == 2){AnimPlayer = 1; return;} //если положение 2 то делаем 1 и стопорим этот фрагмент кода
          if (AnimPlayer == 1){AnimPlayer = 2;} //если 1 то 2 и стопорить нет смысла так что не забиваем память лишним кодом, ее у нас там и так очень мало
     }
}
"--------------------------------------------------------------------------"


После запуска, мы видим чУловечка, который находиться в центре экрана, на 2й строке и качается, так сказать.

Вывод: сегодня я рассказал как узнать данные через ИК порт, как обойти задержку работы кода микроконтроллера и как сделать начальную анимацию.

Остальное скоро :) писать еще очень много чего, так что гляну как это вообще будет вам интересно и если да, то завтра же приступлю к написанию продолжения.

Всем спасибо за внимание, чао-какао!

© Habrahabr.ru