2025-10-11 16:41:59 [显示全部楼层]
23浏览
查看: 23|回复: 0

[ESP8266/ESP32] FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏

[复制链接]
本帖最后由 豆爸 于 2025-10-11 21:48 编辑

一、项目概述

1、项目背景

本项目以 ESP32-C5 开发板 为核心,结合 128×64 OLED 显示屏与 ADC 矩阵键盘,开发一款经典的 “打地鼠” 游戏。项目旨在通过实践掌握以下技术:

  • ESP32-C5 的 GPIO 配置、I2C 通信(OLED 驱动)与 ADC 外设应用(键盘读取);
  • 入式系统中的 状态机设计(游戏启动、运行、结束三状态切换);
  • 图形化界面(OLED)的动态绘制与交互逻辑实现;
  • 实时事件处理(按键防抖、地鼠限时显示、击中判断)。


2、功能简介


本项目实现了完整的打地鼠游戏流程,核心功能包括:
1. 多状态界面切换:启动界面(操作提示)→ 游戏界面(网格、地鼠、分数)→ 结束界面(成绩统计);
2. 随机地鼠生成:3×3 网格中随机生成地鼠,限时 1.5~2.5 秒显示;
3. 按键交互:通过 ADC 键盘(K0~K9)控制游戏(K0 启动 / 重启,K1~K9 对应网格位置打地鼠);
4. 成绩统计:实时计算击中数、总地鼠数与准确率,游戏结束后展示最终成绩;
5. 回合制游戏:固定 10 回合,回合结束自动进入游戏结束界面。

二、硬件介绍

1、FireBeetle 2 ESP32-C5开发板

FireBeetle 2  ESP32-C5 是一款搭载乐鑫 ESP32-C5 模组的低功耗 IoT 开发板,面向智能家居和广泛物联网场景,集高性能计算、多协议支持与智能电源管理于一体,为各种部署需求提供高可靠性、高灵活性与长续航的解决方案。
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图7
2、Fermion: 10位AD按键板

10位AD按键板可以作为开发板的AD按键输入,通过一个模拟口就可以扩展出10个按键,大幅度节省开发板的IO扣。同时还可以通过两边的焊盘,连接其他按键,适应项目结构。
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图6
3、 Rravity OLED-12864 显示屏

Rravity OLED-12864 显示屏是一款无需背景光源,自发光式的显示模块。模块采用蓝色背景,显示尺寸控制在0.96英寸,采用OLED专用驱动芯片SSD1306控制。该模块支持通过I2C接口与控制器通信,支持高传输速率,能够实现60Hz的刷新频率。
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图8
三、软件介绍
1、开发工具:Arduino IDE2.3.6

从官网下载并安装Arduino IDE 2.3.6版本,https://downloads.arduino.cc/ard ... 6_Windows_64bit.exe

2、安装ESP32开发板卡软件包

打开Arduino IDE,进入文件 -> 首选项,在附加开发板管理器网址中输入:https://jihulab.com/esp-mirror/e ... esp32_index_cn.json
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图1
选择开发板esp32 by Espressif Systems v3.3.0-alpha1-cn版本进行安装。
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图10
3、安装库:

1. U8g2lib.h:OLED 显示屏驱动库,支持多种分辨率与通信方式,提供丰富的图形绘制接口(如线段、圆形、文字);
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图9
四、主要代码及说明

1. 游戏状态机模块

这部分代码负责管理游戏的状态流转,是整个游戏的核心控制逻辑:
  1. // 游戏状态定义
  2. enum GameState { START_SCREEN, PLAYING, GAME_OVER };
  3. GameState gameState = START_SCREEN;
  4. // 状态处理函数
  5. void loop() {
  6.   // 读取按键
  7.   int key = readADCKey();
  8.   
  9.   // 处理按键
  10.   if (key != -1) {
  11.     handleKeyPress(key);
  12.   }
  13.   
  14.   // 更新游戏状态
  15.   if (gameState == PLAYING) {
  16.     updateGame();
  17.   }
  18.   
  19.   // 绘制游戏界面
  20.   drawGame();
  21.   
  22.   delay(50); // 短暂延迟以减少闪烁
  23. }
  24. void handleKeyPress(int key) {
  25.   switch (gameState) {
  26.     case START_SCREEN:
  27.       if (key == 0) { // K0 - 开始游戏
  28.         startGame();
  29.       }
  30.       break;
  31.       
  32.     case PLAYING:
  33.       if (key >= 1 && key <= 9) { // K1-K9 - 打地鼠
  34.         handleMoleHit(key);
  35.       }
  36.       break;
  37.       
  38.     case GAME_OVER:
  39.       if (key == 0) { // K0 - 重新开始游戏
  40.         resetGame();
  41.         gameState = START_SCREEN;
  42.       }
  43.       break;
  44.   }
  45.   
  46.   // 标记按键已处理
  47.   keyProcessed = true;
  48. }
