Создадим классическую игру "Змейка" на основе Ардуино, экрана от Nokia 5510 с кнопками, звуком, записью рекордов и меню настроек.
Компоненты
Для создания нашей игры "Змейка" на Ардуино нам понадобятся всего несколько компонентов:
- Arduino Uno x 1
- Экран от Nikia 5510 x 1
- Кнопки (кнопочные переключатели с подсветкой серии APEM A01) x 4
- Спикер
- Перемычки
- Макетная плата
Из программного обеспечения нам понадобится среда разработки Arduino Ide, а кроме того, мы должны забрать проект из репозитория автора на GitHub:
В этом проекте всего несколько деталей, поэтому аппаратную часть очень легко собрать:
- 4 кнопки для управления (лучше поменьше, но они у меня уже были)
- Nokia 5110 ЖК-экран (есть разные версии этого модуля - подсветка разного цвета: красная, зеленая, синяя и белая)
- Пьезо-динамик
- Ардуино Уно
Описание игры
Игра включает в себя следующую функциональность:
- Главное меню с 3 режимами: Play (Играть), Settings (Настройки) и Рекорды (High score)
- Меню настроек: управление контрастностью, подсветкой и игровыми звуками
- Звуки: воспроизведение звуков при окончании игры и поедании еды. В этом проекте звук играет только тогда, когда змея съедает еду и игра завершается.
- Рекорды: возможность хранения лучших результатов
Кнопки
- Влево - переместить змейку влево и вернуться в игровое меню
- Вправо - переместить змейку вправо и выбрать в меню
- Вверх - перемещение змейки вверх, вверх по меню и управление контрастом
- Вниз - перемещение змейки вниз, вниз в меню и управление контрастом
Схема соединений
В нашем случае у нас может быть две схемы - с резисторами и без резисторов.
Без резисторов
С резисторами
Библиотеки для проекта
Для экрана я использовал ЖК-библиотеку от Adafruit под названием PCD8544 Nokia 5110 LCD.
Описание библиотеки есть на нашем сайте на этой странице, но вы также можете установить её из "Менеджера библиотек" Ардуино.
Кроме того, она должна быть связана с библиотекой Adafruit GFX, поэтому нам нужно установить и её.
Также нам подойдет простой пьезо-динамик и ему не нужна библиотека. Функция tone()
используется для воспроизведения нот. Он использует 2 контакта Arduino: заземление и один цифровой контакт:
tone(pin, frequency, duration)
pin
(пин) - это цифровой вывод, который используется,frequency
(частота) - это частота тона в герцах,duration
(длительность) не является обязательной.
Код игры
Всё что нам необходимо мы забираем из репозитория на GitHub:
Ниже мы пройдемся по основным важным блокам нашей игры.
Рекорд
Рекорд сохраняется в памяти Arduino (EEPROM), так что он будет там, даже если отключить Arduino. Из EEPROM очень легко читать данные и их туда записывать.
void writeIntIntoEEPROM(int address, int number){
EEPROM.write(address, number >> 8);
EEPROM.write(address + 1, number & 0xFF);
}
int readIntFromEEPROM(int address){
byte byte1 = EEPROM.read(address);
byte byte2 = EEPROM.read(address + 1);
return (byte1 << 8) + byte2;
}
Логика игры
В игре у нас представлены следующие объекты:
- Block (блок) - представляет собой часть змеи
- Snake (змея)
- Food (еда)
- GameManager (менеджер игры)
Ниже объясню важные части кода исходя из этих классов. Целиком код игры вы найдете в репозитории на GitHub по ссылке выше.
Идея перемещения змейки заключается в том, чтобы переместить первый элемент в последовательности (хвост змейки) к последнему (голова змейки), а все остальные переместить назад. Другими словами, хвост получает новые координаты и становится головой змеи.
Змею можно перемещать по всему экрану и она не умрет, если столкнется с краями. Если это произойдет, то змея продолжит движение по другой стороне экрана в том же направлении.
void Snake::execute(){
Block* tail = _blocks[0];
//SWITCH CASE HAVE BUG!!!!
if(_direction == UP)
{
moveUp(tail);
}
else if(_direction == DOWN)
{
moveDown(tail);
}
else if(_direction == RIGHT)
{
moveRight(tail);
}
else if(_direction == LEFT)
{
moveLeft(tail);
}
for(int j = 0;j<_size;j++)
{
_blocks[j]=_blocks[j+1];
}
_blocks[_size-1] = tail;
_headX = tail->_x;
_headY = tail->_y;
for(int j = 0;j<_size-1;j++)
{
if(_blocks[j]->_x == _headX &&
_blocks[j]->_y == _headY)
{
_selfTouch=true;
}
}
draw();
}
void Snake::moveRight(Block* tail){
if(_blocks[_size-1]->_x + WEIGHT != WIDTH)
{
tail->_x=_blocks[_size-1]->_x + WEIGHT;
tail->_y=_blocks[_size-1]->_y;
}
else
{
tail->_x=0;
tail->_y=_blocks[_size-1]->_y;
}
}
void Snake::moveUp(Block* tail){
if(_blocks[_size-1]->_y != 0)
{
tail->_y=_blocks[_size-1]->_y - WEIGHT;
tail->_x = _blocks[_size-1]->_x;
}
else
{
tail->_y = HEIGHT - WEIGHT;
tail->_x = _blocks[_size-1]->_x;
}
}
void Snake::moveDown(Block* tail){
if(_blocks[_size-1]->_y + WEIGHT != HEIGHT)
{
tail->_y = _blocks[_size-1]->_y + WEIGHT;
tail->_x = _blocks[_size-1]->_x;
}
else
{
tail->_y = 0;
tail->_x = _blocks[_size-1]->_x;
}
}
void Snake::moveLeft(Block* tail){
if(_blocks[_size-1]->_x != 0)
{
tail->_x=_blocks[_size-1]->_x - WEIGHT;
tail->_y=_blocks[_size-1]->_y;
}
else
{
tail->_x = WIDTH - WEIGHT;
tail->_y = _blocks[_size-1]->_y;
}
}
Змея отрисовывается в цикле, где в каждой итерации рисуется по одному блоку.
void Snake::draw(){
for (byte i = 0; i < _size; i ++) {
_display->fillRect(_blocks[i]->_x, _blocks[i]->_y, _weight, _weight, BLACK);
}
}
Столкновение с едой проверяется в объекте GameManager. После того как змея съела еду, в массив блоков добавляется новый блок с координатами еды.
void GameManager::checkForCollision()
{
//check collision with food
if(_food->_x == _snake->_headX &&
_food->_y == _snake->_headY)
{
if(_sound)
{
tone(SPEAKER, NOTE_A7, TONE_FOOD_DURATION);
}
score+=_food->points;
_snake->addBlock(_food->_x, _food->_y);
_food->randomize(WIDTH - WEIGHT, HEIGHT - WEIGHT, WEIGHT);
}
}
Координаты еды определяются следующим образом:
void Food::randomize(byte width, byte height, byte weight){
_x = random(weight,width - weight);
if(_x % weight != 0)
_x+=1;
_y = random(2,height -weight);
if(_y % weight != 0)
_y+=1;
}
Как я уже сказал ранее - целиком код игры в репозитории:
SnakeGame.h содержит все публичные константы, поэтому вы можете изменить максимальную длину змеи, начальный контраст, ширину и высоту экрана и т.д.
Snake содержит массив блоков. Каждый блок имеет свои координаты x и y.
Food также имеет свои собственные координаты. Для рандомизации координат еды я использовал функцию Arduino random()
, в настройках которой используется randomSeed(analogRead(0))
. См. официальную документацию по функции randomSeed.
GameManager отвечает за логику игры (рисование игровых объектов, проверка на столкновение…). Объявлено в файле SnakeGame.ino.