С тех пор как появилось программирование появились и различные электронные игры. При этом программированием игр часто занимаются даже опытные программисты, улучшая с помощью этого свои навыки программирования, а также получая удовольствие от создания интересной игры.
В этом проекте мы рассмотрим создание с помощью платформы Arduino игры под названием "космическая гонка", в которую можно будет играть на графическом ЖК дисплее Nokia 5110 - его подключение к плате Arduino мы рассматривали в этой статье. Этакий "олдскульный" графический дисплей от бывшего флагмана в производстве сотовых телефонов.
Также на нашем сайте вы можете посмотреть похожий проект – игра в змейку с помощью Arduino.
Основные принципы создания игры
Перед началом создания данного проекта удостоверьтесь в том, что вы уже освоили подключение к плате Arduino дисплея Nokia 5110 и джойстика. Поскольку дисплей Nokia 5110 (купить на AliExpress) не может похвастаться приличным разрешением ограничимся разрешением 84х48 пикселов для нашей игры.
Внутри этого пространства необходимо разместить игровую зону и служебную зону, в которой будет отображаться счет игры и некоторые другие вещи. Необходимо будет с точностью до пикселя знать все размеры этих зон.
Теперь необходимо определиться с объектами в нашей игре. Первым объектом будет наш космический корабль, а вторым – вражеский космический корабль. Поскольку дисплей Nokia 5110 может отображать растровые изображения (bitmap images), то мы можем использовать это чтобы отображать на его экране эти два объекта космических кораблей.
Таким образом, у нас будет космический корабль, который будет лететь сквозь рой вражеских кораблей, пытаясь уклониться от встречи с ними – для этого у него будет три линии, по которым он будет маневрировать. В любой момент времени вражеские корабли могут занимать только две линии, поэтому у игрока будет всегда возможность переместиться на свободную линию.
Работа схемы
Схема устройства для игры в космическую гонку представлена на следующем рисунке.
Как видите, схема очень проста – достаточно подключить к плате Arduino ЖК дисплей Nokia 5110 и джойстик.
Джойстик работает от питающего напряжения 5V, а ЖК дисплей Nokia 5110 – от напряжения 3.3V, поэтому убедитесь в том что вы его подключили к разъему 3.3V платы Arduino, а не к 5V – иначе можно необратимо повредить дисплей. ЖК дисплей Nokia 5110 взаимодействует с платой Arduino по протоколу SPI, а значения с выхода джойстика считываются с помощью аналого-цифрового преобразователя (АЦП) на контакте A1 платы Arduino.
После сборки схемы на макетной плате у вас должна получиться примерно следующая конструкция.
Объяснение программы для Arduino
Полный код программы приведен в конце статьи, здесь же рассмотрим его основные фрагменты.
Вначале в программе нам необходимо подключить заголовочные файлы библиотек, которые мы будем использовать. Библиотека для работы SPI по умолчанию присутствует в Arduino IDE, остальные две библиотеки необходимо скачать с Github. Работа с ними более подробно рассмотрена в статье про подключение ЖК дисплея Nokia 5110 к плате Arduino.
1 2 3 |
#include <SPI.h> //библиотека для связи по протоколу SPI #include <Adafruit_GFX.h> //графическая библиотека для ЖК дисплея #include <Adafruit_PCD8544.h> //библиотека для ЖК дисплея Nokia 5110 |
ЖК дисплей Nokia 5110 позволяет отображать растровые/точечные изображения (bitmap images). Более подробно про конвертирование изображений в шестнадцатеричный код для данного дисплея можно прочитать в уже упоминавшейся статье про подключение этого дисплея к плате Arduino. Поэтому вместо использованных нами изображений для отображения космических кораблей (нашего и вражеских) вы можете использовать любые изображения, скачанные из интернета – главное чтобы по размеру они подходили к размеру экрана нашего дисплея. Далее приведены коды для использованных нами изображений космических кораблей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//Bitmap Data для нашего космического корабля static const unsigned char PROGMEM ship[] = { B00000000,B00000000, B00000001,B00000000, B00000011,B10000000, B00000010,B10000000, B00000010,B11000000, B00000111,B11000000, B00001101,B11100000, B00011111,B11110000, B00111111,B11111000, B01111111,B11111100, B01111111,B11111100, B01111111,B11111100, B00011111,B11110000, B00000111,B11100000, B00000000,B00000000, }; //Bitmap Data для вражеского космического корабля static const unsigned char PROGMEM enemy[] = { B00000101,B11000000, B00001011,B11100000, B00000011,B11100000, B00110011,B11111000, B01111111,B11111100, B10111111,B11111010, B01110111,B11011100, B01111110,B11111100, B00111111,B11111100, B11101111,B11101110, B11000001,B00000110, B10000001,B00000010, B10000000,B00000010, B00000000,B00000000, }; |
Далее в программе мы должны указать плате Arduino к каким ее контактам подключен ЖК дисплей Nokia LCD 5110. Если вы использовали соединения как в представленной нами схеме, то когда код этой команды будет следующий:
1 |
Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3); //Specifiy the pins to which the LCD is connected |
Внутри функции void setup() мы инициализируем последовательную связь со скорость 9600 бод/с (для целей отладки программы) и ЖК дисплей. Далее установим нужную нам контрастность ЖК дисплея – можете поэкспериментировать со значением контрастности чтобы игра наилучшим образом смотрелась на вашем дисплее. И затем очистим экран ЖК дисплея.
1 2 3 4 5 6 |
void setup() { Serial.begin(9600); //последовательная связь для целей отладки display.begin(); //инициализируем ЖК дисплей display.setContrast(30); //устанавливаем контрастность дисплея display.clearDisplay(); // очищаем экран дисплея } |
После очистки экрана дисплея мы попадаем в функцию loop, в которой мы будем отображать экран нашей игры, который представляет собой "скелет" игры и кроме самого процесса игры на нем еще отображается счет и скорость нашего космического корабля. Мы использовали функцию line чтобы нарисовать линии (дорожки) для движения нашего корабля. Как можно видеть, у нас получился интерфейс, похожий на интерфейс старых "олдскульных" игр для игровых приставок.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void gamescreen() { //рисуем границы экрана display.drawLine(0, 0, 0, 47, BLACK); display.drawLine(50, 0, 50, 47, BLACK); display.drawLine(0, 47, 50, 47, BLACK); //отображаем текст по умолчанию display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(52,2); display.println("Speed"); display.setCursor(54,12); display.println(game_speed); display.setCursor(52,25); display.println("Score"); display.setCursor(54,35); display.println(score); } |
Далее в программе мы должны считывать от пользователя сигналы управления космическим кораблем, которые передаются к нам в плату Arduino от джойстика, подключенного к контакту A1. Если рукоятка джойстика находится в среднем положении, то на выходе АЦП контакта A1 будет значение 512 – при движении рукоятки джойстика вдоль оси X это значение будет изменяться. Мы будем использовать это чтобы определить в какую сторону наклонена рукоятка джойстика – вправо или влево. Более подробно о работе с джойстиком можно прочитать в статье про его подключение к плате Arduino.
1 2 3 4 5 6 7 8 9 |
//Get input from user Joy_X = analogRead(A1); //считываем значение X с джойстика if (Joy_X < 312 && POS!=1 && control==true) //если рукоятка джойстика наклонена влево { POS--; control = false;} //уменьшаем позицию космического корабля else if (Joy_X > 712 && POS!=3 && control==true) // если рукоятка джойстика наклонена вправо { POS++; control = false;} // увеличиваем позицию космического корабля else if (Joy_X >502 && Joy_X<522) // если рукоятка джойстика находится в исходном положении control = true; //подготовка к следующему движению //Input from user received |
После получения позиции космического корабля от пользователя мы должны разместить его на экране дисплея в соответствующей позиции.
1 2 3 4 5 6 7 8 9 10 11 |
void player_car(char pos) //помещаем космический корабль на выбранную пользователем позицию { if (pos==1) display.drawBitmap(2, 32, ship, 15, 15, BLACK); //на первую линию if (pos==2) display.drawBitmap(18, 32, ship, 15, 15, BLACK); //на вторую линию if (pos==3) display.drawBitmap(34, 32, ship, 15, 15, BLACK); //на третью линию } |
Теперь, когда наш космический корабль размещен на экране дисплея в нужной позиции, мы должны смоделировать на экране дисплея движение вражеских космических кораблей. Каждый раз когда вражеский корабль будет пересекать линию экрана мы будем считать его мертвым и создавать новый вражеский корабль – это выполняется в следующей нижеприведенной функции. В этой функции создается новая позиция для двух вражеских кораблей и они размещаются вверху экрана.
1 2 3 4 5 6 7 |
if (enemy_dead) //проверяем мертвы ли вражеские корабли { //если они мертвы enemy_0_pos = POS; // создаем первый вражеский корабль enemy_1_pos = random(0,4); // создаем второй вражеский корабль в случайном месте enemy_phase = 0; //помещаем врагов на верх экрана enemy_dead = false; //вражеские корабли создаются "живыми" } |
После помещения вражеских кораблей на верх экрана мы должны постепенно перемещать их вниз чтобы у пользователя создавалось впечатление что он на своем космическом корабле летит вперед.
1 2 |
enemy_ship (enemy_0_pos,enemy_phase); enemy_phase++; //помещаем первый вражеский корабль на экран и двигаем его вниз enemy_ship (enemy_1_pos,enemy_phase); enemy_phase++; //помещаем второй вражеский корабль на экран и двигаем его вниз |
В функции enemy_ship мы отображаем вражеский корабль в нужном нам месте. Ее параметры: place – позиция корабля по горизонтали (она неизменна после создания корабля), phase – позиция корабля по вертикали (она изменяется в процессе перемещения вражеского корабля).
1 2 3 4 5 6 7 8 9 |
void enemy_ship(int place, int phase) //помещаем вражеский корабль на экране дисплея в точку с заданными координатами { if (place==1) display.drawBitmap(2, phase, enemy, 15, 15, BLACK); if (place==2) display.drawBitmap(18, phase, enemy, 15, 15, BLACK); if (place==3) display.drawBitmap(34, phase, enemy, 15, 15, BLACK); } |
В следующем фрагменте кода проверяется избежал ли наш корабль столкновения с вражеским кораблем. Чтобы проверить это нам нужно знать позиции обоих кораблей. Если пользователю не удалось избежать столкновения с вражеским кораблем, это будет означать конец игры.
1 2 |
if (enemy_phase>22 && ((enemy_0_pos == POS) || (enemy_1_pos == POS)) ) //если наш космический корабль касается вражеского корабля game_over(); //отображаем конец игры |
Если пользователь удачно избежал столкновения с вражеским кораблем мы убиваем вражеский корабль и начисляем пользователю один балл (счета). Чтобы осуществить это мы проверяем достиг ли вражеский корабль низа экрана и если достиг, то мы убиваем его с помощью следующего фрагмента кода.
1 2 |
if (enemy_phase>40) // если наш корабль избежал столкновения с вражеским кораблем {enemy_dead = true; score++;} //увеличиваем счет и убиваем вражеские корабли |
Но в любой игре интересно увеличивать ее сложность по мере того как мы начинаем играть в нее все лучше и лучше. Поэтому в следующей функции мы анализируем счет игры и зависимости от его значения увеличиваем скорость игры – это мы делаем с помощью функции delay, которая управляет интервалом между событиями игры.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void Level_Controller() //увеличиваем (изменяем) скорость игры в зависимости от счета { if (score>=0 && score<=10) //если счет 0-10 { game_speed = 0; delay(80); //замедляем игру на 80ms } if (score>10 && score<=20) // если счет 10-40 { game_speed = 1; delay(70); // замедляем игру на 70ms } if (score>20 && score<=30) // если счет 20-40 { game_speed = 2; delay(60); // замедляем игру на 60ms } if (score>30 && score<=40) // если счет 30-40 { game_speed = 3; delay(50); // замедляем игру на 50ms } } |
Тестирование игры “космическая гонка”
После того, как будет готова аппаратная часть проекта, можете приступать к загрузке программы в плату Arduino. После окончания загрузки программы вы должны на экране ЖК дисплея увидеть следующую картину:
Далее используйте джойстик чтобы уклониться вашим кораблем от столкновения с вражескими кораблями наклоняя рукоятку джойстика вправо или влево. По мере увеличения счета будет увеличиваться скорость игры – задержка, регулирующая скорость игры, будет уменьшаться на 10 мс на каждые 10 единиц счета. Вы можете усовершенствовать этот проект игры “космическая гонка” на основе добавления в нее акселерометра или внести в нее любые другие изменения на ваш вкус.
Исходный код программы
Комментарии к исходному коду программы переведены в разделе статьи, посвященном объяснению программы. Если вам нужна помощь в объяснении каких либо фрагментов этой программы – пишите об этом в комментариях к данной статье, постараюсь помочь.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
/* SPACE RACE Game using Arduino and Nokia 5110 LCD * Coded by: Aswinth Raj * Dated: 10-5-2018 * Website: www.circuitdigest.com * Input -> Joystick (A0,A1) */ #include <SPI.h> //SPI librarey for Communication #include <Adafruit_GFX.h> //Graphics lib for LCD #include <Adafruit_PCD8544.h> //Nokia 5110 LCD library //Bitmap Data for SpaceShip static const unsigned char PROGMEM ship[] = { B00000000,B00000000, B00000001,B00000000, B00000011,B10000000, B00000010,B10000000, B00000010,B11000000, B00000111,B11000000, B00001101,B11100000, B00011111,B11110000, B00111111,B11111000, B01111111,B11111100, B01111111,B11111100, B01111111,B11111100, B00011111,B11110000, B00000111,B11100000, B00000000,B00000000, }; //Bitmap Data for enemyship static const unsigned char PROGMEM enemy[] = { B00000101,B11000000, B00001011,B11100000, B00000011,B11100000, B00110011,B11111000, B01111111,B11111100, B10111111,B11111010, B01110111,B11011100, B01111110,B11111100, B00111111,B11111100, B11101111,B11101110, B11000001,B00000110, B10000001,B00000010, B10000000,B00000010, B00000000,B00000000, }; Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3); //Specifiy the pins to which the LCD is connected int enemy_0_pos, enemy_1_pos, enemy_phase; int Joy_X; int game_speed = 0; int score = 0; char POS=2; boolean enemy_dead = true; boolean control = true; void setup() { Serial.begin(9600); //Serial Monitor for Debugging display.begin(); //Begin the LCD communication display.setContrast(30); //Set the contrast of the display display.clearDisplay(); // clears the screen and start new } void loop() { display.clearDisplay(); // clears the screen and start new gamescreen(); //Displays the box, score and speed values //Get input from user Joy_X = analogRead(A1); //Read the X vaue from Joystick if (Joy_X < 312 && POS!=1 && control==true) //If joy stick moves right { POS--; control = false;} //Decrement position of spaceship else if (Joy_X > 712 && POS!=3 && control==true) //If joy stick moves right { POS++; control = false;} //Increment position of spaceship else if (Joy_X >502 && Joy_X<522) //If joystick back to initial position control = true; //Preare it for next move //Input from user received player_car(POS); //Place the Space ship based on the input from user if (enemy_dead) //Check of enemy ships are dead { //If they are dead enemy_0_pos = POS; //create first enemy above the space ship enemy_1_pos = random(0,4); //create secound enemy at some other random place enemy_phase = 0; //Bring the enemy form the top enemy_dead = false; //Enemy is created so they are not dead anymore } enemy_ship (enemy_0_pos,enemy_phase); enemy_phase++; //Place the first enemy on screen and drive him down enemy_ship (enemy_1_pos,enemy_phase); enemy_phase++; //Place the secound enemy on screen and drive him down if (enemy_phase>22 && ((enemy_0_pos == POS) || (enemy_1_pos == POS)) ) //If the Spaceship touches any one of the enemy game_over(); //Display game over if (enemy_phase>40) //If thespace ship escapes the enemys {enemy_dead = true; score++;} //Increase the score and kill the enemys Level_Controller(); //BAsed on score increase the speed of game display.display(); //Update the display with all the changes made so far } void Level_Controller() //Increase the speed of game based on the score. { if (score>=0 && score<=10) //If score 0-10 { game_speed = 0; delay(80); //slow the game by 80ms } if (score>10 && score<=20) //If score 10-40 { game_speed = 1; delay(70); //slow the game by 70ms } if (score>20 && score<=30) //If score 20-40 { game_speed = 2; delay(60); //slow the game by 60ms } if (score>30 && score<=40) //If score 30-40 { game_speed = 3; delay(50); //slow the game by 50ms } } void enemy_ship(int place, int phase) //Place the enemy_ship in the new place and phase { if (place==1) display.drawBitmap(2, phase, enemy, 15, 15, BLACK); if (place==2) display.drawBitmap(18, phase, enemy, 15, 15, BLACK); if (place==3) display.drawBitmap(34, phase, enemy, 15, 15, BLACK); } void game_over() //Display game over screen { while(1) //The program will be stuck here for ever { delay(100); display.clearDisplay(); display.setCursor(20,2); display.println("GAME OVER"); display.display(); } } void gamescreen() { //Draw the Border for Screen display.drawLine(0, 0, 0, 47, BLACK); display.drawLine(50, 0, 50, 47, BLACK); display.drawLine(0, 47, 50, 47, BLACK); //Enter Default Texts display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(52,2); display.println("Speed"); display.setCursor(54,12); display.println(game_speed); display.setCursor(52,25); display.println("Score"); display.setCursor(54,35); display.println(score); } void player_car(char pos) //Place the spaceship based on the user selected position { if (pos==1) display.drawBitmap(2, 32, ship, 15, 15, BLACK); if (pos==2) display.drawBitmap(18, 32, ship, 15, 15, BLACK); if (pos==3) display.drawBitmap(34, 32, ship, 15, 15, BLACK); } |