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

от автора

ЧАСТЬ #2 от начала до конца

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

Что бы понять, что нам сейчас нужно писать, мы должны составить план, что же мы будем иметь и в каком виде. У нас есть главный герой, у него два кадра анимации и это мы уже в прошлом уроке уже разобрали. Делать ему изменение спрайта прыжка я не стал, не потому что мне лень, просто не вижу пока что в этом смысла. Далее у нас должно быть обозначение жизни, делать просто сердечки в ряд не интересно, и цифры тоже банально, давайте сделаем три очка жизни и обозначим их, например уровнем заряда батарейки. И для радости глаза сердечко, которое будет биться возле этой батарейки. Количество очков за раунд, установим слева экрана, чисто в цифровом виде и враг у нас будет злой кактус, для начала пойдет.

С объектами мы определились и давайте их отрисуем, сразу все спрайты и запишем их.

Рабочие спрайты у нас есть и мы уже можем представить как будет выглядеть это на экране, следуя первому уроку мы выпишем их в двоичной системе, помним, где ноль там пусто, а где единица, у нас горит пиксель. Приступим:

"--------------------------------------------------------------------------"  //Главный герой первый спрайт: byte Player_1[SYMBOL_HEIGHT] = {B01110,B01110,B00100,B01110,B10101,B00100,B01110,B01010,};  //Главный герой второй спрайт: byte Player_2[SYMBOL_HEIGHT] = {B00000,B01110,B01110,B00100,B11111,B00100,B01110,B01010,};  //Злой кактус:  byte Enemy_1[SYMBOL_HEIGHT]  = {B00010,B00110,B10111,B10110,B11111,B00110,B00110,B11111,};  //Сердечко большое: byte Heart_L[SYMBOL_HEIGHT]  = {B00000,B01010,B11111,B11111,B11111,B01110,B00100,B00000,};  //Сердечко маленькое: byte Heart_R[SYMBOL_HEIGHT]  = {B00000,B00000,B01010,B11111,B01110,B00100,B00000,B00000,};  //Батарейка три жизни: byte Battery1[SYMBOL_HEIGHT] = {B01110,B11111,B11111,B11111,B11111,B11111,B11111,B11111,};  //Батарейка две жизни: byte Battery2[SYMBOL_HEIGHT] = {B01110,B10001,B10011,B10111,B11111,B11111,B11111,B11111,};  //Батарейка одна жизнь: byte Battery3[SYMBOL_HEIGHT] = {B01110,B10001,B10001,B10001,B10011,B10111,B11111,B11111,};  //Батарейка ноль жизней: byte Battery4[SYMBOL_HEIGHT] = {B01110,B10001,B10001,B10001,B10001,B10001,B10001,B11111,}; "--------------------------------------------------------------------------" 

Теперь у нас есть спрайты, и пришло время это все оживлять. Для начала подумаем какие дополнительные функции нам нужны. Зная, что ardruino работает не идеально и местами даже очень капризно, мы начинаем стараться максимально упростить этому микроконтроллеру жизнь. Не перегружать и одновременно требовать полной отдачи. Так что выведем такие дополнительные функции, которые будут жить своей жизнью и делать все что нам надо:
— анимация сердца
— анимация героя
— проверка урона по герою
— движение злого кактуса
— начисление очков (пока что ежесекундно +1, дальше поменяем)
— обновление экрана (но это не точно, скорее всего, мы уберем эту функцию и добавим другую, мне не понравилось, что экран моргает, хочется стабильности). В последствии заменим эту функцию на удаление прошлого местоположения героя, это уберет пративное мерцание экрана, а обнуления злодея, будет внутри злого скрипта, думаю там одна или две строчки максимум будет.
— управление пультом
— настройки loop и setup

