Классическая игра Asteroids на Arduino Mega и матрице MAX72XX (LED 8×8)
Необходимые компоненты
- Arduino Mega 2560 — 1 шт.
- Модуль/шилд драйвера MAX7219 (MAXREFDES99# или любой 8×8 матрица на MAX7219) — 1 шт.
- Тактильные кнопки 12 мм — 2 шт. (влево/вправо)
- Пьезо-зуммер (активный) — 1 шт.
- Breadboard и перемычки
О проекте
Простая аркадная игра «Астероиды» на одной матрице 8×8. Игрок — космический корабль (один светодиод в нижнем ряду), управляется двумя кнопками. Астероиды (два светодиода вертикально) падают сверху в случайных колонках. Со временем игра ускоряется. При столкновении — взрыв (анимация) и показ счёта (сколько тактов выжили). Нажатие любой кнопки после Game Over — рестарт.
Схема подключения
- MAX7219: DIN → D12, CLK → D11, CS/LOAD → D10, VCC/GND → 5V/GND.
- Кнопка «Влево»: D2 (INPUT_PULLUP), прерывание FALLING.
- Кнопка «Вправо»: D3 (INPUT_PULLUP), прерывание FALLING.
- Зуммер: D9 (PWM), GND.
Алгоритм и код (ключевые моменты)
- Экран — массив
int columns[8]: каждый элемент хранит строку (1..8) астероида в этой колонке (0 — пусто). - Главный цикл по таймеру
tick(начинается 300 мс, каждые 4 такта уменьшается на 2%:tick /= 1.02). - Каждый такт: с вероятностью 1/8 в случайной колонке появляется астероид (если колонка свободна). Все астероиды сдвигаются вниз (
columns[i]++). Ушедшие за экран (>10) удаляются. - Корабль: позиция
ship(0..7). Прерывания кнопок меняют её с обратной связью (циклически). Антидребезг 150 мс. - Отрисовка:
lc.setLed(0, 7, ship, true)— корабль; астероиды — два светодида подряд (columns[i]-2и-3). - Столкновение: если астероид в колонке корабля достиг строк 9 или 10 (корабль в строке 7, астероид 2 пикселя высотой).
- Взрыв: быстрая анимация расширяющегося креста + звук зуммера (случайная частота).
- Счёт: количество тактов. Вывод на матрице: две цифры (0..99), если >99 — последние две цифры + точки вверху для сотен.
- Цифры 0..9 рисуются функциями
draw0..draw9черезlc.setColumn(битмапы 3 колонки).
Полный скетч
/*********************************************************
* Asteroids на MAX72XX LED Matrix
* (c) 2018 Joshua Weßner
*********************************************************/
#include "LedControl.h" // библиотека Eberhard Fahle
// Пины MAX7219
LedControl lc = LedControl(12, 11, 10, 1);
// Глобальные переменные
unsigned long delaytime = 1000;
int score = 0, tick = 300, tickCounter = 1;
unsigned long now = 0, buttonPressed = 0;
const int buttonDelay = 150;
volatile bool gameOver = false;
int ship = 3; // начальная колонка
int columns[8] = {0}; // состояние астероидов
int randomInt;
void setup() {
gameOver = false; score = 0; tick = 300; tickCounter = 1; ship = 3; now = millis(); buttonPressed = millis();
for (int i=0;i<8;i++) columns[i]=0;
randomSeed(analogRead(15));
lc.shutdown(0,false);
lc.setIntensity(0,1); // яркость 0..15
lc.clearDisplay(0);
pinMode(2, INPUT_PULLUP); // Left
pinMode(3, INPUT_PULLUP); // Right
attachInterrupt(digitalPinToInterrupt(2), left, FALLING);
attachInterrupt(digitalPinToInterrupt(3), right, FALLING);
}
// Прерывания кнопок
void left() {
if (millis() - buttonPressed > buttonDelay) {
ship = (ship == 0) ? 7 : ship - 1;
lc.clearDisplay(0);
buttonPressed = millis();
}
if (gameOver) { gameOver = false; setup(); }
}
void right() {
if (millis() - buttonPressed > buttonDelay) {
ship = (ship == 7) ? 0 : ship + 1;
lc.clearDisplay(0);
buttonPressed = millis();
}
if (gameOver) { gameOver = false; setup(); }
}
void loop() {
if (millis() - now > tick) {
now = millis();
score++;
if (tickCounter == 1) { // каждые 4 такта
tick = tick / 1.02; // ускорение
randomInt = random(0,8);
if (columns[randomInt] == 0) columns[randomInt] = 1; // новый астероид
}
tickCounter = (tickCounter == 4) ? 1 : tickCounter + 1;
// Падение астероидов
for (int i=0;i<8;i++) {
if (columns[i] == 10) columns[i] = 0;
if (columns[i] != 0) columns[i]++;
}
lc.clearDisplay(0);
}
// Отрисовка
lc.setLed(0, 7, ship, true); // корабль
for (int i=0;i<8;i++) {
if (columns[i] > 0) {
lc.setLed(0, columns[i]-2, i, true);
lc.setLed(0, columns[i]-3, i, true);
}
}
// Проверка столкновения
if (columns[ship] == 9 || columns[ship] == 10) {
lc.clearDisplay(0);
// Анимация взрыва
for (int i=0;i<4;i++) {
lc.setLed(0,7,ship+i,true);
lc.setLed(0,7,ship-i,true);
lc.setLed(0,7-i,ship+i,true);
lc.setLed(0,7-i,ship-i,true);
lc.setLed(0,7-1.5*i,ship,true);
// Звук
unsigned long t = millis();
int rndSnd = 1000;
while (millis() - t <= 250) {
rndSnd--;
tone(9, random(rndSnd, 1000));
}
lc.clearDisplay(0);
noTone(9);
}
delay(500);
// Показ счёта
String s = String(score);
char d0 = s.length()>=3 ? s[0] : '0';
char d1 = s.length()>=2 ? s[s.length()-2] : (s.length()==1 ? '0' : s[0]);
char d2 = s.length()>=1 ? s[s.length()-1] : '0';
// Для простоты: выводим последние две цифры
int pos = 1;
auto drawDigit = [&](char c, int p) {
switch(c) {
case '0': draw0(p); break;
case '1': draw1(p); break;
case '2': draw2(p); break;
case '3': draw3(p); break;
case '4': draw4(p); break;
case '5': draw5(p); break;
case '6': draw6(p); break;
case '7': draw7(p); break;
case '8': draw8(p); break;
case '9': draw9(p); break;
}
};
if (score < 100) {
drawDigit(d1, 1); drawDigit(d2, 5);
} else {
drawDigit(d1, 1); drawDigit(d2, 5);
// Сотни — точки вверху
int hundreds = d0 - '0';
for (int i=1;i<=hundreds;i++) {
lc.setLed(0,0,i-1,true);
lc.setLed(0,1,i-1,true);
delay(200);
}
}
gameOver = true;
while (gameOver) { /* ждём нажатия кнопки в прерывании */ }
}
}
// Функции рисования цифр (3 колонки каждая)
void draw1(int p) { lc.setColumn(0,p,B00001000); lc.setColumn(0,p+1,B00011111); }
void draw2(int p) { lc.setColumn(0,p,B00010111); lc.setColumn(0,p+1,B00010101); lc.setColumn(0,p+2,B00011101); }
void draw3(int p) { lc.setColumn(0,p,B00010001); lc.setColumn(0,p+1,B00010101); lc.setColumn(0,p+2,B00011111); }
void draw4(int p) { lc.setColumn(0,p,B00011100); lc.setColumn(0,p+1,B00000100); lc.setColumn(0,p+2,B00011111); }
void draw5(int p) { lc.setColumn(0,p,B00011101); lc.setColumn(0,p+1,B00010101); lc.setColumn(0,p+2,B00010111); }
void draw6(int p) { lc.setColumn(0,p,B00011111); lc.setColumn(0,p+1,B00010101); lc.setColumn(0,p+2,B00010111); }
void draw7(int p) { lc.setColumn(0,p,B00010000); lc.setColumn(0,p+1,B00010011); lc.setColumn(0,p+2,B00011100); }
void draw8(int p) { lc.setColumn(0,p,B00011111); lc.setColumn(0,p+1,B00010101); lc.setColumn(0,p+2,B00011111); }
void draw9(int p) { lc.setColumn(0,p,B00011101); lc.setColumn(0,p+1,B00010101); lc.setColumn(0,p+2,B00011111); }
void draw0(int p) { lc.setColumn(0,p,B00011111); lc.setColumn(0,p+1,B00010001); lc.setColumn(0,p+2,B00011111); }
Библиотека
Требуется LedControl (Eberhard Fahle): GitHub — установите через Library Manager.
Идеи для развития
- Добавить несколько матриц каскадом (параметр 4-й в конструкторе LedControl) для шире экрана.
- Реализовать выстрел (третья кнопка) и разрушение астероидов.
- Звуковые эффекты: запуск, попадание, уровень.
- Сохранение рекорда в EEPROM.
Производственный процесс
- Шахматы: от древних корней к современному производству — экспертный обзор
- Пинбол: история, дизайн и производство классической аркадной игры
- Эволюция и мастерство американского футбольного мяча: от древних игр к современным стандартам НФЛ
- Создание классической игры Pong на Arduino UNO с OLED-дисплеем: пошаговый урок
- Создание рулетки на 37 светодиодов с Arduino Nano
- Сборка консоли Pong 24×16 на Arduino Nano и MAX7219
- Tetris на Arduino Nano: самостоятельная сборка 16×8 LED-матрицы на 74HC595
- Интерактивная игра с джойстиком и LED-обратной связью на Arduino
- Интерактивный LED-дисплей в двери с PIR-датчиком — новогоднее приветствие на Arduino
- Звукоактивируемый светодиодный дисплей 8 LED на Arduino