本帖最后由 豆爸 于 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 开发板,面向智能家居和广泛物联网场景,集高性能计算、多协议支持与智能电源管理于一体,为各种部署需求提供高可靠性、高灵活性与长续航的解决方案。 2、Fermion: 10位AD按键板
10位AD按键板可以作为开发板的AD按键输入,通过一个模拟口就可以扩展出10个按键,大幅度节省开发板的IO扣。同时还可以通过两边的焊盘,连接其他按键,适应项目结构。 3、 Rravity OLED-12864 显示屏
Rravity OLED-12864 显示屏是一款无需背景光源,自发光式的显示模块。模块采用蓝色背景,显示尺寸控制在0.96英寸,采用OLED专用驱动芯片SSD1306控制。该模块支持通过I2C接口与控制器通信,支持高传输速率,能够实现60Hz的刷新频率。 三、软件介绍
2、安装ESP32开发板卡软件包
选择开发板esp32 by Espressif Systems v3.3.0-alpha1-cn版本进行安装。 3、安装库:
1. U8g2lib.h:OLED 显示屏驱动库,支持多种分辨率与通信方式,提供丰富的图形绘制接口(如线段、圆形、文字); 四、主要代码及说明
1. 游戏状态机模块
这部分代码负责管理游戏的状态流转,是整个游戏的核心控制逻辑:
- // 游戏状态定义
- enum GameState { START_SCREEN, PLAYING, GAME_OVER };
- GameState gameState = START_SCREEN;
-
- // 状态处理函数
- void loop() {
- // 读取按键
- int key = readADCKey();
-
- // 处理按键
- if (key != -1) {
- handleKeyPress(key);
- }
-
- // 更新游戏状态
- if (gameState == PLAYING) {
- updateGame();
- }
-
- // 绘制游戏界面
- drawGame();
-
- delay(50); // 短暂延迟以减少闪烁
- }
-
- void handleKeyPress(int key) {
- switch (gameState) {
- case START_SCREEN:
- if (key == 0) { // K0 - 开始游戏
- startGame();
- }
- break;
-
- case PLAYING:
- if (key >= 1 && key <= 9) { // K1-K9 - 打地鼠
- handleMoleHit(key);
- }
- break;
-
- case GAME_OVER:
- if (key == 0) { // K0 - 重新开始游戏
- resetGame();
- gameState = START_SCREEN;
- }
- break;
- }
-
- // 标记按键已处理
- keyProcessed = true;
- }
复制代码
核心说明:
- 使用枚举类型GameState定义了游戏的三种状态:启动界面、游戏中、游戏结束
- 在主循环loop()中,根据当前状态执行相应的逻辑
- handleKeyPress()函数根据不同游戏状态处理按键输入,实现状态间的切换
- 这种状态机设计使游戏逻辑清晰,各状态间的耦合度低,便于维护和扩展
2. 地鼠逻辑模块
这部分代码实现了地鼠的生成、超时检测和击中判断:
- // 地鼠相关变量
- int currentMole = -1; // 使用 1-9 表示位置,-1 表示没有地鼠
- unsigned long moleStartTime = 0;
- unsigned long moleDuration = 0;
- int roundsPlayed = 0;
- int totalMoles = 0;
- int hitMoles = 0;
-
- void updateGame() {
- // 检查地鼠是否需要消失
- checkMoleTimeout();
-
- // 如果没有地鼠,生成新的地鼠
- if (currentMole == -1 && roundsPlayed < MAX_GAME_ROUNDS) {
- spawnNewMole();
- }
- }
-
- void spawnNewMole() {
- // 随机选择新的地鼠位置 (1-9)
- currentMole = random(1, 10);
-
- // 随机设置地鼠显示时间(1500-2500毫秒)
- moleDuration = random(1500, 2500);
- moleStartTime = millis();
-
- totalMoles++;
- }
-
- void checkMoleTimeout() {
- if (currentMole != -1 && (millis() - moleStartTime) > moleDuration) {
- currentMole = -1;
-
- // 增加已玩回合数(即使没有击中)
- roundsPlayed++;
-
- // 检查游戏是否结束
- if (roundsPlayed >= MAX_GAME_ROUNDS) {
- gameState = GAME_OVER;
- }
- }
- }
-
- void handleMoleHit(int position) {
- // 检查是否击中地鼠
- if (currentMole != -1 && position == currentMole) {
- hitMoles++;
- currentMole = -1;
-
- // 增加已玩回合数
- roundsPlayed++;
-
- // 检查游戏是否结束
- if (roundsPlayed >= MAX_GAME_ROUNDS) {
- gameState = GAME_OVER;
- }
- }
- }
复制代码
核心说明:
- spawnNewMole()函数随机生成地鼠位置和显示时间,增加游戏的不确定性
- checkMoleTimeout()函数通过millis()实现非阻塞式计时,检测地鼠是否超时
- handleMoleHit()函数判断玩家是否击中地鼠,并更新游戏状态和分数
- 使用currentMole变量跟踪当前地鼠位置,-1 表示当前没有地鼠
3. 输入处理模块
这部分代码实现了 ADC 键盘的读取和防抖处理:
- // ADC键盘参数
- const int ADC_PIN = 2; // ADC键盘连接引脚
- const int DEBOUNCE_DELAY = 50; // 防抖时间
- const int ADC_TOLERANCE = 100; // ADC值容差范围
-
- // 按键ADC值(K0-K9)
- const int keyValues[] = {5, 407, 743, 985, 1360, 1759, 2005, 2347, 2755, 3060};
- const int KEY_COUNT = sizeof(keyValues) / sizeof(keyValues[0]);
-
- // 输入状态变量
- int lastKey = -1;
- unsigned long lastKeyTime = 0;
- bool keyProcessed = true;
-
- int readADCKey() {
- int adcValue = analogRead(ADC_PIN);
- unsigned long currentTime = millis();
-
- // 防抖处理
- if (currentTime - lastKeyTime < DEBOUNCE_DELAY) {
- return -1;
- }
-
- // 查找匹配的按键
- int detectedKey = -1;
- int minDifference = 10000; // 很大的初始值
-
- for (int i = 0; i < KEY_COUNT; i++) {
- int difference = abs(adcValue - keyValues[i]);
- if (difference <= ADC_TOLERANCE && difference < minDifference) {
- detectedKey = i;
- minDifference = difference;
- }
- }
-
- // 按键状态处理
- if (detectedKey == -1) {
- if (lastKey != -1) {
- // 按键释放
- lastKey = -1;
- keyProcessed = true;
- }
- return -1;
- }
-
- // 检测到新按键
- if (detectedKey == lastKey && !keyProcessed) {
- return -1; // 同一个按键且未处理,避免重复触发
- }
-
- // 更新按键状态
- lastKey = detectedKey;
- lastKeyTime = currentTime;
- keyProcessed = false;
-
- return detectedKey;
- }
复制代码
核心说明:
- 采用 ADC 方式读取矩阵键盘,只需一个 ADC 引脚即可识别多个按键
- 实现了软件防抖处理,通过DEBOUNCE_DELAY过滤按键抖动
- 使用容差范围ADC_TOLERANCE处理硬件差异和环境干扰导致的 ADC 值波动
- 通过keyProcessed标记确保每个按键按下只被处理一次
4. 显示模块
这部分代码实现了游戏界面的绘制,包括网格、地鼠和分数等:
- void drawGame() {
- u8g2.clearBuffer();
-
- switch (gameState) {
- case START_SCREEN:
- drawStartScreen();
- break;
- case PLAYING:
- drawPlayingScreen();
- break;
- case GAME_OVER:
- drawGameOverScreen();
- break;
- }
-
- u8g2.sendBuffer();
- }
-
- void drawPlayingScreen() {
- // 绘制网格
- drawGrid();
-
- // 如果有地鼠,绘制地鼠
- if (currentMole != -1) {
- drawMole(currentMole);
- }
-
- // 绘制分数
- drawScore();
-
- // 绘制游戏进度
- u8g2.setFont(u8g2_font_6x10_tf);
- u8g2.setCursor(0, GRID_SIZE * CELL_HEIGHT + 36);
- u8g2.print("回合: ");
- u8g2.print(roundsPlayed);
- u8g2.print("/");
- u8g2.print(MAX_GAME_ROUNDS);
- }
-
- void drawMole(int position) {
- // 将 1-9 的位置转换为 0-8 的行列索引
- int pos = position - 1; // 转换为 0-8
- int row = pos / GRID_SIZE;
- int col = pos % GRID_SIZE;
-
- int centerX = col * CELL_WIDTH + CELL_WIDTH / 2;
- int centerY = row * CELL_HEIGHT + CELL_HEIGHT / 2;
-
- // 绘制地鼠(简单的圆形)
- u8g2.drawDisc(centerX, centerY, MOLE_RADIUS);
-
- // 绘制眼睛(两个小圆点)
- u8g2.drawDisc(centerX - 2, centerY - 2, 1);
- u8g2.drawDisc(centerX + 2, centerY - 2, 1);
-
- // 绘制鼻子(小圆点)
- u8g2.drawDisc(centerX, centerY + 1, 1);
- }
复制代码
核心说明:
- 使用 U8g2 库实现 OLED 屏幕的绘制功能,支持图形和文字显示
- drawGame()函数根据当前游戏状态调用不同的界面绘制函数
- drawMole()函数将地鼠位置 (1-9) 转换为屏幕坐标,并绘制简单的地鼠图形
- 采用分层绘制策略:先绘制网格,再绘制地鼠,最后绘制分数和状态信息
这些核心模块相互配合,构成了完整的打地鼠游戏系统。每个模块职责明确,通过变量和函数调用进行交互,使整个代码结构清晰,易于理解和维护。
五、效果演示
1、游戏开始效果
2、游戏进行中效果
3、游戏结束效果
六、项目总结与展望
1、项目总结
本项目成功实现了基于 ESP32-C5 的打地鼠游戏,完成了所有核心功能:
1. 硬件层面:实现了 OLED(I2C)与 ADC 键盘的稳定通信,引脚配置合理;
2. 软件层面:采用状态机设计降低模块耦合,游戏逻辑清晰(地鼠生成、击中判断、分数统计);
3. 交互层面:界面友好,操作提示明确,实时反馈游戏状态,用户体验流畅。 通过项目实践,深入掌握了 ESP32 外设(ADC、I2C)应用、嵌入式状态机设计与 OLED 图形绘制,解决了按键防抖、中文显示等实际问题。
2、改进方向
1. 难度递增机制:随回合数增加,地鼠显示时间逐渐缩短,提升游戏挑战性;
2. 声音反馈:增加蜂鸣器模块,击中地鼠时发出提示音,增强交互体验;
3. 高分记录功能:使用 ESP32 的 EEPROM 存储历史最高分,游戏结束后对比并更新;
附件:
Whac_A_Mole.zip
|