Мы хотим, что бы у нас была анимация биения сердца. Выводя ее в отдельную функцию и заставив жить своей отдельной жизнью нам проще будет отслеживать работу и в дальнейшем проще будет редактировать, так как у нас все, ну или почти все находиться в одном месте. Этот код, можно было бы вывести в loop () и закомментировать, но я, лично, привык разбивать на отдельные функции, ты не ищешь код во всем списке и знаешь, что отдельная функция, контролирует отдельные элементы нашей игры.
Сейчас, я буду писать отрезки кода, мы их соединим в конце и получиться полноценный скрипт, я вам сейчас объясню суть да идею, а потом соберем пазл и будем наслаждаться результатом.
Перед началом самого кода, объясню парочку моментов.
lcd.createChar // это команда обращения к экрану и задействования одной из ячеек памяти для записи новых символов. В скобочках записывается номер ячейки и через запятую, название переменной с информацией.
Контроль за отрисовкой будем вести через цифровую переменную, используя четыре цифры, для правильности анимации, если бы мы хотели сделать так что бы сердце просто билось туда-сюда то нам и подошла бы обычная переменная bool. Но у меня мысль другая, один большой удар и два коротких, так будет смотреться интереснее.

"--------------------------------------------------------------------------" void HeartHit () // функция анимации сердечка {   if (HeartControl == 0 || HeartControl == 2){lcd.createChar(1, Heart_L);}  //если наша переменная покажет ноль или два, мы в ячейку один записываем большое сердце   if (HeartControl == 1 || HeartControl == 3){lcd.createChar(1, Heart_R);} //если наша переменная покажет один или два, мы в ячейку один записываем малое сердце   if (currentMillis - HeartHitBigCheck >= HeartHitBig) {  // время большого зависания удара     if (currentMillis - HeartHitLightCheck >= HeartHitLight) { // время коротких ударов       HeartHitLightCheck = currentMillis; // обнуление контроля времени коротких ударов       if (HeartControl<3){HeartControl++;}  // если переменная контроля отрисовки менее трех, то при каждом срабатывании скрипта мы плюсуем один к сумме       else {HeartControl = 0; HeartHitBigCheck = currentMillis;} //но если сумма переменной превысила три, то обнуляем ее и обнуляем счетчик длительного залипания сердца     }    } } "--------------------------------------------------------------------------" 

Еще раз, хочу сконцентрировать ваше внимание на этом коде:
lcd.createChar(x, y); присваивание в ячейку пямяти « x » от (0…7) данные для отображения на экране « y »

Идем дальше =)

Теперь, у нас есть код, который создает эффект интересного биения сердечка, ничего полезного оно не делает, только понты =)

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

"--------------------------------------------------------------------------" // я переписал этот кусок кода и он будет отличаться от первой части статьи(чуток). Это для меня нормально =) На работоспособность, это никак не влияет но лучше использовать новый вариант. void PlAn () // функция анимации главного героя {   If (JumpB == true && GGpozY == 0){ // это контроль прыжка (активация прыжка будет в другой части кода) Если прыжок = правда И позиция нахождения наверху.     if (currentMillis - JumpUPCheck >= JumpUP) { // проверка времени в полете 0.8f       JumpB = false; GGclear (); GGpozY = 1; // когда время выходит, возвращаем нашего героя на землю и ждем следующего прыжка. Прыжок = лож; активация функции очищения местонахождения героя (); позиция вертикали главного героя = нижняя полоса;     }   }   if (AnimPlayer == 1){lcd.createChar(0, Player_1);} //Если переменная контроля анимации один, записываем в ячейку памяти первый спрайт героя   if (AnimPlayer == 2){lcd.createChar(0, Player_2);} //Если переменная контроля анимации два, записываем в ячейку памяти второй спрайт героя   if (AnimPlayer < 2) // если переменная до двух, выставляем курсор на то место, где находиться наш главный герой и рисуем из памяти спрайт героя   {     lcd.setCursor(GGpozX, GGpozY);  // выставили на позицию     lcd.write(0); // отрисовали   }   if (currentMillis - AnimatedTimeCheck >= AnimatedTime) { // проверка времени     AnimatedTimeCheck = currentMillis; // обнуление времен     if (AnimPlayer == 2){AnimPlayer = 1;} // если переменная контроля анимации два то один     else{AnimPlayer = 2;} // а если один, то два.   } } void GGclear () // функция обновления героя {   lcd.setCursor(GGpozX, GGpozY);  // позиция героя   lcd.print(" "); //очищаем его } "--------------------------------------------------------------------------" 

