📖 项目简介
在这个充满创意的项目中,我使用 ESP32-C6 微控制器打造了一个完整的掌上游戏机系统。这不仅仅是一个简单的游戏演示,而是一个功能完备的游戏平台,包含:
🎮 10 款经典游戏:从贪吃蛇到俄罗斯方块,应有尽有
🎵 独立配乐系统:每款游戏都有专属的背景音乐
🏆 双难度模式:简单和困难模式满足不同玩家需求
💾 最高分记录:自动保存每款游戏的最佳成绩
🎨 精美UI界面:流畅的菜单动画和用户体验
🔧 硬件配置
核心组件
| 组件 | 型号/规格 | 用途 |
|------|-----------|------|
| 主控 | ESP32-C6 | 系统核心处理器 |
| 显示屏 | SSD1306 OLED (128x64) | 游戏画面显示 |
| 输入 | 双轴摇杆 + 按键 | 游戏控制 |
| 音频 | 无源蜂鸣器 | 音效和背景音乐 |
| 通信 | I2C总线 | OLED屏幕通信 |
引脚连接
```cpp
#define BUZZER_PIN 8 // 蜂鸣器引脚
#define JOYSTICK_X 1 // 摇杆X轴 (ADC)
#define JOYSTICK_Y 2 // 摇杆Y轴 (ADC)
#define JOYSTICK_SW 3 // 摇杆按键
I2C: SDA=21, SCL=22 // OLED显示屏
🎮 游戏阵容
1. Snake (贪吃蛇)🐍
经典的贪吃蛇游戏,控制蛇吃食物变长,避免撞墙和自己。
音乐:超级马里奥主题
难度差异:困难模式速度更快
2. Runner (跑酷)🏃
横版跑酷游戏,按时跳跃避开障碍物。
音乐:俄罗斯方块旋律
特色:动态地面效果
3. Flappy Bird (飞扬小鸟)🐦
点击控制小鸟飞行,穿越管道间隙。
音乐:塞尔达传说风格
物理:真实的重力系统
4. Breakout (打砖块)🧱
使用挡板反弹小球击碎所有砖块。
音乐:神庙逃亡风格
特点:40块砖墙 (10x4)
5. Tetris (俄罗斯方块)🟦
经典的方块消除游戏,7种不同形状的方块。
音乐:吃豆人旋律
功能:完整的旋转和消行系统
6. Space Invaders (太空入侵者)👾
射击外星人保卫地球!
音乐:太空入侵者原版音效
敌人:3排x8列共24个外星人
7. Pong (乒乓)🏓
对战AI的经典乒乓球游戏。
音乐:简约电子音
AI:困难模式的AI更加聪明
8. Maze (迷宫)🌀
随机生成的迷宫探险游戏。
音乐:迷宫探险风格
特色:每次游戏地图都不同
9. Racing (赛车)🏎️
躲避道路上的其他车辆,尽可能跑得更远。
音乐:赛车狂飙节奏
效果:流动的道路边缘动画
10. Catcher (接星星)⭐
移动篮子接住下落的星星,不能失误超过5次。
音乐:轻快的上升音阶
挑战:同时5颗星星下落
💡 核心技术特性
1. 状态机架构
系统使用清晰的状态机管理不同界面:
```cpp
enum GameState {
SPLASH_SCREEN, // 启动画面
MAIN_MENU, // 主菜单
DIFFICULTY_SELECT, // 难度选择
GAME_SNAKE, // 游戏1-10
// ... 其他游戏状态
GAME_OVER_SCREEN, // 游戏结束
HIGH_SCORE_SCREEN, // 高分榜
SETTINGS_SCREEN // 设置界面
};
2. 音乐系统
每款游戏都配备独立的背景音乐,使用音符频率和时长数组:
```cpp
struct MusicNote {
int freq; // 频率 (Hz)
int dur; // 时长 (ms)
};
音乐循环播放,不阻塞游戏主循环,保证流畅的游戏体验。
3. 数据持久化
使用 ESP32 的 `Preferences` 库保存:
✅ 每款游戏的简单模式最高分 (10个)
✅ 每款游戏的困难模式最高分 (10个)
✅ 音效开关状态
```cpp
preferences.begin("gameBox", false);
preferences.putInt("hs0", score); // 保存分数
preferences.putBool("sound", true); // 保存设置
```
4. 输入防抖处理
实现了时间阈值的输入防抖,避免误操作:
```cpp
const int INPUT_DELAY = 180; // 毫秒
if (now - lastInputTime > INPUT_DELAY) {
// 处理输入
lastInputTime = now;
}
5. 精美的UI设计
启动动画
渐进式加载效果
进度条显示
音效配合
主菜单
正弦波动的标题动画
滚动的游戏列表
实时显示最高分
游戏界面
帧率优化的动画效果
粒子效果和闪烁动画
实时分数显示
🎯 开发亮点
1. 内存优化
对于ESP32-C6的内存管理至关重要:
✅ 使用 `byte` 类型存储网格数据 (Tetris, Maze)
✅ 固定大小的数组避免动态分配
✅ 复用显示缓冲区
2. 性能优化
✅ 不同游戏采用不同的更新频率
✅ 音乐播放采用非阻塞实现
✅ 碰撞检测使用早期退出优化
3. 代码结构
✅ 清晰的函数命名和模块划分
✅ 每个游戏独立的 `init` 和 `update` 函数
✅ 统一的游戏调度系统
```cpp
void initGame() {
switch(currentState) {
case GAME_SNAKE: initSnake(); break;
// ...
}
}
void updateGame() {
switch(currentState) {
case GAME_SNAKE: updateSnake(); break;
// ...
}
}
```
🚀 快速上手
环境准备
1. Arduino IDE(这里我使用的是Arduino IDE) 或 PlatformIO
2. 安装依赖库:
```
- Adafruit_GFX
- Adafruit_SSD1306
- Preferences (ESP32内置)
```
烧录步骤
1. 打开 Arduino IDE
2. 选择开发板:`ESP32C6 Dev Module`
3. 配置参数:
- Flash Size: 4MB
- Partition Scheme: Default
4. 连接ESP32-C6并上传代码
项目完整代码
/*
* ╔═══════════════════════════════════════════════════════════════╗
* ║ ESP32-C6 专业游戏合集系统 v2.0 ║
* ║ 10个完整游戏 + 双难度 + 独立配乐 + 最高分系统 ║
* ║ 作者: XIU YUAN ║
* ║ 日期:2025-10-14 ║
* ╚═══════════════════════════════════════════════════════════════╝
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
// ═══════════════════════════════════════════════════════════════
// 硬件配置
// ═══════════════════════════════════════════════════════════════
#define BUZZER_PIN 8
#define JOYSTICK_X 1
#define JOYSTICK_Y 2
#define JOYSTICK_SW 3
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
Preferences preferences;
// ═══════════════════════════════════════════════════════════════
// 音符频率定义
// ═══════════════════════════════════════════════════════════════
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define REST 0
// ═══════════════════════════════════════════════════════════════
// 游戏状态
// ═══════════════════════════════════════════════════════════════
enum GameState {
SPLASH_SCREEN,
MAIN_MENU,
DIFFICULTY_SELECT,
GAME_SNAKE,
GAME_RUNNER,
GAME_FLAPPY,
GAME_BREAKOUT,
GAME_TETRIS,
GAME_INVADERS,
GAME_PONG,
GAME_MAZE,
GAME_RACING,
GAME_CATCHER,
GAME_OVER_SCREEN,
HIGH_SCORE_SCREEN,
SETTINGS_SCREEN,
PAUSE_SCREEN
};
GameState currentState = SPLASH_SCREEN;
GameState pausedFrom = MAIN_MENU;
// ═══════════════════════════════════════════════════════════════
// 全局变量
// ═══════════════════════════════════════════════════════════════
const int GAME_COUNT = 10;
int menuSelection = 0;
int difficultySelection = 0;
bool isDifficultMode = false;
bool soundEnabled = true;
int currentScore = 0;
int currentGameIndex = 0;
const char* gameNames[] = {
"Snake", "Runner", "Flappy", "Breakout", "Tetris",
"Invaders", "Pong", "Maze", "Racing", "Catcher"
};
int highScores[20]; // [0-9]简单, [10-19]困难
unsigned long lastInputTime = 0;
unsigned long musicTimer = 0;
const int INPUT_DELAY = 180;
// ═══════════════════════════════════════════════════════════════
// 音乐系统
// ═══════════════════════════════════════════════════════════════
struct MusicNote {
int freq;
int dur;
};
int musicIndex = 0;
MusicNote* currentMusic = nullptr;
int currentMusicLen = 0;
// 超级马里奥主题
MusicNote music1[] = {
{NOTE_E5,150},{NOTE_E5,150},{REST,150},{NOTE_E5,150},{REST,150},
{NOTE_C5,150},{NOTE_E5,150},{REST,150},{NOTE_G5,300},{REST,300}
};
int music1_len = 10;
// 俄罗斯方块
MusicNote music2[] = {
{NOTE_E5,200},{NOTE_B4,100},{NOTE_C5,100},{NOTE_D5,200},{NOTE_C5,100},{NOTE_B4,100},
{NOTE_A4,200},{NOTE_A4,100},{NOTE_C5,100},{NOTE_E5,200},{NOTE_D5,100},{NOTE_C5,100},
{NOTE_B4,300},{NOTE_C5,100},{NOTE_D5,200},{NOTE_E5,200}
};
int music2_len = 16;
// 塞尔达
MusicNote music3[] = {
{NOTE_A4,200},{NOTE_D5,400},{NOTE_F5,200},{NOTE_G5,150},{NOTE_A5,200},
{NOTE_AS5,200},{NOTE_G5,300},{NOTE_F5,400}
};
int music3_len = 8;
// 神庙逃亡
MusicNote music4[] = {
{NOTE_C5,150},{NOTE_D5,150},{NOTE_E5,150},{NOTE_G5,300},
{NOTE_E5,150},{NOTE_D5,150},{NOTE_C5,300}
};
int music4_len = 7;
// 吃豆人
MusicNote music5[] = {
{NOTE_B4,100},{NOTE_B5,100},{NOTE_FS5,100},{NOTE_DS5,100},
{NOTE_B5,200},{NOTE_FS5,200},{NOTE_DS5,100},{NOTE_C5,100}
};
int music5_len = 8;
// 太空入侵者
MusicNote music6[] = {
{NOTE_D4,100},{NOTE_D4,100},{NOTE_D4,100},{NOTE_D4,100},
{NOTE_A3,100},{NOTE_A3,100},{NOTE_A3,100},{NOTE_A3,100}
};
int music6_len = 8;
// Pong简约音
MusicNote music7[] = {
{NOTE_C5,200},{NOTE_G4,200},{NOTE_C5,200},{REST,200}
};
int music7_len = 4;
// 迷宫探险
MusicNote music8[] = {
{NOTE_E4,200},{NOTE_G4,200},{NOTE_A4,200},{NOTE_C5,400},{REST,200}
};
int music8_len = 5;
// 赛车狂飙
MusicNote music9[] = {
{NOTE_G5,100},{NOTE_E5,100},{NOTE_C5,100},{NOTE_G4,100},
{NOTE_G5,100},{NOTE_E5,100},{NOTE_C5,100},{NOTE_G4,100}
};
int music9_len = 8;
// 接星星
MusicNote music10[] = {
{NOTE_C5,150},{NOTE_D5,150},{NOTE_E5,150},{NOTE_F5,150},{NOTE_G5,150},{NOTE_A5,150}
};
int music10_len = 6;
void setMusic(int gameIdx) {
musicIndex = 0;
switch(gameIdx) {
case 0: currentMusic = music1; currentMusicLen = music1_len; break;
case 1: currentMusic = music2; currentMusicLen = music2_len; break;
case 2: currentMusic = music3; currentMusicLen = music3_len; break;
case 3: currentMusic = music4; currentMusicLen = music4_len; break;
case 4: currentMusic = music5; currentMusicLen = music5_len; break;
case 5: currentMusic = music6; currentMusicLen = music6_len; break;
case 6: currentMusic = music7; currentMusicLen = music7_len; break;
case 7: currentMusic = music8; currentMusicLen = music8_len; break;
case 8: currentMusic = music9; currentMusicLen = music9_len; break;
case 9: currentMusic = music10; currentMusicLen = music10_len; break;
}
}
void playMusic() {
if (!soundEnabled || currentMusic == nullptr) return;
unsigned long now = millis();
if (now - musicTimer >= currentMusic[musicIndex].dur) {
ledcWriteTone(BUZZER_PIN, 0);
musicIndex = (musicIndex + 1) % currentMusicLen;
if (currentMusic[musicIndex].freq != REST) {
ledcWriteTone(BUZZER_PIN, currentMusic[musicIndex].freq);
}
musicTimer = now;
}
}
void stopMusic() {
ledcWriteTone(BUZZER_PIN, 0);
currentMusic = nullptr;
}
void playTone(int f, int d) {
if (!soundEnabled) return;
ledcWriteTone(BUZZER_PIN, f);
delay(d);
ledcWriteTone(BUZZER_PIN, 0);
}
void beepSelect() { playTone(NOTE_E5, 40); }
void beepStart() { playTone(NOTE_C5, 60); playTone(NOTE_E5, 60); playTone(NOTE_G5, 100); }
void beepScore() { playTone(NOTE_G5, 50); playTone(NOTE_A5, 50); }
void beepGameOver() { playTone(NOTE_E4,150); playTone(NOTE_C4,150); playTone(NOTE_A3,300); }
// ═══════════════════════════════════════════════════════════════
// 输入系统
// ═══════════════════════════════════════════════════════════════
int readJoystick() {
int x = analogRead(JOYSTICK_X);
int y = analogRead(JOYSTICK_Y);
if (x > 3000) return 0; // 右
if (x < 1000) return 2; // 左
if (y > 3000) return 3; // 上
if (y < 1000) return 1; // 下
return -1;
}
bool btnPressed() {
static bool last = false;
bool now = digitalRead(JOYSTICK_SW) == LOW;
if (now && !last) {
last = true;
return true;
}
if (!now) last = false;
return false;
}
// ═══════════════════════════════════════════════════════════════
// 数据存储
// ═══════════════════════════════════════════════════════════════
void loadScores() {
preferences.begin("gameBox", false);
for (int i = 0; i < 20; i++) {
char key[10];
sprintf(key, "hs%d", i);
highScores[i] = preferences.getInt(key, 0);
}
soundEnabled = preferences.getBool("sound", true);
preferences.end();
}
void saveScore(int gameIdx, bool hard, int score) {
int idx = gameIdx + (hard ? 10 : 0);
if (score > highScores[idx]) {
highScores[idx] = score;
preferences.begin("gameBox", false);
char key[10];
sprintf(key, "hs%d", idx);
preferences.putInt(key, score);
preferences.end();
}
}
void saveSound() {
preferences.begin("gameBox", false);
preferences.putBool("sound", soundEnabled);
preferences.end();
}
int getHighScore(int gameIdx, bool hard) {
return highScores[gameIdx + (hard ? 10 : 0)];
}
// ═══════════════════════════════════════════════════════════════
// UI工具
// ═══════════════════════════════════════════════════════════════
void drawBorder() {
display.drawRect(0, 0, OLED_WIDTH, OLED_HEIGHT, SSD1306_WHITE);
}
void drawTitle(const char* txt, int y = 8) {
display.setTextSize(2);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h);
display.setCursor((OLED_WIDTH - w) / 2, y);
display.print(txt);
}
void drawCenter(const char* txt, int y, int sz = 1) {
display.setTextSize(sz);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h);
display.setCursor((OLED_WIDTH - w) / 2, y);
display.print(txt);
}
void drawProgress(int x, int y, int w, int h, int val, int max) {
display.drawRect(x, y, w, h, SSD1306_WHITE);
int fill = (val * (w - 2)) / max;
if (fill > 0) display.fillRect(x + 1, y + 1, fill, h - 2, SSD1306_WHITE);
}
// ═══════════════════════════════════════════════════════════════
// 启动画面
// ═══════════════════════════════════════════════════════════════
void showSplash() {
static int frame = 0;
if (frame == 0) {
display.clearDisplay();
drawBorder();
drawTitle("GAME", 12);
drawTitle("BOX", 30);
display.display();
playTone(NOTE_C5, 80);
} else if (frame == 8) {
display.setTextSize(1);
drawCenter("v2.0 Professional", 50, 1);
display.display();
playTone(NOTE_E5, 80);
} else if (frame == 16) {
drawCenter("Loading...", 58, 1);
display.display();
} else if (frame > 16 && frame < 36) {
drawProgress(15, 56, 98, 6, frame - 16, 20);
display.display();
} else if (frame >= 36) {
currentState = MAIN_MENU;
playTone(NOTE_G5, 100);
frame = -1;
}
frame++;
delay(50);
}
// ═══════════════════════════════════════════════════════════════
// 主菜单
// ═══════════════════════════════════════════════════════════════
void handleMainMenu() {
display.clearDisplay();
drawBorder();
// 标题动画
display.setTextSize(1);
int titleY = 3 + sin(millis() / 200.0);
display.setCursor(8, titleY);
display.print("< SELECT GAME >");
// 游戏列表
int start = max(0, menuSelection - 2);
int end = min(GAME_COUNT, start + 5);
for (int i = start; i < end; i++) {
int y = 14 + (i - start) * 10;
if (i == menuSelection) {
display.fillRoundRect(2, y - 1, OLED_WIDTH - 4, 9, 2, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
} else {
display.setTextColor(SSD1306_WHITE);
}
display.setCursor(6, y);
display.print(i + 1);
display.print(".");
display.print(gameNames[i]);
// 最高分
int hs = getHighScore(i, false);
display.setCursor(90, y);
if (hs > 0) display.print(hs);
}
display.setTextColor(SSD1306_WHITE);
display.drawLine(0, 57, OLED_WIDTH, 57, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(2, 58);
display.print("<Scores Set>");
display.display();
// 输入
int dir = readJoystick();
unsigned long now = millis();
if (now - lastInputTime > INPUT_DELAY) {
if (dir == 3) {
menuSelection = (menuSelection - 1 + GAME_COUNT) % GAME_COUNT;
beepSelect();
lastInputTime = now;
} else if (dir == 1) {
menuSelection = (menuSelection + 1) % GAME_COUNT;
beepSelect();
lastInputTime = now;
} else if (dir == 2) {
currentState = HIGH_SCORE_SCREEN;
beepSelect();
lastInputTime = now;
} else if (dir == 0) {
currentState = SETTINGS_SCREEN;
beepSelect();
lastInputTime = now;
}
}
if (btnPressed()) {
currentGameIndex = menuSelection;
currentState = DIFFICULTY_SELECT;
beepStart();
delay(200);
}
}
// ═══════════════════════════════════════════════════════════════
// 难度选择
// ═══════════════════════════════════════════════════════════════
void handleDifficulty() {
display.clearDisplay();
drawBorder();
display.setTextSize(1);
drawCenter(gameNames[currentGameIndex], 6, 1);
display.drawLine(10, 15, OLED_WIDTH - 10, 15, SSD1306_WHITE);
drawCenter("SELECT DIFFICULTY", 20, 1);
// 简单
if (difficultySelection == 0) {
display.fillRoundRect(12, 30, 104, 11, 2, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
} else {
display.drawRoundRect(12, 30, 104, 11, 2, SSD1306_WHITE);
display.setTextColor(SSD1306_WHITE);
}
drawCenter("EASY MODE", 32, 1);
// 困难
display.setTextColor(SSD1306_WHITE);
if (difficultySelection == 1) {
display.fillRoundRect(12, 43, 104, 11, 2, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
} else {
display.drawRoundRect(12, 43, 104, 11, 2, SSD1306_WHITE);
display.setTextColor(SSD1306_WHITE);
}
drawCenter("HARD MODE", 45, 1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 58);
display.print("Press to start");
display.display();
// 输入
int dir = readJoystick();
if (millis() - lastInputTime > INPUT_DELAY) {
if (dir == 3 || dir == 1) {
difficultySelection = 1 - difficultySelection;
beepSelect();
lastInputTime = millis();
}
}
if (btnPressed()) {
isDifficultMode = (difficultySelection == 1);
currentScore = 0;
currentState = (GameState)(GAME_SNAKE + currentGameIndex);
setMusic(currentGameIndex);
// 初始化游戏
initGame();
beepStart();
delay(300);
}
}
// ═══════════════════════════════════════════════════════════════
// 游戏结束
// ═══════════════════════════════════════════════════════════════
void handleGameOver() {
stopMusic();
int hs = getHighScore(currentGameIndex, isDifficultMode);
bool newRecord = (currentScore > hs);
if (newRecord) {
saveScore(currentGameIndex, isDifficultMode, currentScore);
}
display.clearDisplay();
drawBorder();
drawTitle("GAME", 5);
drawTitle("OVER", 23);
display.setTextSize(1);
display.setCursor(10, 42);
display.print("Score:");
display.setCursor(70, 42);
display.print(currentScore);
display.setCursor(10, 50);
display.print("Best:");
display.setCursor(70, 50);
display.print(newRecord ? currentScore : hs);
if (newRecord) {
drawCenter("NEW RECORD!", 58, 1);
} else {
drawCenter("Press to menu", 58, 1);
}
display.display();
if (btnPressed()) {
delay(300);
currentState = MAIN_MENU;
}
}
// ═══════════════════════════════════════════════════════════════
// 高分榜
// ═══════════════════════════════════════════════════════════════
void handleHighScores() {
static int page = 0;
display.clearDisplay();
drawBorder();
display.setTextSize(1);
drawCenter("HIGH SCORES", 4, 1);
display.drawLine(5, 13, OLED_WIDTH - 5, 13, SSD1306_WHITE);
// 表头
display.setCursor(5, 16);
display.print("Game");
display.setCursor(70, 16);
display.print("Easy");
display.setCursor(100, 16);
display.print("Hard");
// 数据
int start = page * 4;
for (int i = 0; i < 4 && (start + i) < GAME_COUNT; i++) {
int idx = start + i;
int y = 26 + i * 9;
display.setCursor(5, y);
display.print(gameNames[idx]);
display.setCursor(70, y);
int easy = getHighScore(idx, false);
if (easy > 0) display.print(easy);
else display.print("-");
display.setCursor(100, y);
int hard = getHighScore(idx, true);
if (hard > 0) display.print(hard);
else display.print("-");
}
display.drawLine(5, 56, OLED_WIDTH - 5, 56, SSD1306_WHITE);
display.setCursor(5, 58);
display.print("U/D:Pg ");
display.print(page + 1);
display.print("/3 B:Back");
display.display();
// 输入
int dir = readJoystick();
if (millis() - lastInputTime > INPUT_DELAY) {
if (dir == 3 && page > 0) {
page--;
beepSelect();
lastInputTime = millis();
} else if (dir == 1 && page < 2) {
page++;
beepSelect();
lastInputTime = millis();
}
}
if (btnPressed()) {
currentState = MAIN_MENU;
page = 0;
beepSelect();
delay(200);
}
}
// ═══════════════════════════════════════════════════════════════
// 设置
// ═══════════════════════════════════════════════════════════════
void handleSettings() {
static int sel = 0;
display.clearDisplay();
drawBorder();
drawTitle("SETTINGS", 6);
display.drawLine(10, 24, OLED_WIDTH - 10, 24, SSD1306_WHITE);
display.setTextSize(1);
// 声音
if (sel == 0) {
display.fillRect(10, 28, 108, 10, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
} else {
display.setTextColor(SSD1306_WHITE);
}
display.setCursor(15, 30);
display.print("Sound: ");
display.print(soundEnabled ? "ON" : "OFF");
// 清除分数
display.setTextColor(SSD1306_WHITE);
if (sel == 1) {
display.fillRect(10, 40, 108, 10, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
}
display.setCursor(15, 42);
display.print("Clear Scores");
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 56);
display.print("U/D:Sel B:Menu");
display.display();
// 输入
int dir = readJoystick();
if (millis() - lastInputTime > INPUT_DELAY) {
if (dir == 3 || dir == 1) {
sel = 1 - sel;
beepSelect();
lastInputTime = millis();
} else if (dir == 0 || dir == 2) {
if (sel == 0) {
soundEnabled = !soundEnabled;
saveSound();
beepSelect();
}
lastInputTime = millis();
}
}
if (btnPressed()) {
if (sel == 1) {
// 清除分数确认
display.clearDisplay();
drawBorder();
drawCenter("Clear all scores?", 25, 1);
drawCenter("Press again", 38, 1);
display.display();
delay(1000);
if (btnPressed()) {
preferences.begin("gameBox", false);
for (int i = 0; i < 20; i++) {
char key[10];
sprintf(key, "hs%d", i);
preferences.putInt(key, 0);
highScores[i] = 0;
}
preferences.end();
beepSelect();
delay(500);
}
} else {
currentState = MAIN_MENU;
beepSelect();
delay(200);
}
}
}
// ═══════════════════════════════════════════════════════════════
// 游戏 1: 贪吃蛇
// ═══════════════════════════════════════════════════════════════
struct Point { int x, y; };
Point snake[100];
int snakeLen;
Point food;
int snakeDir;
bool snakeOver;
unsigned long snakeTime;
void initSnake() {
snakeLen = 3;
snakeDir = 0;
snakeOver = false;
currentScore = 0;
snakeTime = millis();
snake[0] = {60, 30};
snake[1] = {57, 30};
snake[2] = {54, 30};
food.x = random(0, OLED_WIDTH / 3) * 3;
food.y = random(0, OLED_HEIGHT / 3) * 3;
}
void updateSnake() {
if (snakeOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
// 控制
int dir = readJoystick();
int speed = isDifficultMode ? 80 : 130;
if (dir != -1 && millis() - lastInputTime > 100) {
if ((dir == 0 && snakeDir != 2) || (dir == 2 && snakeDir != 0) ||
(dir == 1 && snakeDir != 3) || (dir == 3 && snakeDir != 1)) {
snakeDir = dir;
lastInputTime = millis();
}
}
// 移动
if (millis() - snakeTime > speed) {
for (int i = snakeLen - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
switch (snakeDir) {
case 0: snake[0].x += 3; break;
case 1: snake[0].y += 3; break;
case 2: snake[0].x -= 3; break;
case 3: snake[0].y -= 3; break;
}
// 边界
if (snake[0].x < 0 || snake[0].x >= OLED_WIDTH || snake[0].y < 0 || snake[0].y >= OLED_HEIGHT) {
snakeOver = true;
return;
}
// 自撞
for (int i = 1; i < snakeLen; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
snakeOver = true;
return;
}
}
// 吃食物
if (snake[0].x == food.x && snake[0].y == food.y) {
snakeLen++;
currentScore += 10;
food.x = random(0, OLED_WIDTH / 3) * 3;
food.y = random(0, OLED_HEIGHT / 3) * 3;
beepScore();
}
snakeTime = millis();
}
// 绘制
display.clearDisplay();
// 蛇
for (int i = 0; i < snakeLen; i++) {
if (i == 0) {
display.fillRect(snake[i].x, snake[i].y, 3, 3, SSD1306_WHITE);
} else {
display.drawRect(snake[i].x, snake[i].y, 3, 3, SSD1306_WHITE);
}
}
// 食物闪烁
if (millis() % 400 < 200) {
display.fillRect(food.x, food.y, 3, 3, SSD1306_WHITE);
}
// 分数
display.setTextSize(1);
display.setCursor(0, 0);
display.print("S:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 2: 跑酷
// ═══════════════════════════════════════════════════════════════
int runY;
int runVel;
bool runJump;
int runObsX;
int runObsH;
bool runOver;
unsigned long runTime;
void initRunner() {
runY = 50;
runVel = 0;
runJump = false;
runObsX = OLED_WIDTH;
runObsH = random(8, 16);
runOver = false;
currentScore = 0;
runTime = millis();
}
void updateRunner() {
if (runOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 25 : 40;
if (millis() - runTime < delay_ms) return;
runTime = millis();
// 跳跃
if (btnPressed() && !runJump && runY >= 50) {
runVel = -8;
runJump = true;
playTone(NOTE_C5, 30);
}
// 物理
runVel++;
runY += runVel;
if (runY >= 50) {
runY = 50;
runVel = 0;
runJump = false;
}
if (runY < 10) {
runY = 10;
runVel = 1;
}
// 障碍物
runObsX -= (isDifficultMode ? 3 : 2);
if (runObsX < -10) {
runObsX = OLED_WIDTH;
runObsH = random(8, 16);
currentScore += 10;
beepScore();
}
// 碰撞
if (runObsX >= 8 && runObsX <= 18 && runY + 8 >= 58 - runObsH) {
runOver = true;
return;
}
// 绘制
display.clearDisplay();
// 地面
display.drawLine(0, 58, OLED_WIDTH, 58, SSD1306_WHITE);
for (int i = 0; i < OLED_WIDTH; i += 8) {
display.drawPixel((i + (millis() / 50) % 8), 59, SSD1306_WHITE);
}
// 玩家
display.fillRect(10, runY, 8, 8, SSD1306_WHITE);
// 障碍
display.fillRect(runObsX, 58 - runObsH, 8, runObsH, SSD1306_WHITE);
// 分数
display.setTextSize(1);
display.setCursor(0, 0);
display.print("S:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 3: Flappy Bird
// ═══════════════════════════════════════════════════════════════
int birdY;
int birdVel;
int pipeX;
int pipeGap;
int pipeY;
bool flappyOver;
unsigned long flappyTime;
void initFlappy() {
birdY = 32;
birdVel = 0;
pipeX = OLED_WIDTH;
pipeGap = isDifficultMode ? 20 : 25;
pipeY = random(10, 40);
flappyOver = false;
currentScore = 0;
flappyTime = millis();
}
void updateFlappy() {
if (flappyOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 30 : 45;
if (millis() - flappyTime < delay_ms) return;
flappyTime = millis();
// 飞行
if (btnPressed()) {
birdVel = -3;
playTone(NOTE_E5, 25);
delay(80);
}
// 物理
birdVel++;
birdY += birdVel;
// 边界
if (birdY < 0 || birdY > OLED_HEIGHT - 5) {
flappyOver = true;
return;
}
// 管道移动
pipeX -= 2;
if (pipeX < -10) {
pipeX = OLED_WIDTH;
pipeY = random(10, 40);
currentScore += 10;
beepScore();
}
// 碰撞
if (pipeX >= 18 && pipeX <= 30) {
if (birdY < pipeY || birdY > pipeY + pipeGap) {
flappyOver = true;
return;
}
}
// 绘制
display.clearDisplay();
// 小鸟
display.fillCircle(24, birdY, 3, SSD1306_WHITE);
display.drawPixel(26, birdY - 1, SSD1306_WHITE);
// 管道
display.fillRect(pipeX, 0, 10, pipeY, SSD1306_WHITE);
display.fillRect(pipeX, pipeY + pipeGap, 10, OLED_HEIGHT, SSD1306_WHITE);
// 分数
display.setTextSize(1);
display.setCursor(0, 0);
display.print("S:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 4: 打砖块
// ═══════════════════════════════════════════════════════════════
int paddleX;
int ballX, ballY;
int ballDX, ballDY;
bool bricks[10][4];
bool breakoutOver;
unsigned long breakoutTime;
void initBreakout() {
paddleX = OLED_WIDTH / 2 - 10;
ballX = OLED_WIDTH / 2;
ballY = 50;
ballDX = 2;
ballDY = -2;
breakoutOver = false;
currentScore = 0;
breakoutTime = millis();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 4; j++) {
bricks[i][j] = true;
}
}
}
void updateBreakout() {
if (breakoutOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 25 : 35;
if (millis() - breakoutTime < delay_ms) return;
breakoutTime = millis();
// 挡板控制
int x = analogRead(JOYSTICK_X);
if (x > 3000) paddleX += (isDifficultMode ? 5 : 4);
if (x < 1000) paddleX -= (isDifficultMode ? 5 : 4);
paddleX = constrain(paddleX, 0, OLED_WIDTH - 20);
// 球移动
ballX += ballDX;
ballY += ballDY;
// 边界
if (ballX <= 0 || ballX >= OLED_WIDTH) ballDX = -ballDX;
if (ballY <= 0) ballDY = -ballDY;
// 挡板反弹
if (ballY >= OLED_HEIGHT - 6 && ballY <= OLED_HEIGHT - 3 &&
ballX >= paddleX && ballX <= paddleX + 20) {
ballDY = -ballDY;
playTone(NOTE_C5, 25);
}
// 掉落
if (ballY >= OLED_HEIGHT) {
breakoutOver = true;
return;
}
// 砖块碰撞
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 4; j++) {
if (bricks[i][j]) {
int bx = i * 13;
int by = j * 6;
if (ballX >= bx && ballX <= bx + 12 && ballY >= by && ballY <= by + 5) {
bricks[i][j] = false;
ballDY = -ballDY;
currentScore += 10;
beepScore();
}
}
}
}
// 绘制
display.clearDisplay();
// 砖块
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 4; j++) {
if (bricks[i][j]) {
display.fillRect(i * 13, j * 6, 12, 5, SSD1306_WHITE);
}
}
}
// 挡板
display.fillRect(paddleX, OLED_HEIGHT - 3, 20, 3, SSD1306_WHITE);
// 球
display.fillCircle(ballX, ballY, 2, SSD1306_WHITE);
// 分数
display.setTextSize(1);
display.setCursor(0, 56);
display.print("S:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 5: 俄罗斯方块
// ═══════════════════════════════════════════════════════════════
const int GRID_W = 10;
const int GRID_H = 16;
const int BLOCK_SIZE = 4;
byte grid[GRID_H][GRID_W];
int tetromino[4][4];
int tetX, tetY;
int tetType;
bool tetrisOver;
unsigned long tetrisTime;
int tetrisSpeed;
const byte shapes[7][4][4] = {
{{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}, // I
{{1,1,0,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}}, // O
{{0,1,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}}, // S
{{1,0,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}}, // Z
{{1,0,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}}, // T
{{1,0,0,0},{1,0,0,0},{1,1,0,0},{0,0,0,0}}, // L
{{0,1,0,0},{0,1,0,0},{1,1,0,0},{0,0,0,0}} // J
};
void newTetromino() {
tetType = random(7);
tetX = 3;
tetY = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
tetromino[i][j] = shapes[tetType][i][j];
}
}
// 检查游戏结束
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tetromino[i][j] && grid[tetY + i][tetX + j]) {
tetrisOver = true;
}
}
}
}
bool checkCollision(int dx, int dy) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tetromino[i][j]) {
int nx = tetX + j + dx;
int ny = tetY + i + dy;
if (nx < 0 || nx >= GRID_W || ny >= GRID_H) return true;
if (ny >= 0 && grid[ny][nx]) return true;
}
}
}
return false;
}
void lockTetromino() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tetromino[i][j] && tetY + i >= 0) {
grid[tetY + i][tetX + j] = 1;
}
}
}
// 消行
int lines = 0;
for (int i = GRID_H - 1; i >= 0; i--) {
bool full = true;
for (int j = 0; j < GRID_W; j++) {
if (!grid[i][j]) {
full = false;
break;
}
}
if (full) {
lines++;
for (int k = i; k > 0; k--) {
for (int j = 0; j < GRID_W; j++) {
grid[k][j] = grid[k - 1][j];
}
}
for (int j = 0; j < GRID_W; j++) {
grid[0][j] = 0;
}
i++;
}
}
if (lines > 0) {
currentScore += lines * 100;
beepScore();
}
newTetromino();
}
void rotateTetromino() {
int temp[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
temp[i][j] = tetromino[3 - j][i];
}
}
// 临时保存
int old[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
old[i][j] = tetromino[i][j];
tetromino[i][j] = temp[i][j];
}
}
// 检查碰撞
if (checkCollision(0, 0)) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
tetromino[i][j] = old[i][j];
}
}
} else {
playTone(NOTE_E5, 30);
}
}
void initTetris() {
for (int i = 0; i < GRID_H; i++) {
for (int j = 0; j < GRID_W; j++) {
grid[i][j] = 0;
}
}
tetrisOver = false;
currentScore = 0;
tetrisSpeed = isDifficultMode ? 300 : 500;
tetrisTime = millis();
newTetromino();
}
void updateTetris() {
if (tetrisOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
// 控制
int dir = readJoystick();
if (millis() - lastInputTime > 150) {
if (dir == 2 && !checkCollision(-1, 0)) {
tetX--;
lastInputTime = millis();
} else if (dir == 0 && !checkCollision(1, 0)) {
tetX++;
lastInputTime = millis();
} else if (dir == 1) {
tetrisSpeed = 50;
} else if (dir == 3) {
rotateTetromino();
lastInputTime = millis();
}
}
if (btnPressed()) {
rotateTetromino();
delay(150);
}
// 下落
if (millis() - tetrisTime > tetrisSpeed) {
if (!checkCollision(0, 1)) {
tetY++;
} else {
lockTetromino();
}
tetrisTime = millis();
tetrisSpeed = isDifficultMode ? 300 : 500;
}
// 绘制
display.clearDisplay();
// 网格边框
display.drawRect(0, 0, GRID_W * 6 + 2, OLED_HEIGHT, SSD1306_WHITE);
// 固定方块
for (int i = 0; i < GRID_H; i++) {
for (int j = 0; j < GRID_W; j++) {
if (grid[i][j]) {
display.fillRect(j * 6 + 1, i * 4, 5, 3, SSD1306_WHITE);
}
}
}
// 当前方块
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tetromino[i][j] && tetY + i >= 0) {
display.fillRect((tetX + j) * 6 + 1, (tetY + i) * 4, 5, 3, SSD1306_WHITE);
}
}
}
// 分数
display.setTextSize(1);
display.setCursor(65, 20);
display.print("Score");
display.setCursor(65, 30);
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 6: 太空入侵者
// ═══════════════════════════════════════════════════════════════
int shipX;
int bulletX, bulletY;
bool bulletActive;
int aliens[3][8];
int alienX, alienY;
int alienDX;
bool invadersOver;
unsigned long invadersTime;
void initInvaders() {
shipX = OLED_WIDTH / 2;
bulletActive = false;
alienX = 0;
alienY = 10;
alienDX = 1;
invadersOver = false;
currentScore = 0;
invadersTime = millis();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 8; j++) {
aliens[i][j] = 1;
}
}
}
void updateInvaders() {
if (invadersOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 40 : 60;
if (millis() - invadersTime < delay_ms) return;
invadersTime = millis();
// 飞船控制
int x = analogRead(JOYSTICK_X);
if (x > 3000) shipX += 3;
if (x < 1000) shipX -= 3;
shipX = constrain(shipX, 0, OLED_WIDTH - 8);
// 发射子弹
if (btnPressed() && !bulletActive) {
bulletX = shipX + 4;
bulletY = OLED_HEIGHT - 10;
bulletActive = true;
playTone(NOTE_E5, 30);
delay(100);
}
// 子弹移动
if (bulletActive) {
bulletY -= 3;
if (bulletY < 0) {
bulletActive = false;
}
// 碰撞检测
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 8; j++) {
if (aliens[i][j]) {
int ax = alienX + j * 12;
int ay = alienY + i * 10;
if (bulletX >= ax && bulletX <= ax + 8 && bulletY >= ay && bulletY <= ay + 6) {
aliens[i][j] = 0;
bulletActive = false;
currentScore += 10;
beepScore();
}
}
}
}
}
// 外星人移动
alienX += alienDX;
if (alienX <= 0 || alienX >= 32) {
alienDX = -alienDX;
alienY += 5;
}
// 外星人到达底部
if (alienY >= OLED_HEIGHT - 20) {
invadersOver = true;
return;
}
// 检查胜利
bool anyAlive = false;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 8; j++) {
if (aliens[i][j]) anyAlive = true;
}
}
if (!anyAlive) {
invadersOver = true;
currentScore += 100;
return;
}
// 绘制
display.clearDisplay();
// 外星人
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 8; j++) {
if (aliens[i][j]) {
int ax = alienX + j * 12;
int ay = alienY + i * 10;
display.fillRect(ax, ay, 8, 6, SSD1306_WHITE);
}
}
}
// 飞船
display.fillRect(shipX, OLED_HEIGHT - 8, 8, 6, SSD1306_WHITE);
display.drawPixel(shipX + 4, OLED_HEIGHT - 9, SSD1306_WHITE);
// 子弹
if (bulletActive) {
display.fillRect(bulletX, bulletY, 2, 4, SSD1306_WHITE);
}
// 分数
display.setTextSize(1);
display.setCursor(0, 0);
display.print("S:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 7: Pong
// ═══════════════════════════════════════════════════════════════
int paddle1Y, paddle2Y;
int pongBallX, pongBallY;
int pongDX, pongDY;
int score1, score2;
bool pongOver;
unsigned long pongTime;
void initPong() {
paddle1Y = OLED_HEIGHT / 2 - 8;
paddle2Y = OLED_HEIGHT / 2 - 8;
pongBallX = OLED_WIDTH / 2;
pongBallY = OLED_HEIGHT / 2;
pongDX = 2;
pongDY = 1;
score1 = 0;
score2 = 0;
pongOver = false;
currentScore = 0;
pongTime = millis();
}
void updatePong() {
if (pongOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 25 : 35;
if (millis() - pongTime < delay_ms) return;
pongTime = millis();
// 玩家控制
int y = analogRead(JOYSTICK_Y);
if (y > 3000) paddle1Y += 3;
if (y < 1000) paddle1Y -= 3;
paddle1Y = constrain(paddle1Y, 0, OLED_HEIGHT - 16);
// AI控制
if (pongBallY < paddle2Y + 8) paddle2Y -= (isDifficultMode ? 2 : 1);
if (pongBallY > paddle2Y + 8) paddle2Y += (isDifficultMode ? 2 : 1);
paddle2Y = constrain(paddle2Y, 0, OLED_HEIGHT - 16);
// 球移动
pongBallX += pongDX;
pongBallY += pongDY;
// 上下边界
if (pongBallY <= 0 || pongBallY >= OLED_HEIGHT) {
pongDY = -pongDY;
playTone(NOTE_C5, 30);
}
// 左挡板
if (pongBallX <= 5 && pongBallY >= paddle1Y && pongBallY <= paddle1Y + 16) {
pongDX = -pongDX;
playTone(NOTE_E5, 30);
}
// 右挡板
if (pongBallX >= OLED_WIDTH - 5 && pongBallY >= paddle2Y && pongBallY <= paddle2Y + 16) {
pongDX = -pongDX;
playTone(NOTE_E5, 30);
}
// 得分
if (pongBallX < 0) {
score2++;
pongBallX = OLED_WIDTH / 2;
pongBallY = OLED_HEIGHT / 2;
pongDX = 2;
}
if (pongBallX > OLED_WIDTH) {
score1++;
currentScore += 10;
pongBallX = OLED_WIDTH / 2;
pongBallY = OLED_HEIGHT / 2;
pongDX = -2;
beepScore();
}
// 游戏结束
if (score2 >= 5) {
pongOver = true;
return;
}
// 绘制
display.clearDisplay();
// 中线
for (int i = 0; i < OLED_HEIGHT; i += 4) {
display.drawPixel(OLED_WIDTH / 2, i, SSD1306_WHITE);
}
// 挡板
display.fillRect(2, paddle1Y, 3, 16, SSD1306_WHITE);
display.fillRect(OLED_WIDTH - 5, paddle2Y, 3, 16, SSD1306_WHITE);
// 球
display.fillCircle(pongBallX, pongBallY, 2, SSD1306_WHITE);
// 分数
display.setTextSize(1);
display.setCursor(OLED_WIDTH / 2 - 20, 2);
display.print(score1);
display.setCursor(OLED_WIDTH / 2 + 15, 2);
display.print(score2);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 8: 迷宫
// ═══════════════════════════════════════════════════════════════
const int MAZE_W = 21;
const int MAZE_H = 16;
byte maze[MAZE_H][MAZE_W];
int playerMX, playerMY;
int exitMX, exitMY;
bool mazeOver;
unsigned long mazeTime;
void generateMaze() {
// 简单迷宫生成
for (int i = 0; i < MAZE_H; i++) {
for (int j = 0; j < MAZE_W; j++) {
if (i == 0 || i == MAZE_H - 1 || j == 0 || j == MAZE_W - 1) {
maze[i][j] = 1;
} else if (i % 2 == 0 && j % 2 == 0) {
maze[i][j] = random(2);
} else {
maze[i][j] = random(3) == 0 ? 1 : 0;
}
}
}
// 确保起点和终点可达
playerMX = 1;
playerMY = 1;
maze[1][1] = 0;
exitMX = MAZE_W - 2;
exitMY = MAZE_H - 2;
maze[exitMY][exitMX] = 0;
}
void initMaze() {
generateMaze();
mazeOver = false;
currentScore = 0;
mazeTime = millis();
}
void updateMaze() {
if (mazeOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
// 控制
int dir = readJoystick();
if (millis() - lastInputTime > 150) {
int nx = playerMX;
int ny = playerMY;
if (dir == 0) nx++;
if (dir == 2) nx--;
if (dir == 1) ny++;
if (dir == 3) ny--;
if (nx >= 0 && nx < MAZE_W && ny >= 0 && ny < MAZE_H && maze[ny][nx] == 0) {
playerMX = nx;
playerMY = ny;
currentScore++;
playTone(NOTE_C5, 20);
lastInputTime = millis();
}
}
// 到达终点
if (playerMX == exitMX && playerMY == exitMY) {
mazeOver = true;
currentScore += 500;
beepScore();
return;
}
// 绘制
display.clearDisplay();
// 迷宫
for (int i = 0; i < MAZE_H; i++) {
for (int j = 0; j < MAZE_W; j++) {
if (maze[i][j]) {
display.drawPixel(j * 6, i * 4, SSD1306_WHITE);
}
}
}
// 玩家
display.fillRect(playerMX * 6 - 1, playerMY * 4 - 1, 3, 3, SSD1306_WHITE);
// 终点闪烁
if (millis() % 500 < 250) {
display.fillRect(exitMX * 6 - 1, exitMY * 4 - 1, 3, 3, SSD1306_WHITE);
}
// 分数
display.setTextSize(1);
display.setCursor(0, 0);
display.print("M:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 9: 赛车
// ═══════════════════════════════════════════════════════════════
int carX;
int road[4];
int roadY[4];
bool racingOver;
unsigned long racingTime;
void initRacing() {
carX = OLED_WIDTH / 2 - 4;
for (int i = 0; i < 4; i++) {
road[i] = random(20, OLED_WIDTH - 28);
roadY[i] = -i * 20;
}
racingOver = false;
currentScore = 0;
racingTime = millis();
}
void updateRacing() {
if (racingOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 30 : 45;
if (millis() - racingTime < delay_ms) return;
racingTime = millis();
// 控制
int x = analogRead(JOYSTICK_X);
if (x > 3000) carX += 3;
if (x < 1000) carX -= 3;
carX = constrain(carX, 10, OLED_WIDTH - 18);
// 道路移动
for (int i = 0; i < 4; i++) {
roadY[i] += (isDifficultMode ? 3 : 2);
if (roadY[i] > OLED_HEIGHT) {
roadY[i] = -16;
road[i] = random(20, OLED_WIDTH - 28);
currentScore += 10;
if (currentScore % 50 == 0) beepScore();
}
// 碰撞
if (roadY[i] >= OLED_HEIGHT - 20 && roadY[i] <= OLED_HEIGHT - 5) {
if (carX >= road[i] - 8 && carX <= road[i] + 8) {
racingOver = true;
return;
}
}
}
// 绘制
display.clearDisplay();
// 道路边缘
for (int i = 0; i < OLED_HEIGHT; i += 8) {
display.drawPixel(5, i + (millis() / 50) % 8, SSD1306_WHITE);
display.drawPixel(OLED_WIDTH - 6, i + (millis() / 50) % 8, SSD1306_WHITE);
}
// 障碍车
for (int i = 0; i < 4; i++) {
display.fillRect(road[i], roadY[i], 8, 12, SSD1306_WHITE);
}
// 玩家车
display.fillRect(carX, OLED_HEIGHT - 12, 8, 12, SSD1306_WHITE);
// 分数
display.setTextSize(1);
display.setCursor(0, 0);
display.print("S:");
display.print(currentScore);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏 10: 接星星
// ═══════════════════════════════════════════════════════════════
int basketX;
int stars[5][2]; // x, y
bool catcherOver;
unsigned long catcherTime;
int missCount;
void initCatcher() {
basketX = OLED_WIDTH / 2 - 10;
for (int i = 0; i < 5; i++) {
stars[i][0] = random(5, OLED_WIDTH - 5);
stars[i][1] = -i * 15 - random(20);
}
catcherOver = false;
currentScore = 0;
missCount = 0;
catcherTime = millis();
}
void updateCatcher() {
if (catcherOver) {
currentState = GAME_OVER_SCREEN;
beepGameOver();
return;
}
playMusic();
int delay_ms = isDifficultMode ? 30 : 45;
if (millis() - catcherTime < delay_ms) return;
catcherTime = millis();
// 控制
int x = analogRead(JOYSTICK_X);
if (x > 3000) basketX += 4;
if (x < 1000) basketX -= 4;
basketX = constrain(basketX, 0, OLED_WIDTH - 20);
// 星星下落
for (int i = 0; i < 5; i++) {
stars[i][1] += (isDifficultMode ? 2 : 1);
if (stars[i][1] >= OLED_HEIGHT) {
missCount++;
stars[i][0] = random(5, OLED_WIDTH - 5);
stars[i][1] = -10;
if (missCount >= 5) {
catcherOver = true;
return;
}
}
// 接住星星
if (stars[i][1] >= OLED_HEIGHT - 8 && stars[i][1] <= OLED_HEIGHT - 3) {
if (stars[i][0] >= basketX && stars[i][0] <= basketX + 20) {
currentScore += 10;
stars[i][0] = random(5, OLED_WIDTH - 5);
stars[i][1] = -10;
beepScore();
}
}
}
// 绘制
display.clearDisplay();
// 篮子
display.drawRect(basketX, OLED_HEIGHT - 5, 20, 5, SSD1306_WHITE);
display.fillRect(basketX + 1, OLED_HEIGHT - 4, 18, 1, SSD1306_WHITE);
// 星星
for (int i = 0; i < 5; i++) {
int sx = stars[i][0];
int sy = stars[i][1];
display.drawPixel(sx, sy, SSD1306_WHITE);
display.drawPixel(sx - 1, sy, SSD1306_WHITE);
display.drawPixel(sx + 1, sy, SSD1306_WHITE);
display.drawPixel(sx, sy - 1, SSD1306_WHITE);
display.drawPixel(sx, sy + 1, SSD1306_WHITE);
}
// 分数和失误
display.setTextSize(1);
display.setCursor(0, 0);
display.print("S:");
display.print(currentScore);
display.setCursor(OLED_WIDTH - 30, 0);
display.print("X:");
display.print(missCount);
display.display();
}
// ═══════════════════════════════════════════════════════════════
// 游戏调度
// ═══════════════════════════════════════════════════════════════
void initGame() {
switch(currentState) {
case GAME_SNAKE: initSnake(); break;
case GAME_RUNNER: initRunner(); break;
case GAME_FLAPPY: initFlappy(); break;
case GAME_BREAKOUT: initBreakout(); break;
case GAME_TETRIS: initTetris(); break;
case GAME_INVADERS: initInvaders(); break;
case GAME_PONG: initPong(); break;
case GAME_MAZE: initMaze(); break;
case GAME_RACING: initRacing(); break;
case GAME_CATCHER: initCatcher(); break;
}
}
void updateGame() {
switch(currentState) {
case GAME_SNAKE: updateSnake(); break;
case GAME_RUNNER: updateRunner(); break;
case GAME_FLAPPY: updateFlappy(); break;
case GAME_BREAKOUT: updateBreakout(); break;
case GAME_TETRIS: updateTetris(); break;
case GAME_INVADERS: updateInvaders(); break;
case GAME_PONG: updatePong(); break;
case GAME_MAZE: updateMaze(); break;
case GAME_RACING: updateRacing(); break;
case GAME_CATCHER: updateCatcher(); break;
}
}
// ═══════════════════════════════════════════════════════════════
// 主程序
// ═══════════════════════════════════════════════════════════════
void setup() {
Serial.begin(115200);
Serial.println("\n╔═══════════════════════════════════╗");
Serial.println("║ ESP32-C6 Game Box v2.0 ║");
Serial.println("║ Professional Edition ║");
Serial.println("╚═══════════════════════════════════╝\n");
// 初始化OLED
Wire.begin(21, 22);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("❌ OLED initialization failed!");
while (1);
}
Serial.println("✓ OLED initialized");
// 初始化蜂鸣器
ledcAttach(BUZZER_PIN, 2000, 8);
Serial.println("✓ Buzzer initialized");
// 初始化摇杆
pinMode(JOYSTICK_SW, INPUT_PULLUP);
analogReadResolution(12);
Serial.println("✓ Joystick initialized");
// 加载数据
loadScores();
Serial.println("✓ Scores loaded");
display.clearDisplay();
display.display();
randomSeed(analogRead(0));
Serial.println("\n🎮 System ready! Enjoy gaming!\n");
}
void loop() {
switch(currentState) {
case SPLASH_SCREEN:
showSplash();
break;
case MAIN_MENU:
handleMainMenu();
break;
case DIFFICULTY_SELECT:
handleDifficulty();
break;
case GAME_OVER_SCREEN:
handleGameOver();
break;
case HIGH_SCORE_SCREEN:
handleHighScores();
break;
case SETTINGS_SCREEN:
handleSettings();
break;
default:
updateGame();
break;
}
delay(10);
}
🎨 自定义和扩展
添加新游戏
1. 在 `GameState` 枚举中添加新状态
2. 创建 `initYourGame()` 和 `updateYourGame()` 函数
3. 在 `initGame()` 和 `updateGame()` 中添加对应的 case
4. 设计专属的背景音乐
调整难度
修改游戏中的 `isDifficultMode` 判断:
```cpp
int speed = isDifficultMode ? 80 : 130; // 速度调整
int pipeGap = isDifficultMode ? 20 : 25; // 间隙调整
更换音乐
定义新的音符数组:
```cpp
MusicNote myMusic[] = {
{NOTE_C5, 200}, {NOTE_E5, 200},
{NOTE_G5, 400}
};
int myMusic_len = 3;
🐛 已知问题与解决方案
问题1: OLED显示模糊
解决:检查I2C地址 (0x3C或0x3D),调整对比度
问题2: 摇杆漂移
解决:增加死区判断或校准中心值
```cpp
if (abs(x - 2048) < 200) x = 2048; // 死区
```
问题3: 音乐卡顿
解决:降低游戏更新频率或简化音符数组
🌟 未来改进计划
添加双人对战模式
实现蓝牙手柄支持
增加彩色OLED支持 (SSD1351)
添加关卡编辑器
实现云端排行榜 (WiFi)
加入震动反馈
支持自定义皮肤
🎓 适用人群
✅ Arduino/ESP32 初学者
✅ 游戏开发爱好者
✅ 创客和硬件DIY爱好者
✅ 嵌入式系统学习者
✅ 复古游戏收藏家
💬 总结
这个 ESP32-C6 游戏合集系统展示了微控制器的强大潜力。通过合理的架构设计和优化,我们在小小的芯片上实现了10款完整的游戏,每款都有独特的玩法和音乐。
这个项目不仅是一个有趣的玩具,更是学习嵌入式开发、游戏编程和系统设计的绝佳实践。无论你是想学习技术,还是单纯享受复古游戏的乐趣,这都是一个值得尝试的项目。
🔗 相关资源
ESP32-C6 文档: [Espressif官网](https://www.espressif.com/en/products/socs/esp32-c6)
SSD1306 库文档: [Adafruit GFX库](https://github.com/adafruit/Adafruit-GFX-Library)
音符频率表: [Arduino音符定义](https://github.com/robsoncouto/arduino-songs)
> 💡 提示: 如果这个项目对你有帮助,欢迎点赞、收藏和分享!有任何问题或建议,欢迎在评论区讨论。
🎮 开始你的游戏之旅吧!
代码即艺术,创造即快乐! 🚀✨

