用 Arduino Mega 与 MAX72XX LED 矩阵复刻经典《小行星》游戏
元器件与物料
| Arduino Mega 2560 | ×1 |
| MAX7219 LED 矩阵驱动板(MAXREFDES99# 或同类) | ×1 |
| 12 mm 轻触开关 | ×2 |
| 有源蜂鸣器 | ×1 |
| 面包板 + 跳线 | ×1 |
项目简介
在 8×8 LED 点阵上复刻 1979 年雅达利街机经典《小行星》:玩家控制飞船(底行单点)左右移动躲避从顶部随机落下的小行星(双点)。游戏随时间加速,碰撞即结束并显示生存分数(滴答数)。两按键分别对应左/右,支持环绕移动。蜂鸣器模拟爆炸音效。代码基于 LedControl 库,结构清晰,适合学习游戏循环、中断去抖、点阵驱动。
接线说明
| 模块引脚 | Mega 引脚 |
|---|---|
| DIN (DataIn) | 12 |
| CLK | 11 |
| LOAD (CS) | 10 |
| 左键 | 2 (INT0, INPUT_PULLUP) |
| 右键 | 3 (INT1, INPUT_PULLUP) |
| 蜂鸣器 | 9 (PWM) |
核心逻辑
- 游戏节拍:初始 300 ms,每 4 拍加速 2%(
tick/=1.02)。 - 小行星生成:每 4 拍在 8 列中随机选一列,若该列为空则在行 1 生成。
- 下落:每拍所有列行号 +1,行号达 10 清零(移出屏幕)。
- 碰撞判定:飞船列与小行星列相同且行号为 9 或 8(即将触及底行)判定失败。
- 爆炸动画:四帧十字扩散配合随机频率蜂鸣。
- 分数显示:< 100 直接显示两位数字;≥100 显示低两位,百位以底行亮点数量表示(每点 100 分)。
- 重新开始:Game Over 状态下任意按键复位
setup()。
完整代码
/*********************************************************
* Asteroids on MAX72XX LED Matrix
* (c) 2018 Joshua Weßner
*********************************************************/
#include "LedControl.h" // Eberhard Fahle 库
LedControl lc = LedControl(12, 11, 10, 1); // DIN, CLK, LOAD, 1 级联
// 全局变量
unsigned long delaytime = 1000;
int score = 0, tick = 300, tickCounter = 1;
int ship = 3; // 飞船列 0-7
int columns[8] = {0}; // 每列小行星行号,0=无
volatile unsigned long btnTime = 0;
const int btnDelay = 150; // 去抖 ms
volatile bool gameOver = false;
unsigned long now;
void setup() {
gameOver = false; score = 0; tick = 300; tickCounter = 1; ship = 3;
now = millis(); btnTime = millis();
randomSeed(analogRead(A15));
lc.shutdown(0, false); lc.setIntensity(0, 1); lc.clearDisplay(0);
pinMode(2, INPUT_PULLUP); pinMode(3, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), left, FALLING);
attachInterrupt(digitalPinToInterrupt(3), right, FALLING);
}
void left() {
if (millis() - btnTime > btnDelay) {
ship = (ship == 0) ? 7 : ship - 1;
lc.clearDisplay(0); btnTime = millis();
}
if (gameOver) { gameOver = false; setup(); }
}
void right() {
if (millis() - btnTime > btnDelay) {
ship = (ship == 7) ? 0 : ship + 1;
lc.clearDisplay(0); btnTime = millis();
}
if (gameOver) { gameOver = false; setup(); }
}
void loop() {
if (millis() - now > tick) {
now = millis(); score++;
if (tickCounter == 1) { // 每 4 拍
tick = tick / 1.02; // 加速
int col = random(0, 8);
if (columns[col] == 0) columns[col] = 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);
}
// 绘制飞船(底行 row 7)
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 f = 0; f < 4; f++) {
lc.setLed(0, 7, ship+f, true); lc.setLed(0, 7, ship-f, true);
lc.setLed(0, 7-f, ship+f, true); lc.setLed(0, 7-f, ship-f, true);
lc.setLed(0, 7-1.5*f, ship, true);
unsigned long t = millis(); int rs = 1000;
while (millis() - t <= 250) { rs--; tone(9, random(rs, 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[0];
char d2 = s[s.length()-1];
auto drawDigit = [&](char c, int pos) {
byte pat[3];
switch(c){
case '0': pat[0]=B00011111; pat[1]=B00010001; pat[2]=B00011111; break;
case '1': pat[0]=B00001000; pat[1]=B00011111; pat[2]=0; break;
case '2': pat[0]=B00010111; pat[1]=B00010101; pat[2]=B00011101; break;
case '3': pat[0]=B00010001; pat[1]=B00010101; pat[2]=B00011111; break;
case '4': pat[0]=B00011100; pat[1]=B00000100; pat[2]=B00011111; break;
case '5': pat[0]=B00011101; pat[1]=B00010101; pat[2]=B00010111; break;
case '6': pat[0]=B00011111; pat[1]=B00010101; pat[2]=B00010111; break;
case '7': pat[0]=B00010000; pat[1]=B00010011; pat[2]=B00011100; break;
case '8': pat[0]=B00011111; pat[1]=B00010101; pat[2]=B00011111; break;
case '9': pat[0]=B00011101; pat[1]=B00010101; pat[2]=B00011111; break;
default: pat[0]=pat[1]=pat[2]=0;
}
for(int r=0;r<3;r++) if(pat[r]) lc.setColumn(0, pos+r, pat[r]);
};
if (score < 100) {
drawDigit(d1, 1); drawDigit(d2, 5);
} else {
drawDigit(d1, 1); drawDigit(d2, 5);
int hundreds = d0 - '0';
for (int h = 1; h <= hundreds; h++) {
lc.setLed(0, 0, h-1, true); lc.setLed(0, 1, h-1, true);
delay(200);
}
}
gameOver = true;
while (gameOver) { /* 等待按键 */ }
}
}
原理图

扩展建议
- 增加多级难度、生命值、高分存 EEPROM。
- 改用 16×16 或 32×8 矩阵扩大视野。
- 加入 OLED 显示分数/菜单,释放矩阵仅做游戏区。
制造工艺
- 国际象棋:从古代起源到现代制造的专家概览
- 弹球机:经典街机游戏的历史、设计与制造
- 美式橄榄球的演变与工艺:从古老游戏到现代NFL标准
- 用 Arduino UNO 和 OLED 显示屏制作经典 Pong 游戏:分步教程
- 用 Arduino Nano 自制 37 灯珠轮盘赌游戏机
- 用 Arduino Nano 与 MAX7219 制作 24×16 LED Pong 游戏机
- 自制 16×8 LED 矩阵俄罗斯方块游戏机:Arduino Nano + 74HC595 驱动
- Arduino + 模拟摇杆互动游戏:五向 LED 反馈
- 互动 LED 矩阵门显:PIR 触发节日问候 + 待机特效
- 声控 8 路 LED 灯光秀:Arduino 入门项目
- 从零构建智能 Arduino 四足机器人:完整指南
- DIY 热熔胶棒 LED 矩阵灯 — 打造多彩显示屏