Теперь таймер, эм, точнее очки наши, которые будут начисляться, давайте просто напишем таймер и будем считать что это очки.

"--------------------------------------------------------------------------" void timer () // функция очков {   if (currentMillis - DHTTimeRCheck >= DHTTimeR)  // таймер который срабатывает раз в секунду   {     DHTTimeRCheck = currentMillis; // обнуление таймера     Timer_z ++; // плюсуем единицу к общей сумме     lcd.setCursor(0, 0);  // выставляем курсор на первую верхнюю точку     lcd.print(Timer_z); // выводим данные на экран   } } --------------------------------------------------------------------------" 

Вот и все. Чем дальше тем легче.

Теперь, осталось отработать нашего кактуса.
Его задача простая, появиться, прошагать весь путь справа на левое и попытаться соприкоснутся с героем, что бы нанести урон. С уроном все проще, one touch – one hit. Пока что увеличения сложности, делать мы не будем. Пусть кактус двигается со скоростью 0.5f (сложность это уже будет ваше домашнее задание =)) ну или русским языком пол секунды – один шаг.
Давайте глянем, как будет смотреться, этот кусок кода:

"--------------------------------------------------------------------------" void enemy_go () // функция злодея {   if (Emeny_check_1 == 0)  // что бы враг появлялся с случайно задержкой, нам надо сделать так что бы пока не было злодея на экране, у нас срабатывал рандом и при правильном числе, призывался злодей а этот рандом ждал пока он снова не понадобиться       {         Emeny_control = random (100); // я подумал, если мы задействуем разные методы, то это будет интереснее, по этому, высчитать шанс появления злодея, можно будет рандомом.         if (Emeny_control == 1) {  // если рандом = 1 из 100 то запускаем злодея. Emeny_check_1  =  1; // запускаем персонажа, по сути, тут можно использовать bool так как у нас два состояния, или да или нет, а мне сейчас переделывать было лень  hitON = false; // эта функция проверяет был ли нанесен урон главному герою       }   }   if (Emeny_check_1 == 1)  // когда рандом одобрен, злодей пошел в бой   {     if (currentMillis - TimeBlinkCheck >= TimeBlink) //проверка временем 0.5f     {       TimeBlinkCheck = currentMillis; //обнуление проверки время       lcd.createChar(2, Enemy_1);  //присваиваем ячейке памяти 2 спрайт кактуса       lcd.setCursor(E1pozX, E1pozY); //выбрали 1ю точку кактуса       lcd.print(" "); //обнуляем ее       E1pozX--; //перемещаем влево на один шаг       lcd.setCursor(E1pozX, E1pozY);  //выставляем 2ю позицию       lcd.write(2); //отрисовываем кактус       if (E1pozX <= 0) //если кактус дошел до края экрана       {         lcd.setCursor(0,1); //выставляем курсор на край экрана         lcd.print(" "); //обнуляем его         Emeny_control = 0; //обнуляем функцию рандома         Emeny_check_1 = 0; //закрываем доступ к этой части скрипта         E1pozX = 16; // - \/ \/ \/         E1pozY = 1; // - выставляем катус обратно на позицию Х и У       }     }   } } "--------------------------------------------------------------------------" 

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

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

