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

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


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

image

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

"--------------------------------------------------------------------------"
// качаем библиотеку IRremote
#include <IRremote.h>
decode_results results;
IRrecv irrecv (A0); // включаем аналоговый порт для датчика
void setup ()
{
     Serial.begin(9600); // настраиваем скорость com порта
     irrecv.enableIRIn(); // запускаем сам сенсор
}
void loop ()
{
     if (irrecv.decode( &results )) // если датчик видит любой ИК сигнал, то условие выполнено
     {
          Serial.println( results.value, HEX ); //считываем код с пульта и выводим его в логи порта
          irrecv.resume(); //обнуляем данные приема что бы наш кон не вошел в постоянный цикл
     }
}
"--------------------------------------------------------------------------"

Выглядит код сигнала пульта так: «FF18E7» у вас коды будут конечно другие, но суть вы должны понять, а когда будете делать к этому коду обращение, в начале дописываем «0х» и у нас получится (0хFF18E7).

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

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

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

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



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

01110
01110
00100
01110
10101
00100
01110
01010

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



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

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й строке и качается, так сказать.

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

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

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

Комментарии (16)


  1. napa3um
    29.09.2018 20:24
    +1

    Как раз сегодня с почты забрал Arduboy, тоже буду заниматься программированием 8-битного контроллера с монохромным экраном, но без необходимости пайки :).


  1. AntonSazonov
    30.09.2018 00:48

    У вас в коде микроконтоллера (первый код) пустой #include.
    Проверяйте всё перед публикацией.


    1. Asmodroid Автор
      30.09.2018 03:04

      ох, точно, уже все исправил ;)


  1. pansom
    30.09.2018 02:39

    Я аналог «Волка и яиц» кодил.


  1. SadAngel
    30.09.2018 08:12
    +1

    "Данный микроконтроллер не умеет делать мультизадачность. Он считает код сверху вниз, проходя по всем веткам и функциям и после завершения, он начинает заново. " все MCU так работают, мультизадачностю занимаеться софт.как напишете так и работает. Но arduino ограничила Вас рамками. Используйте interrupt для упрощения кода. Можете познакомииься с FreeRTOS — там будет мультизадачность. Arduino отлично подходить для прошивки MCU без программатора. (Arduino- нет нормальной IDE, нет Debug и другие "-")


    1. Asmodroid Автор
      30.09.2018 16:24

      Так я сделал ардруино мультизадачным :) просто если делать код в стандартом виде как это предлагают все примеры библиотек, то ничего из этого не выйдет, да, если вы делаете последовательные действия, или оду задачу, то все хорошо, но если вы например хотите сделать что-то глобальное, стандартный метод уже не катит и надо делать по другому, я нашел выход, делать все через временной промежуток. Собственно это я и описал тут, и во второй статье более раскрою этот метод.


  1. Bismuth208
    30.09.2018 09:59

    Добро пожаловать!
    Статься у Вас маленькая вышла, побольше б.
    Многозадачность легко сделать на базе стэйт машины.
    Интересное совпадение, как раз хотел выложить сегодня-завтра еще одну статью тоже по игре под AVR над которой занимаюсь иногда последний год.


    1. NordicEnergy
      30.09.2018 10:04

      Для такой задачи объем как раз отличный. Ардуино и авр в 2018-м шлак унылый, но в таком достаточно компактном виде даже у меня раздражения не вызвало. Тем более это только «Часть 1»))


      1. Bismuth208
        30.09.2018 10:23
        +1

        Шлак или нет, тема очень холиварная и лучше её не трогать.
        Просто воспринимайте это как демосцену под старое железо.
        AVR умрет очень не скоро.


        1. NordicEnergy
          30.09.2018 10:27
          +1

          Я уточнил, что это сугубо мое мнение, навязывать не пытаюсь. Тем более если статья грамотно составлена и заголовок соответствует тексту, то это однозначно плюс статье и в карму от меня)
          AVR не умрет пока есть казуальщики (не оскорбление, просто факт), это понятно. Полностью не умерли даже вещи куда старше всяких ATmega8.


          1. Temtaime
            30.09.2018 14:21

            Это вполне хороший микроконтроллер, под него написано много софта, его используют в промышленных решениях. AVR будет жить ещё очень долго и без «казуальщиков».


            1. NordicEnergy
              30.09.2018 23:13

              Не смешите. Промышленные решения, то есть индастриал — это ядро С28 от TI, например, или C166 от Infineon, или dsPIC от Microchip. AVR, конкретно ATmega разные — это мусор для всяких мелочей типа часы, калькуляторы, всякие реле и прочее. Ну или хотя бы давайте ссылочку на ATmega в исполнение industrial, а не commercial.

              Написано много софта… это треш, который ардуинщики плодят и прочий говнокод? Нормальному разработчику кроме reference manual ничего вообще не надо, особенно дурных примеров «как не надо делать».


    1. Asmodroid Автор
      30.09.2018 16:26

      Здорово! Надо будет почитать :) это моя первая статья, я не знаю какой объем информации нормальный, во второй статье напишу больше инфы.


  1. OnYourLips
    30.09.2018 12:24

    А вы не находили подобных пультов, но работающих не по ИК, а по радио?


    1. Asmodroid Автор
      30.09.2018 16:15

      Не находил, да и у меня радио модуля нету, так что я даже и не искал :)


  1. safari2012
    01.10.2018 17:25

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

    конечно, интересно. хочется поскорей увидеть видос с кактусами :)