复制代码
核心说明:
  • 使用枚举类型GameState定义了游戏的三种状态:启动界面、游戏中、游戏结束
  • 在主循环loop()中,根据当前状态执行相应的逻辑
  • handleKeyPress()函数根据不同游戏状态处理按键输入,实现状态间的切换
  • 这种状态机设计使游戏逻辑清晰,各状态间的耦合度低,便于维护和扩展

2. 地鼠逻辑模块

这部分代码实现了地鼠的生成、超时检测和击中判断:
  1. // 地鼠相关变量
  2. int currentMole = -1;  // 使用 1-9 表示位置,-1 表示没有地鼠
  3. unsigned long moleStartTime = 0;
  4. unsigned long moleDuration = 0;
  5. int roundsPlayed = 0;
  6. int totalMoles = 0;
  7. int hitMoles = 0;
  8. void updateGame() {
  9.   // 检查地鼠是否需要消失
  10.   checkMoleTimeout();
  11.   
  12.   // 如果没有地鼠,生成新的地鼠
  13.   if (currentMole == -1 && roundsPlayed < MAX_GAME_ROUNDS) {
  14.     spawnNewMole();
  15.   }
  16. }
  17. void spawnNewMole() {
  18.   // 随机选择新的地鼠位置 (1-9)
  19.   currentMole = random(1, 10);
  20.   
  21.   // 随机设置地鼠显示时间(1500-2500毫秒)
  22.   moleDuration = random(1500, 2500);
  23.   moleStartTime = millis();
  24.   
  25.   totalMoles++;
  26. }
  27. void checkMoleTimeout() {
  28.   if (currentMole != -1 && (millis() - moleStartTime) > moleDuration) {
  29.     currentMole = -1;
  30.    
  31.     // 增加已玩回合数(即使没有击中)
  32.     roundsPlayed++;
  33.    
  34.     // 检查游戏是否结束
  35.     if (roundsPlayed >= MAX_GAME_ROUNDS) {
  36.       gameState = GAME_OVER;
  37.     }
  38.   }
  39. }
  40. void handleMoleHit(int position) {
  41.   // 检查是否击中地鼠
  42.   if (currentMole != -1 && position == currentMole) {
  43.     hitMoles++;
  44.     currentMole = -1;
  45.    
  46.     // 增加已玩回合数
  47.     roundsPlayed++;
  48.    
  49.     // 检查游戏是否结束
  50.     if (roundsPlayed >= MAX_GAME_ROUNDS) {
  51.       gameState = GAME_OVER;
  52.     }
  53.   }
  54. }
复制代码
核心说明:
  • spawnNewMole()函数随机生成地鼠位置和显示时间,增加游戏的不确定性
  • checkMoleTimeout()函数通过millis()实现非阻塞式计时,检测地鼠是否超时
  • handleMoleHit()函数判断玩家是否击中地鼠,并更新游戏状态和分数
  • 使用currentMole变量跟踪当前地鼠位置,-1 表示当前没有地鼠

3. 输入处理模块

这部分代码实现了 ADC 键盘的读取和防抖处理:
  1. // ADC键盘参数
  2. const int ADC_PIN = 2;              // ADC键盘连接引脚
  3. const int DEBOUNCE_DELAY = 50;      // 防抖时间
  4. const int ADC_TOLERANCE  = 100;     // ADC值容差范围
  5. // 按键ADC值(K0-K9)
  6. const int keyValues[] = {5, 407, 743, 985, 1360, 1759, 2005, 2347, 2755, 3060};
  7. const int KEY_COUNT = sizeof(keyValues) / sizeof(keyValues[0]);
  8. // 输入状态变量
  9. int lastKey = -1;
  10. unsigned long lastKeyTime = 0;
  11. bool keyProcessed = true;
  12. int readADCKey() {
  13.   int adcValue = analogRead(ADC_PIN);
  14.   unsigned long currentTime = millis();
  15.   
  16.   // 防抖处理
  17.   if (currentTime - lastKeyTime < DEBOUNCE_DELAY) {
  18.     return -1;
  19.   }
  20.   
  21.   // 查找匹配的按键
  22.   int detectedKey = -1;
  23.   int minDifference = 10000; // 很大的初始值
  24.   
  25.   for (int i = 0; i < KEY_COUNT; i++) {
  26.     int difference = abs(adcValue - keyValues[i]);
  27.     if (difference <= ADC_TOLERANCE && difference < minDifference) {
  28.       detectedKey = i;
  29.       minDifference = difference;
  30.     }
  31.   }
  32.   
  33.   // 按键状态处理
  34.   if (detectedKey == -1) {
  35.     if (lastKey != -1) {
  36.       // 按键释放
  37.       lastKey = -1;
  38.       keyProcessed = true;
  39.     }
  40.     return -1;
  41.   }
  42.   
  43.   // 检测到新按键
  44.   if (detectedKey == lastKey && !keyProcessed) {
  45.     return -1; // 同一个按键且未处理,避免重复触发
  46.   }
  47.   
  48.   // 更新按键状态
  49.   lastKey = detectedKey;
  50.   lastKeyTime = currentTime;
  51.   keyProcessed = false;
  52.   
  53.   return detectedKey;
  54. }