"--------------------------------------------------------------------------" void check_hit_gg_1 () //функция получения урона {   if (E1pozX == GGpozX && E1pozY == GGpozY && hitON == false){  //проверка что координаты Х и Y совпали, что у героя, что у кактуса     LifeCheck -= 1; // минусуем од ХП     hitON = true; // возвращаем проверку и теперь наш герой снова может получать урон     if (LifeCheck <= 0){ // если жизни меньше чем ноль       AnimPlayer = 50; //отключаем срабатывание loop ()       Emeny_check_1 = 50; // отключаем случайное срабатывание кактуса       lcd.clear(); //чистим экран       lcd.setCursor(3, 0); //устанавливаем курсор       lcd.print("GAME OVER"); // пишем заветное слово     }   } else { // НО! Если герой и кактус не сошлись в соитие то…   lcd.setCursor(13, 0); // устанавливаем курсор и …   lcd.write(1);  // отображение сердечка   lcd.setCursor(14, 0);   lcd.print("=");  // это просто равно   lcd.setCursor(15, 0);   lcd.write(3);  // отображение батарейки   } } "--------------------------------------------------------------------------" 

Этот код очень простой, основная его функция, это проверять, и ждать пока условия все будут выполнены, что бы остановить игру и сообщить нам, что мы проиграли.

Теперь последняя функция, это управление. По сути, если мы разобрали код выше, то для нас он покажется проще простого. С первого урока, мы вытянули коды пульта, у меня выписаны они все, выглядят они вот так:

* CH- 0xFFA25D
* CH 0xFF629D
* CH+ 0xFFE21D
* << 0xFF22DD
* >> 0xFF02FD
* >|| 0xFFC23D
* — 0xFFE01F
* + 0xFFA857
* EQ 0xFF906F
* 0 0xFF6897
* 100+ 0xFF9867
* 200+ 0xFFB04F
* 1 0xFF30CF
* 2 0xFF18E7
* 3 0xFF7A85
* 4 0xFF10EF
* 5 0xFF38C7
* 6 0xFF5AA5
* 7 0xFF42BD
* 8 0xFF4AB5
* 9 0xFF52AD

Кнопка _ код (внимание!) (коды на пультах могут отличаться)

Кто не читал, советую прочитать 1ю часть.

Что-то подобное, потом будет и у вас, и вы сможете без проблем настраивать все, что вам нужно.
Теперь мы создадим простейший алгоритм, основываясь на том, что уже знаем и наша игра, оживет.

"--------------------------------------------------------------------------" void IRCheck () // функция ИК порта {   if ( irrecv.decode( &results )) // если данные пришли, то   {        if (results.value == 0xFF18E7 && GGpozY == 1){ // если нажали кнопку « 2 » и позиция на «Земле»         GGclear (); // очищаем прошлое местонахождение героя         GGpozY = 0; // выставляем позицию на 2ю полосу (типа прыжок)         JumpB = true; // переменная обозначающая что мы в фазе прыжка         JumpUPCheck = currentMillis; // обнуляем счетчик проверки времянахождения в воздухе         } // 2       if (results.value == 0xFF10EF && GGpozX >= 0){  // Если нажали на четверку и вы не упираетесь в левый край экрана         GGclear (); // тоже что и до этого         GGpozX -= 1;  // перемещаем главного героя влево       } // 4       if (results.value == 0xFF5AA5 && GGpozX <= 15){ //если нажали шесть и не упираемся в правую сторону экрана         GGclear (); //должны были уже запомнить         GGpozX += 1; // перемещаем вправо         } // 6       if (results.value == 0xFF6897){ // 0 //если нажать ноль, то игра делает рестарт…         lcd.clear(); // очищаем экран         AnimPlayer = 1; //возвращаем переменную запуска анимации         LifeCheck = 3; // восстанавливаем ХП         Timer_z = 0; // обнуляем счетчик         GGpozX = 8; // \/ \/ \/         GGpozY = 1; // возвращаем главного героя на центр экрана         Emeny_check_1 = 0; // возвращаем данные генератора кактусов         E1pozX = 16; // \/ \/ \/          E1pozY = 1;   // выставляем стартовую позицию кактуса.    }     irrecv.resume(); // обнуляем данные датчика } "--------------------------------------------------------------------------" 

