Проекты на Arduino Uno и Slot Shield
«Flappy Bird» — звезда вьетнамского игростроя. Она произвела революцию на рынке мобильных игр. За неказистой графикой и простым геймплеем скрывалась невероятно азартная и увлекательная игра. Говорят, она стоила геймерам не одного разбитого телефона. Мы портировали игру на Ардуино.
Игровое поле — это пара RGB матриц. Они превратятся в экран разрешением 4 на 8 пикселей. Мозг проекта — оригинальная Arduino Uno. Полётом жёлтой птички управляет 3D-джойстик. Нажмёте джойстик вверх, птичка взмахнёт крыльями и взлетит. Оставите в покое, и она начнёт снижаться. Всё просто. Главное — не врезайтесь в зелёные трубы.
Полный сет компонентов проекта. В сет входят:
Установите Troyka Slot Shield на Arduino Uno
Вставьте RGB матрицу в разъём С
. Ножка S
должна подключится к пину 4
.
3D-джойстик подключите в слот A
. Сигнал с X
придёт на пин A4
, сигнал с Y
на A4
. Нажатие на джойстик будет передаваться на пин 2
.
Вставьте вторую RGB матрицу в разъём F
. Сигнальный пин S
модуля должен подключится к пину "10" платы.
Прошейте контроллер скетчем через Arduino IDE.
// библиотека для работы с RGB-матрицей #include <Adafruit_NeoPixel.h> // номер пина, к которому подключена RGB-матрица #define MATRIX_C_PIN 4 #define MATRIX_F_PIN 10 // количество светодиодов в матрице #define LED_COUNT 16 // зерно для генератора случайных чисел #define ANALOG_PIN_FOR_RND A3 // максимальная яркость матрицы от 0 до 255 #define BRIGHT 10 // даём разумные имена пинам, к которым подключён джойстик #define X A5 #define Y A4 #define Z 2 // создаём объект класса Adafruit_NeoPixel Adafruit_NeoPixel matrix [] = {Adafruit_NeoPixel(LED_COUNT, MATRIX_C_PIN, NEO_GRB + NEO_KHZ800), Adafruit_NeoPixel(LED_COUNT, MATRIX_F_PIN, NEO_GRB + NEO_KHZ800) }; // создаем структуру для размещения в ней цветов struct Light { uint32_t Y = 0xffff00; uint32_t G = 0x00ff00; uint32_t B = 0x0000ff; }; // создаем структуру для размещения в ней параметров счета struct Store { int value; bool flag; }; // создаем структуру для размещения в ней параметров игровых объектов struct GameObjects { int x; int y; bool flag; }; // создаем объект структуры Light Light light; // создаем объект структуры Store Store store; // создаем объекты структуры GameObjects: игрока и стену GameObjects player, wall; // создаем объект класса long для хранения времени unsigned long timeOfGravity = 0; // создаем счетчик, по которому действует гравитация int gravity = 500; // создаем объект класса long для хранения времени unsigned long timeOfSide = 0; // создаем счетчик, который определяет "скорость" стены int side; void setup() { // инициализируем последовательность случайных чисел randomSeed(analogRead(ANALOG_PIN_FOR_RND)); // инициализируем все матрицы for (int i = 0; i < sizeof(matrix) / sizeof(Adafruit_NeoPixel); i++) { matrix[i].begin(); matrix[i].setBrightness(10); } // открываем последовательный порт Serial.begin(9600); // очищаем матрицы draw_clear (); } void loop() { // запускаем функцию предстартовой подготовки at_the_start(); // пока флаг счета опущен, игра не начнется // когда же он поднят, то не игра закончится while (store.flag) { // очищаем матрицы draw_clear (); // отслеживаем перемещение стены wall_move (); // рисуем стену на матрицах wall_draw(); // считываем движения джойстика player_move (); // рисуем на матрице положение игрока draw_point(player.x, player.y, light.Y); // проверяем, не вышел ли игрок за пределы поля if ( player.y < 0 || player.y >= 8) { // если вышел, то воспроизводим проигрыш game_over(); } // когда стена и игрок сравняются по x... if (player.x == wall.x ) { // ...проверяем, не врезался ли игрок в стену if (player.y != wall.y && player.y != wall.y + 1) { // если врезался, то воспроизводим проигрыш game_over(); } // ... если игра все же не проиграна то однократно выполняем условие победы if (wall.flag) { // увеличиваем счет store.value ++; // увеличиваем "скорость" side -= side / 10; // опускаем флаг однократного выполнения wall.flag = false; } } else { // держим флаг поднятым до тех пор, пока не наступит победа wall.flag = true; } } } // функция приготовления к игре void at_the_start() { // очищаем матрицы draw_clear (); // задаем начальное значение счетчика "скорости" стены side = 1500; // обнуляем значение счета и держим флаг счетчика опущенным store = { 0, false}; // задаем начальное значение координат игрока player = {3, 4, true}; // задаем начальное значение координат стены wall = { -1, random(0, 6), true}; // считываем движения джойстика player_move (); // рисуем на матрице положение игрока draw_point(player.x, player.y, light.Y); delay (250); } // функция окончания игры void game_over() { // очищаем матрицы draw_clear (); // заливаем матрицы синим цветом draw_pouring (light.B); // выводим счет в Serial port Serial.print ("Your store: "); Serial.println (store.value); // опускаем флаг счета store.flag = false; // инициализируем последовательность случайных чисел randomSeed(analogRead(ANALOG_PIN_FOR_RND)); delay (250); } // функция движения игрока void player_move () { int y, z; // считываем текущее значение джойстика по Y y = analogRead(Y); if (y < 100 && player.flag) { player.y ++; player.flag = false; } if (y > 924 && player.flag) { player.y --; player.flag = false; } if (100 < y && y < 924) { player.flag = true; } // создаем постоянную простую гравитацию if (millis() - timeOfGravity > gravity) { player.y --; timeOfGravity = millis(); } // считываем текущее значение джойстика по Z z = digitalRead(Z); if (z == 1 && store.flag == false) { store.flag = true; } } // функция движения стены void wall_move () { // запускаем таймер, по которому стена будет перемещаться if (millis() - timeOfSide > side) { // если стена за пределами видимости игрока if (wall.x >= 4) { // то изменяем ее и перемещаем в начало wall.x = 0; wall.y = random(0, 7); } else { // если нет, то двигаем ее на игрока wall.x++; } timeOfSide = millis(); } } // функция рисования препятствия void wall_draw () { // проверяем координату стены, в зависимости от этого рисуем одну или 2 линии switch (wall.y) { case 0: { draw_line(wall.x, 2, wall.x, 7, light.G); break; } case 6: { draw_line(wall.x, 0, wall.x, 5, light.G); break; } default : { draw_line(wall.x, 0, wall.x, wall.y - 1, light.G); draw_line(wall.x, wall.y + 2, wall.x, 7, light.G); break; } } } // функция для перевода координат в № матрицы int nHelper (int x, int y) { return y / 4 + ((x / 4) * 2); } // функция для перевода координат в № светодиода int mHelper(int x, int y) { switch (nHelper (x, y)) { case 0: { return {y * 4 + x}; break; } case 1: { return {abs (y - 8) * 4 - x - 1}; break; } } } // функция для рисования линий по алгоритму Брезенхэма void draw_line(int x1, int y1, int x2, int y2, uint32_t RGB) { const int deltaX = abs(x2 - x1); const int deltaY = abs(y2 - y1); const int signX = x1 < x2 ? 1 : -1; const int signY = y1 < y2 ? 1 : -1; int error = deltaX - deltaY; matrix[nHelper(x2, y2)].setPixelColor(mHelper(x2, y2), RGB); while (x1 != x2 || y1 != y2) { matrix[nHelper(x1, y1)].setPixelColor(mHelper(x1, y1), RGB); const int error2 = error * 2; if (error2 > -deltaY) { error -= deltaY; x1 += signX; } if (error2 < deltaX) { error += deltaX; y1 += signY; } } for (int i = 0; i < sizeof(matrix) / sizeof(Adafruit_NeoPixel); i++) { matrix[i].show(); } } // функция для рисования сплошной заливкой void draw_pouring (uint32_t RGB) { for (int i = 0; i < sizeof(matrix) / sizeof(Adafruit_NeoPixel); i++) { for (int j = 0; j < LED_COUNT; j++) { matrix[i].setPixelColor(j, RGB); matrix[i].show(); } } } // функция для рисования точки void draw_point (int x, int y, uint32_t RGB) { matrix[nHelper(x, y)].setPixelColor(mHelper(x, y), RGB); matrix[nHelper(x, y)].show(); } // функция для очистки матриц void draw_clear () { for (int i = 0; i < sizeof(matrix) / sizeof(Adafruit_NeoPixel); i++) { matrix[i].clear(); matrix[i].show(); } }
Где скачать необходимые библиотеки и как их установить?