DIY 打地鼠街机游戏(按键版)——Arduino Mega 2560 实现
核心特性
- 5 个大型街机按键,各自带高亮 LED,PWM 控制亮度(用于 Game Over 渐隐动画)
- 10 个难度等级:Lv1 简单 → Lv10 疯狂。每关内单步反应时限动态变化
- 伪随机选键:下一目标键永不与当前键相同
- 双 4 位 7 段数码管:左显当前分数/倒计时/选关,右显对应关卡最高分
- 数字音量控制
- 最高分掉电保存(EEPROM)
- 多阶段音效循环(内置扬声器播放)
- 激光切割 MDF 外壳 + 大量 3D 打印结构件
- 自制 Arduino Mega 扩展板
外壳组装


先组装激光切割外壳,可选喷漆。顶盖安装 3D 打印固定座,数码管支架装螺栓于下方(否则竖向立柱无法锁紧)。


扩展板组装:额外叠一组堆叠排针增加间距。

后控制面板、顶盖(按键+数码管)依次组装。





后面板含双电位器:音量、选关。


成品展示

左屏显示当前选关(1-10),右屏显示该关最高分。绿键启动,倒计时后开始游戏。
代码(Whac_A_Mole_v1.0.0)
/* Whac_A_Mole_v1.0.0 By WT040 */
#include "Wire.h"
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"
Adafruit_7segment matrix1 = Adafruit_7segment();
Adafruit_7segment matrix2 = Adafruit_7segment();
#define debounceTime 10
#define potmeterVolume 0
#define potmeterGameLevel 1
#define ampPower 32
#define btn1Pin 22
#define btn2Pin 24
#define btn3Pin 26
#define btn4Pin 28
#define btn5Pin 30
byte btnArray[] = { btn1Pin, btn2Pin, btn3Pin, btn4Pin, btn5Pin };
#define btn1 0; #define btn2 1; #define btn3 2; #define btn4 3; #define btn5 4
#define btnLed1Pin 9; #define btnLed2Pin 10; #define btnLed3Pin 11; #define btnLed4Pin 12; #define btnLed5Pin 13
byte btnLedArray[] = { btnLed1Pin, btnLed2Pin, btnLed3Pin, btnLed4Pin, btnLed5Pin };
#define btnLed1 0; #define btnLed2 1; #define btnLed3 2; #define btnLed4 3; #define btnLed5 4
#define fx0TriggerPin 29; #define fx1TriggerPin 31; #define fx2TriggerPin 33; #define fx3TriggerPin 35; #define fx4TriggerPin 37; #define fx5TriggerPin 39; #define fx6TriggerPin 41; #define fx7TriggerPin 43; #define fx8TriggerPin 45; #define fx9TriggerPin 47; #define fx10TriggerPin 49; #define fxVolUpPin 51; #define fxVolDnPin 53
byte fxTriggerArray[] = { fx0TriggerPin, fx1TriggerPin, fx2TriggerPin, fx3TriggerPin, fx4TriggerPin, fx5TriggerPin, fx6TriggerPin, fx7TriggerPin, fx8TriggerPin, fx9TriggerPin, fx10TriggerPin };
byte fxVolumeArray[] = { fxVolUpPin, fxVolDnPin };
#define fx0Trigger 0; #define fx1Trigger 1; #define fx2Trigger 2; #define fx3Trigger 3; #define fx4Trigger 4; #define fx5Trigger 5; #define fx6Trigger 6; #define fx7Trigger 7; #define fx8Trigger 8; #define fx9Trigger 9; #define fx10Trigger 10
#define fxVolUp 0; #define fxVolDn 1
signed int currentPlayingSound = -1;
byte btnPressed[5], ledState[5];
unsigned long previousMillisLedBlink = 0;
#define intervalLedBlink 750
unsigned long previousMillisGameStarting = 0;
#define intervalCountDown 1000
byte gameStartCountDown;
unsigned long previousMillisGameStep = 0;
int gameStepLength = 0, gameStepBtn = 0;
int valPotmeterVolume = 0, volume = 0, actualVolume = 35;
int valPotmeterGameLevel = 0, gameLevel, currentScore, highScore, highScoreAddr = 1;
byte activeBtn; bool debug; byte wackAMoleState;
#define stateUnknown 0; #define waitForStart 1; #define starting 2; #define running 3; #define gameOver 4; #define stopped 5
void setup() {
Serial.begin(9600);
pinMode(ampPower, OUTPUT); digitalWrite(ampPower, LOW);
for(int Pin=0;Pin<5;Pin++) pinMode(btnArray[Pin], INPUT);
for(int Pin=0;Pin<5;Pin++) pinMode(btnLedArray[Pin], OUTPUT);
for(int Pin=0;Pin<11;Pin++){ pinMode(fxTriggerArray[Pin], OUTPUT); digitalWrite(fxTriggerArray[Pin], HIGH); }
for(int Pin=0;Pin<2;Pin++){ pinMode(fxVolumeArray[Pin], OUTPUT); digitalWrite(fxVolumeArray[Pin], HIGH); }
matrix1.begin(0x70); matrix2.begin(0x71);
while(!eeprom_is_ready()); cli(); highScore = eeprom_read_word((uint16_t*)(gameLevel*2)); sei();
matrix1.print(0); matrix1.writeDisplay(); matrix2.print(0); matrix2.writeDisplay();
initializeSystem();
}
void loop() {
checkButtonInputs(); readVolumePotmeter();
if(wackAMoleState == waitForStart){ playSound(fx0Trigger); if(btnPressed[btn4]){ wackAMoleState = starting; } readGameLevelPotmeter(); ledBlinkStart(); }
if(wackAMoleState == starting){ startGame(); }
if(wackAMoleState == running){ playSound(fx1Trigger); checkNextGameStep(); }
}
void checkNextGameStep() {
unsigned long currentMillisGameStep = millis();
int amountOfButtonPresses = 0;
for(int btn=0;btn<5;btn++) if(btnPressed[btn]) amountOfButtonPresses++;
if(previousMillisGameStep == 0){
previousMillisGameStep = currentMillisGameStep;
digitalWrite(btnLedArray[btnLed4], LOW);
randomSeed(analogRead(2));
gameStepBtn = random(0,5);
gameStepLength = random(3000/gameLevel, 5000/gameLevel);
digitalWrite(btnLedArray[gameStepBtn], HIGH);
}
if((currentMillisGameStep - previousMillisGameStep >= gameStepLength) || (amountOfButtonPresses > 1) || (amountOfButtonPresses == 1 && !btnPressed[gameStepBtn])){
playSound(fx2Trigger); wackAMoleState = gameOver; previousMillisGameStep = 0;
for(int c=0;c<5;c++) analogWrite(btnLedArray[c], 255);
for(int b=255;b>0;b--){ for(int c=0;c<5;c++) analogWrite(btnLedArray[c], b); delay(10); }
for(int c=0;c<5;c++) analogWrite(btnLedArray[c], 0);
if(currentScore > highScore){ while(!eeprom_is_ready()); cli(); eeprom_write_word((uint16_t*)(gameLevel*2), currentScore); sei(); }
int counter=0; do{ delay(1); checkButtonInputs(); counter++; } while((counter<1000) && !btnPressed[btn1] && !btnPressed[btn2] && !btnPressed[btn3] && !btnPressed[btn4] && !btnPressed[btn5]);
matrix1.print(0); matrix1.writeDisplay(); wackAMoleState = waitForStart;
} else if(btnPressed[gameStepBtn] && amountOfButtonPresses == 1){
digitalWrite(btnLedArray[gameStepBtn], LOW);
int counter=0; do{ delay(1); checkButtonInputs(); counter++; } while((counter<1000) && btnPressed[gameStepBtn]);
previousMillisGameStep = currentMillisGameStep;
int tempStepBtn; do{ tempStepBtn = random(0,5); gameStepLength = random(3000/gameLevel, 5000/gameLevel); } while(gameStepBtn == tempStepBtn);
gameStepBtn = tempStepBtn;
digitalWrite(btnLedArray[gameStepBtn], HIGH);
currentScore++; matrix1.print(currentScore); matrix1.writeDisplay();
if(currentScore > highScore){ matrix2.print(currentScore); matrix2.writeDisplay(); }
}
}
void ledBlinkStart(){ unsigned long currentMillisLedBlink = millis(); if(currentMillisLedBlink - previousMillisLedBlink >= intervalLedBlink){ previousMillisLedBlink = currentMillisLedBlink; ledState[0] = (ledState[0]==LOW)?HIGH:LOW; digitalWrite(btnLedArray[btnLed4], ledState[0]); } }
void startGame(){ unsigned long currentMillisGameStarting = millis(); digitalWrite(btnLedArray[btnLed4], 0); currentScore = 0; if(gameStartCountDown == 0){ playSound(fx3Trigger); previousMillisGameStarting = millis(); gameStartCountDown = 4; delay(300); matrix1.print(gameStartCountDown, DEC); matrix1.writeDisplay(); } if(currentMillisGameStarting - previousMillisGameStarting >= intervalCountDown){ previousMillisGameStarting = currentMillisGameStarting; if(gameStartCountDown > 0){ gameStartCountDown--; matrix1.print(gameStartCountDown, DEC); matrix1.writeDisplay(); if(gameStartCountDown == 0) wackAMoleState = running; } } }
void initializeSystem(){ digitalWrite(btnLedArray[btnLed1], HIGH); delay(75); digitalWrite(btnLedArray[btnLed2], HIGH); delay(75); digitalWrite(btnLedArray[btnLed3], HIGH); delay(75); digitalWrite(btnLedArray[btnLed4], HIGH); delay(75); digitalWrite(btnLedArray[btnLed5], HIGH); delay(1000); for(int i=4;i>=0;i--){ digitalWrite(btnLedArray[i], LOW); delay(75); } delay(1000); debug = true; wackAMoleState = waitForStart; initVolume(); if(actualVolume > 0) enableAmpPower(true); }
void checkButtonInputs(){ static byte previousstate[5], currentstate[5]; static long lasttime; byte index; if((lasttime + debounceTime) > millis()) return; lasttime = millis(); for(index=0;index<5;index++){ currentstate[index] = digitalRead(btnArray[index]); if(currentstate[index] != btnPressed[index]){ if(btnPressed[index]==HIGH && currentstate[index]==LOW){ if(debug){ Serial.print("button released: "); Serial.println(index+1); } btnPressed[index]=0; } if(btnPressed[index]==LOW && currentstate[index]==HIGH){ if(debug){ Serial.print("button pressed: "); Serial.println(index+1); } btnPressed[index]=1; } } } }
void initVolume(){ valPotmeterVolume = (analogRead(potmeterVolume)/30); if(actualVolume > valPotmeterVolume){ do{ digitalWrite(fxVolumeArray[fxVolDn], LOW); actualVolume--; delay(20); digitalWrite(fxVolumeArray[fxVolDn], HIGH); delay(20); } while(actualVolume > valPotmeterVolume); } else if(actualVolume < valPotmeterVolume){ do{ digitalWrite(fxVolumeArray[fxVolUp], LOW); actualVolume++; delay(20); digitalWrite(fxVolumeArray[fxVolUp], HIGH); delay(20); } while(actualVolume < valPotmeterVolume); } }
void readVolumePotmeter(){ valPotmeterVolume = (analogRead(potmeterVolume)/30); if(actualVolume > valPotmeterVolume){ digitalWrite(fxVolumeArray[fxVolDn], LOW); actualVolume--; delay(40); digitalWrite(fxVolumeArray[fxVolDn], HIGH); if(actualVolume==0) enableAmpPower(false); } else if(actualVolume < valPotmeterVolume){ digitalWrite(fxVolumeArray[fxVolUp], LOW); if(actualVolume==0) enableAmpPower(true); actualVolume++; delay(40); digitalWrite(fxVolumeArray[fxVolUp], HIGH); } }
void readGameLevelPotmeter(){ valPotmeterGameLevel = analogRead(potmeterGameLevel); if(valPotmeterGameLevel < 102) gameLevel=1; else if(valPotmeterGameLevel < 204) gameLevel=2; else if(valPotmeterGameLevel < 306) gameLevel=3; else if(valPotmeterGameLevel < 408) gameLevel=4; else if(valPotmeterGameLevel < 510) gameLevel=5; else if(valPotmeterGameLevel < 612) gameLevel=6; else if(valPotmeterGameLevel < 714) gameLevel=7; else if(valPotmeterGameLevel < 816) gameLevel=8; else if(valPotmeterGameLevel < 918) gameLevel=9; else gameLevel=10; matrix1.print(gameLevel); matrix1.writeDisplay(); while(!eeprom_is_ready()); cli(); highScore = eeprom_read_word((uint16_t*)(gameLevel*2)); sei(); if(highScore > -1) matrix2.print(highScore); else matrix2.print(0); matrix2.writeDisplay(); }
void playSound(int sound){ if(currentPlayingSound != sound){ for(int c=0;c<11;c++) digitalWrite(fxTriggerArray[c], HIGH); digitalWrite(fxTriggerArray[sound], LOW); currentPlayingSound = sound; } }
void enableAmpPower(bool state){ digitalWrite(ampPower, state); }
自定义零件与外壳
激光切割 MDF:6mm OC9CvHjuTzsYL9POZCJt.dxf、9mm 1irkeqvO4ABzhKqhmyG7.dxf;3mm 亚克力 j4Tnmni6dQ0KXJtiHcy1.dxf
自制 Arduino Mega 扩展板 5yiRX1RdQuPX0gNXjYkm.zip
3D 打印件:功放座×2、Mega 固定座、后接口面板座、后接口面板、线缆扣(任意数量)、数码管支架×2、数码管锁扣×4、数码管上框×2、顶盖后座、顶盖前座×2、顶盖侧座×2
原理图

制造工艺
- 纽扣:从古老扣件到现代创新
- 树莓派 + PIR 传感器 + MQTT 打造 AWS IoT 运动触发按钮
- UnifiedWater v1 — 基于 Arduino MKR GSM 1400 的完整水质监测套件
- 便携式 GSM 投票箱:基于 Hologram + IFTTT 实现云端数据采集
- 现代复古点唱机:Raspberry Pi + Arduino NeoPixel 声控灯光音乐播放器
- DIY Arduino 智能手表:打造属于你的 Apple Watch 风格腕表
- 如何选择靠谱的 PCB 打样厂商:全维度评估指南
- 带锯最优锯切速度选择实用指南
- 选对潜水泵:专家建议与关键特性指南
- 选择立式车削中心进行精密加工的核心理由
- 如何为设备选择合适的仪表旋钮
- 圆头内六角螺丝选购实战指南