[Из песочницы] Простейшая игра на Ardruino с дисплеем 1602 — Часть #1
Вот что у нас должно получиться, ну он еще умеет прыгать, ходить и бить злые кактусы, которые на него нападают, но к этому придем поэтапно :)
Я заказал себе arduino, «так себе игрушка» подумал я, комплект маленький (для пробы) о чем в последствии пожалел. Хотелось раскрыть потенциал, но из-за отсутствия дополнительных модулей этого не выходило, пришлось экспериментировать, подрубал ardruino к системе безопасности и смотрел как датчики делают свою работу, потом решил сделать звуковую сигнализацию (используя пративную пищалку из комплекта), так сказать отучивал собак громко или неожиданно лаять :) и тут мои руки дошли до дисплея 1602. «Хм… это же настоящий дисплей» подумал я, но тут же разочаровался узнав что он сжирает почти половину всех контактов на самой ardruino. Покопавшись я нашел странную плату в комплекте «i2C» и очень подозрительно было ТО! Что количество дырдочек совпало с количеством пимпочек на дисплее. «Хм, не с проста…» подумал я, и решил их спаять. Чуть позже я понял что сделал верную штуку и теперь мой дисплей съедает всего два канала. Начал изучать, что же это за дисплей и что он умеет. Изучив достаточное количество материала я узнал, что дисплей чисто текстовый, но о чудо! Он может обработать 8 новых символов, габаритами 5×8 пикселей. Ну что же, давайте начнем писать игру! Первое, это надо придумать что за игра будет, решил сделать подобие динозаврика гуугл хром, но с парочкой фишек так сказать, для начала я думаю сойдет :), но ведь надо еще чем-то управлять, причем многокнопочным, долго думать не пришлось. Взял ИК пульт из комплекта.
«Вот и джойстик» подозрительно пробормотал я себе под нос, думая о задержке от пульта, четкости работы ИК датчика да и вообще об адекватности данной идеи, но делать было нечего, я мог бы обучить 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).
Так как я еще мало представлял как это будет вообще работать, я начал эксперименты. Сначала делал код скетча шаговый, через (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(); // выполняем само действие, а именно очистку экрана
}
}
"--------------------------------------------------------------------------"
Не трудно, правда?
После переписывания всего кода на новый лад, игра стала работать быстрео и четко, симулировать мультизадачные действия :) Я что-то далеко зашел. Ведь нам надо еще сделать персонажа, подобие интерфейса и анимации. Так как мы можем создавать всего восемь новых символов, нам надо как-то это все промутить по умному. На дисплее много объектов делать я пока что не планирую, следовательно, можно сделать так что бы у меня было как раз восемь активных объектов на экране за одну обработку кода. Что же это будет? Ну естественно главный герой, удар, злодей, сердечко и индикатор здоровья. Для начала этого с головой хватит. Да и у меня еще три уникальных объекта в запасе.
Главный герой будет у нас выглядеть так:
Процесс вписывания нового символа, я произвожу двоичным кодом (мне так удобно)
выглядеть он будет так:
01110
01110
00100
01110
10101
00100
01110
01010
Если всмотреться, то из единичек, мы увидим нашего персонажа, но что бы он без дела не стоял, давайте сделаем ему анимацию.
Теперь к нашему коду, добавиться еще один набор двоичных цифорок, а именно такой:
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й строке и качается, так сказать.
Вывод: сегодня я рассказал как узнать данные через ИК порт, как обойти задержку работы кода микроконтроллера и как сделать начальную анимацию.
Остальное скоро :) писать еще очень много чего, так что гляну как это вообще будет вам интересно и если да, то завтра же приступлю к написанию продолжения.
Всем спасибо за внимание, чао-какао!