Вывод от выше написанного кода:
кнопка 2 это прыжок
кнопка 4 это шаг влево
кнопка 6 это шаг вправо
кнопка 0 обнуляет игру и перезапускает ее

Вот, теперь осталось нам настроить setup & loop, уже все идет к концу. Мы создали все дополнительные функции и нам осталось только все это склеить и добавить библиотеки. Я думаю, мы рассмотрим переменные и главные настройки setup\loop уже в общем коде, так что приступим, а потом от вас требуется ctrl + c & ctrl + v и все =) и дальнейшее самостоятельное развитие в этом направлении, если вам конечно это все понравилось.

"--------------------------------------------------------------------------"  #include <IRremote.h> // ИК модуль #include <Wire.h>  // i2P шина #include <LiquidCrystal_I2C.h> // дисплей 1602 LiquidCrystal_I2C lcd(0x3F,16,2);  // Устанавливаем дисплей  int AnimPlayer = 1; // анимация героя int GGpozX = 8; // позиция героя ось Х int GGpozY = 1; // позиция героя ось У int Emeny_check_1 = 0;  // проверка злодея int Emeny_control = 0; // рандомим это число что бы были задержки между призывом кактуса int E1pozX = 16; // позиция злодея Х int E1pozY = 1; // позиция злодея У int HeartControl = 0; // контроль анимации сердечка int LifeCheck = 3; // количество жизней  long Timer_z = 0; // счетчик очков long AnimatedTime = 300; // время обновления анимации героя long AnimatedTimeCheck = 0; // проверка вышеуказанного long HeartHitBig = 800; // время задержки сердца большое  long HeartHitLight = 250; // время задержки сердца малое long HeartHitBigCheck = 0; // проверка большого удара сердца long HeartHitLightCheck = 0; // проверка малого удара сердца long BatteyBlink = 200;  // время бликов батарейка когда осталось 1 ХП long BatteyBlinkCheck = 0; // проверка вышеуказанного long JumpUP = 800; // время нахождения в прыжке long JumpUPCheck = 0; // проверка вышеуказанного long DHTTimeR = 1000; //обновление секунда long DHTTimeRCheck = 0; //проверка обновление секунд long TimeBlink = 500; // обновление пол секунды long TimeBlinkCheck = 0; //проверка мигание настроек long currentMillis = 0; // главный счетчик времени  bool JumpB = false; // прыжок или нет bool BatteryB = false; // батарейка пуста или почти bool hitON = false; // удар или нет  decode_results results; // вывод результата данных с ИК IRrecv irrecv(A0); // порт аналоговый под ИК модуль  enum { SYMBOL_HEIGHT = 8 }; byte Player_1[SYMBOL_HEIGHT] = {B01110,B01110,B00100,B01110,B10101,B00100,B01110,B01010,}; byte Player_2[SYMBOL_HEIGHT] = {B00000,B01110,B01110,B00100,B11111,B00100,B01110,B01010,}; byte Enemy_1[SYMBOL_HEIGHT]  = {B00010,B00110,B10111,B10110,B11111,B00110,B00110,B11111,}; byte Heart_L[SYMBOL_HEIGHT]  = {B00000,B01010,B11111,B11111,B11111,B01110,B00100,B00000,}; byte Heart_R[SYMBOL_HEIGHT]  = {B00000,B00000,B01010,B11111,B01110,B00100,B00000,B00000,}; byte Battery1[SYMBOL_HEIGHT] = {B01110,B11111,B11111,B11111,B11111,B11111,B11111,B11111,}; byte Battery2[SYMBOL_HEIGHT] = {B01110,B10001,B10011,B10111,B11111,B11111,B11111,B11111,}; byte Battery3[SYMBOL_HEIGHT] = {B01110,B10001,B10001,B10001,B10011,B10111,B11111,B11111,}; byte Battery4[SYMBOL_HEIGHT] = {B01110,B10001,B10001,B10001,B10001,B10001,B10001,B11111,};  void setup() {   Serial.begin(9600); // скорость порта   irrecv.enableIRIn(); // запускаем прием   lcd.init(); // запуск экрана   Wire.begin();  // запуск ИК   lcd.backlight();// Включаем подсветку дисплея } void loop() {     currentMillis = millis(); // присваиваем миллисекунды    IRCheck (); //запускаем функцию ИК модуля     if (AnimPlayer < 3){ // если герой анимируется, значит он жив, значит работаем дальше     if (LifeCheck == 3) {lcd.createChar(3, Battery1);} //показываем жизнь полная     if (LifeCheck == 2) {lcd.createChar(3, Battery2);} //показываем жизнь средняя     if (LifeCheck == 1) {//начинаем блинькать жизнью, когда 1 хп остался       if (BatteryB == false){lcd.createChar(3, Battery3);} //показываем жизнь почти пусто       if (BatteryB == true){lcd.createChar(3, Battery4);} //показываем жизнь пусто       if (currentMillis - BatteyBlinkCheck >= BatteyBlink) {BatteyBlinkCheck = currentMillis; //проверка времени       if (BatteryB == false) {BatteryB = true;} else {BatteryB = false;}} //меняем положение     }     timer(); //запуск таймера     check_hit_gg_1 (); //запуск поверки урона     PlAn(); //запуск анимации ГГ     HeartHit (); // запуск анимации сердца     enemy_go(); // запуск злодея   } } void IRCheck () // функция ИК порта {   if ( irrecv.decode( &results )) // если данные пришли, то   {        if (results.value == 0xFF18E7 && GGpozY == 1){ // если нажали кнопку « 2 » и позиция на «Земле»         GGclear (); // очищаем прошлое местонахождение героя         GGpozY = 0; // выставляем позицию на 2ю полосу (типа прыжок)         JumpB = true; // переменная обозначающая что мы в фазе прыжка         JumpUPCheck = currentMillis; // обнуляем счетчик проверки времянахождения в воздухе         } // 2       if (results.value == 0xFF10EF && GGpozX >= 0){  // Если нажали на четверку и вы не упираетесь в левый край экрана         GGclear (); // тоже что и до этого         GGpozX -= 1;  // перемещаем главного героя влево       } // 4       if (results.value == 0xFF5AA5 && GGpozX <= 15){ //если нажали шесть и не упираемся в правую сторону экрана         GGclear (); //должны были уже запомнить         GGpozX += 1; // перемещаем вправо         } // 6       if (results.value == 0xFF6897){ // 0 //если нажать ноль, то игра делает рестарт…         lcd.clear(); // очищаем экран         AnimPlayer = 1; //возвращаем переменную запуска анимации         LifeCheck = 3; // восстанавливаем ХП         Timer_z = 0; // обнуляем счетчик         GGpozX = 8; // \/ \/ \/         GGpozY = 1; // возвращаем главного героя на центр экрана         Emeny_check_1 = 0; // возвращаем данные генератора кактусов         E1pozX = 16; // \/ \/ \/          E1pozY = 1;   // выставляем стартовую позицию кактуса.    }     irrecv.resume(); // обнуляем данные датчика  } } void timer () // функция очков {   if (currentMillis - DHTTimeRCheck >= DHTTimeR)  // таймер который срабатывает раз в секунду   {     DHTTimeRCheck = currentMillis; // обнуление таймера     Timer_z ++; // плюсуем единицу к общей сумме     lcd.setCursor(0, 0);  // выставляем курсор на первую верхнюю точку     lcd.print(Timer_z); // выводим данные на экран   } } // я переписал этот кусок кода и он будет отличаться от первой части статьи(чуток). Это для меня нормально =) На работоспособность, это никак не влияет но лучше использовать новый вариант. void PlAn () // функция анимации главного героя {   if (JumpB == true && GGpozY == 0){ // это контроль прыжка (активация прыжка будет в другой части кода) Если прыжок = правда И позиция нахождения наверху.     if (currentMillis - JumpUPCheck >= JumpUP) { // проверка времени в полете 0.8f       JumpB = false; GGclear (); GGpozY = 1; // когда время выходит, возвращаем нашего героя на землю и ждем следующего прыжка. Прыжок = лож; активация функции очищения местонахождения героя (); позиция вертикали главного героя = нижняя полоса;     }   }   if (AnimPlayer == 1){lcd.createChar(0, Player_1);} //Если переменная контроля анимации один, записываем в ячейку памяти первый спрайт героя   if (AnimPlayer == 2){lcd.createChar(0, Player_2);} //Если переменная контроля анимации два, записываем в ячейку памяти второй спрайт героя   if (AnimPlayer < 2) // если переменная до двух, выставляем курсор на то место, где находиться наш главный герой и рисуем из памяти спрайт героя   {     lcd.setCursor(GGpozX, GGpozY);  // выставили на позицию     lcd.write(0); // отрисовали   }   if (currentMillis - AnimatedTimeCheck >= AnimatedTime) { // проверка времени     AnimatedTimeCheck = currentMillis; // обнуление времен     if (AnimPlayer == 2){AnimPlayer = 1;} // если переменная контроля анимации два то один     else{AnimPlayer = 2;} // а если один, то два.   } } void GGclear () // функция обновления героя {   lcd.setCursor(GGpozX, GGpozY);  // позиция героя   lcd.print(" "); //очищаем его } void enemy_go () // функция злодея {   if (Emeny_check_1 == 0)  // что бы враг появлялся с случайно задержкой, нам надо сделать так что бы пока не было злодея на экране, у нас срабатывал рандом и при правильном числе, призывался злодей а этот рандом ждал пока он снова не понадобиться       {         Emeny_control = random (100); // я подумал, если мы задействуем разные методы, то это будет интереснее, по этому, высчитать шанс появления злодея, можно будет рандомом.         if (Emeny_control == 1) {  // если рандом = 1 из 100 то запускаем злодея. Emeny_check_1  =  1; // запускаем персонажа, по сути, тут можно использовать bool так как у нас два состояния, или да или нет, а мне сейчас переделывать было лень  hitON = false; // эта функция проверяет был ли нанесен урон главному герою       }   }   if (Emeny_check_1 == 1)  // когда рандом одобрен, злодей пошел в бой   {     if (currentMillis - TimeBlinkCheck >= TimeBlink) //проверка временем 0.5f     {       TimeBlinkCheck = currentMillis; //обнуление проверки время       lcd.createChar(2, Enemy_1);  //присваиваем ячейке памяти 2 спрайт кактуса       lcd.setCursor(E1pozX, E1pozY); //выбрали 1ю точку кактуса       lcd.print(" "); //обнуляем ее       E1pozX--; //перемещаем влево на один шаг       lcd.setCursor(E1pozX, E1pozY);  //выставляем 2ю позицию       lcd.write(2); //отрисовываем кактус       if (E1pozX <= 0) //если кактус дошел до края экрана       {         lcd.setCursor(0,1); //выставляем курсор на край экрана         lcd.print(" "); //обнуляем его         Emeny_control = 0; //обнуляем функцию рандома         Emeny_check_1 = 0; //закрываем доступ к этой части скрипта         E1pozX = 16; // - \/ \/ \/         E1pozY = 1; // - выставляем катус обратно на позицию Х и У       }     }   } } void check_hit_gg_1 () //функция получения урона {   if (E1pozX == GGpozX && E1pozY == GGpozY && hitON == false){  //проверка что координаты Х и Y совпали, что у героя, что у кактуса     LifeCheck -= 1; // минусуем од ХП     hitON = true; // возвращаем проверку и теперь наш герой снова может получать урон     if (LifeCheck <= 0){ // если жизни меньше чем ноль       AnimPlayer = 50; //отключаем срабатывание loop ()       Emeny_check_1 = 50; // отключаем случайное срабатывание кактуса       lcd.clear(); //чистим экран       lcd.setCursor(3, 0); //устанавливаем курсор       lcd.print("GAME OVER"); // пишем заветное слово     }   } else { // НО! Если герой и кактус не сошлись в соитие то…   lcd.setCursor(13, 0); // устанавливаем курсор и …   lcd.write(1);  // отображение сердечка   lcd.setCursor(14, 0);   lcd.print("=");  // это просто равно   lcd.setCursor(15, 0);   lcd.write(3);  // отображение батарейки   } } void HeartHit () // функция анимации сердечка {   if (HeartControl == 0 || HeartControl == 2){lcd.createChar(1, Heart_L);}  //если наша переменная покажет ноль или два, мы в ячейку один записываем большое сердце   if (HeartControl == 1 || HeartControl == 3){lcd.createChar(1, Heart_R);} //если наша переменная покажет один или два, мы в ячейку один записываем малое сердце   if (currentMillis - HeartHitBigCheck >= HeartHitBig) {  // время большого зависания удара     if (currentMillis - HeartHitLightCheck >= HeartHitLight) { // время коротких ударов       HeartHitLightCheck = currentMillis; // обнуление контроля времени коротких ударов       if (HeartControl<3){HeartControl++;}  // если переменная контроля отрисовки менее трех, то при каждом срабатывании скрипта мы плюсуем один к сумме       else {HeartControl = 0; HeartHitBigCheck = currentMillis;} //но если сумма переменной превысила три, то обнуляем ее и обнуляем счетчик длительного залипания сердца     }    } } "--------------------------------------------------------------------------" 