复制代码
核心说明:
  • 采用 ADC 方式读取矩阵键盘,只需一个 ADC 引脚即可识别多个按键
  • 实现了软件防抖处理,通过DEBOUNCE_DELAY过滤按键抖动
  • 使用容差范围ADC_TOLERANCE处理硬件差异和环境干扰导致的 ADC 值波动
  • 通过keyProcessed标记确保每个按键按下只被处理一次

4. 显示模块

这部分代码实现了游戏界面的绘制,包括网格、地鼠和分数等:
  1. void drawGame() {
  2.   u8g2.clearBuffer();
  3.   
  4.   switch (gameState) {
  5.     case START_SCREEN:
  6.       drawStartScreen();
  7.       break;
  8.     case PLAYING:
  9.       drawPlayingScreen();
  10.       break;
  11.     case GAME_OVER:
  12.       drawGameOverScreen();
  13.       break;
  14.   }
  15.   
  16.   u8g2.sendBuffer();
  17. }
  18. void drawPlayingScreen() {
  19.   // 绘制网格
  20.   drawGrid();
  21.   
  22.   // 如果有地鼠,绘制地鼠
  23.   if (currentMole != -1) {
  24.     drawMole(currentMole);
  25.   }
  26.   
  27.   // 绘制分数
  28.   drawScore();
  29.   
  30.   // 绘制游戏进度
  31.   u8g2.setFont(u8g2_font_6x10_tf);
  32.   u8g2.setCursor(0, GRID_SIZE * CELL_HEIGHT + 36);
  33.   u8g2.print("回合: ");
  34.   u8g2.print(roundsPlayed);
  35.   u8g2.print("/");
  36.   u8g2.print(MAX_GAME_ROUNDS);
  37. }
  38. void drawMole(int position) {
  39.   // 将 1-9 的位置转换为 0-8 的行列索引
  40.   int pos = position - 1; // 转换为 0-8
  41.   int row = pos / GRID_SIZE;
  42.   int col = pos % GRID_SIZE;
  43.   
  44.   int centerX = col * CELL_WIDTH + CELL_WIDTH / 2;
  45.   int centerY = row * CELL_HEIGHT + CELL_HEIGHT / 2;
  46.   
  47.   // 绘制地鼠(简单的圆形)
  48.   u8g2.drawDisc(centerX, centerY, MOLE_RADIUS);
  49.   
  50.   // 绘制眼睛(两个小圆点)
  51.   u8g2.drawDisc(centerX - 2, centerY - 2, 1);
  52.   u8g2.drawDisc(centerX + 2, centerY - 2, 1);
  53.   
  54.   // 绘制鼻子(小圆点)
  55.   u8g2.drawDisc(centerX, centerY + 1, 1);
  56. }
复制代码
核心说明:
  • 使用 U8g2 库实现 OLED 屏幕的绘制功能,支持图形和文字显示
  • drawGame()函数根据当前游戏状态调用不同的界面绘制函数
  • drawMole()函数将地鼠位置 (1-9) 转换为屏幕坐标,并绘制简单的地鼠图形
  • 采用分层绘制策略:先绘制网格,再绘制地鼠,最后绘制分数和状态信息

这些核心模块相互配合,构成了完整的打地鼠游戏系统。每个模块职责明确,通过变量和函数调用进行交互,使整个代码结构清晰,易于理解和维护。


五、效果演示


1、游戏开始效果
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图2

2、游戏进行中效果
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图3

3、游戏结束效果
FireBeetle 2 ESP32-C5基于AD Key按键的打地鼠游戏图4

六、项目总结与展望
1、项目总结

本项目成功实现了基于 ESP32-C5 的打地鼠游戏,完成了所有核心功能:
1. 硬件层面:实现了 OLED(I2C)与 ADC 键盘的稳定通信,引脚配置合理;
2. 软件层面:采用状态机设计降低模块耦合,游戏逻辑清晰(地鼠生成、击中判断、分数统计);
3. 交互层面:界面友好,操作提示明确,实时反馈游戏状态,用户体验流畅。  通过项目实践,深入掌握了 ESP32 外设(ADC、I2C)应用、嵌入式状态机设计与 OLED 图形绘制,解决了按键防抖、中文显示等实际问题。
  
2、改进方向

1. 难度递增机制:随回合数增加,地鼠显示时间逐渐缩短,提升游戏挑战性;
2. 声音反馈:增加蜂鸣器模块,击中地鼠时发出提示音,增强交互体验;
3. 高分记录功能:使用 ESP32 的 EEPROM 存储历史最高分,游戏结束后对比并更新;

附件:
下载附件Whac_A_Mole.zip






您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail