ЧАСТЬ #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/
Добавить комментарий