Простейшая игра на Ardruino с дисплеем 1602 — Часть #1

в 17:11, , рубрики: diy или сделай сам, game development, Программирование, Разработка под Arduino, Электроника для начинающих
Простейшая игра на Ardruino с дисплеем 1602 — Часть #1 - 1

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

ЧАСТЬ #1 основы

Я заказал себе 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(); // выполняем само действие, а именно очистку экрана
     }
}
"--------------------------------------------------------------------------"

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

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

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

Простейшая игра на Ardruino с дисплеем 1602 — Часть #1 - 4

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

01110
01110
00100
01110
10101
00100
01110
01010

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

Простейшая игра на Ardruino с дисплеем 1602 — Часть #1 - 5

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

00000
01110
01110
00100
11111
00100
01110
01010

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

"--------------------------------------------------------------------------"
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
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й строке и качается, так сказать.

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

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

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

Автор: Сергей Еременко

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js