По подключению. Код та мы сделали, а вот как собрать проводочки и что да куда втыкать, вот это я вам не объяснил. Хотя я и так уверен что более 80% тех, кто решит почитать эту статейку и так уже это знают, но как по мне, статья будет не полная, если я не предоставлю максимум информации.

A5 Дисплей 1602 — SCL
A4 Дисплей 1602 — SDA
A0 ИК датчик

Сейчас я занимаюсь над созданием радио ключа для машины на основе ardruino, а на экране 1602 хочу вывести данные датчика влаги и температуры снаружи машины (так как я скоро буду в Москве, искать себе новую работу, мне надо будет знать что твориться за бортом машины так как там очень холодно), часы, вольтметр аккумулятора и туда-же поставить игры (у меня осталось 4 пина свободных, а мне надо еще стооолько туда впихнуть), что бы можно было на светофоре потыкать в эту игру или во вторую, которую я планирую сделать как гоночки старых добрых карманных приставок из 2000х где суть была просто объезжать препятствие и музыку поставить на фон из Rock n Roll Racing. Ухх классика =) и всунуть оптимально это все в одну ardruino не используя внешние хранилища данных(чисто челенж), но я уже задействовал ~60 % и если код игры займет максимум 15% — 20%, то музыка… ох…там с этим будут проблемы, весит много, надо оптимизировать, я уже начал собирать трек и умудрился уменьшить вес почти в 10 раз, но я допустил ошибки в построении нот и тонов и теперь мне придется делать все заново. Я скорее всего потом напишу сюда полный вариант с описанием проекта, над которым я сейчас работаю. Надеюсь кому-то что-то новое для размышления я предоставил, если будет все хорошо и время, я продолжу писать статьи. Будут вопросы? Пишите в комментариях.

Все, подписывайтесь, ставьте лайки, следите за новыми выходами статей.
Всем спасибо за внимание, чао-какао!

P.S. работоспособность игры снять на видео, я пока что не могу, умерла камера на телефоне и это очень печально. Но я что-то придумаю и добавлю чуть позже видос в статью.

Первая часть статейки -> habr.com/post/424859


ссылка на оригинал статьи https://habr.com/post/425367/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *