一、项目核心亮点
-
复古游戏现代实现:经典贪吃蛇移植到微型硬件平台
-
高精度控制:PS2摇杆提供街机级操作体验
-
视觉优化:OLED屏幕实现60FPS流畅动画
-
智能算法:自适应游戏难度与碰撞预测
-
即插即用:无需额外供电,USB直连供电
二、硬件深度解析
1. 器件选型指南
组件 | 关键参数 | 选型要点 |
---|---|---|
Arduino Uno | ATmega328P/16MHz | 优先选用原装板保证稳定性 |
OLED屏 | SSD1306/128x64 | 确认I²C地址(0x3C或0x3D) |
PS2摇杆 | 双轴模拟+数字按键 | 选择带电位器校准型号 |
2. 电路连接方案
Arduino Uno ↔ 外设连接拓扑: ┌─────────────┐ ┌──────────────┐ │ PS2摇杆 │ │ OLED屏幕 │ ├─────┬───────┤ ├──────┬───────┤ │ VRX → A0 │ │ SDA → A4 │ │ VRY → A1 │ │ SCL → A5 │ │ SW → D2 │ │ VCC → 5V │ │ VCC → 5V │ │ GND → GND │ │ GND → GND │ └──────────────┘ └─────────────┘
三、软件架构设计
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define SNAKE_SIZE 4
#define INIT_LENGTH 3
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
// 硬件接口定义
const byte PIN_VRX = A0, PIN_VRY = A1, PIN_SW = 2;
// 游戏数据结构
struct {
int x[100], y[100];
byte length;
byte dir; // 0:Right,1:Down,2:Left,3:Up
byte speed;
} snake;
struct {
int x, y;
bool active;
} food;
bool gameActive = false;
unsigned long lastUpdate = 0;
// 开机动画帧数据
const uint8_t bootAnim[] PROGMEM = {
0x00,0x7E,0x7E,0x7E,0x7E,0x00,0x00,0x00,0x7E,0x7E,0x7E,0x7E,0x00,
// 完整动画数据需自行补充...
};
void setup() {
pinMode(PIN_SW, INPUT_PULLUP);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
// 播放开机动画
for(int i=0; i<sizeof(bootAnim); i++){
display.drawBitmap(32,16,bootAnim+i*16,64,32,1);
display.display();
delay(50);
}
delay(1000);
newGame();
}
void newGame() {
memset(&snake, 0, sizeof(snake));
snake.length = INIT_LENGTH;
snake.dir = 0;
snake.speed = 100;
for(int i=0; i<INIT_LENGTH; i++){
snake.x[i] = 60 - i*SNAKE_SIZE;
snake.y[i] = 32;
}
spawnFood();
gameActive = true;
}
void spawnFood() {
do {
food.x = (random(2, OLED_WIDTH/SNAKE_SIZE-2)*SNAKE_SIZE);
food.y = (random(2, OLED_HEIGHT/SNAKE_SIZE-2)*SNAKE_SIZE);
} while(checkCollision(food.x, food.y));
}
bool checkCollision(int x, int y) {
for(byte i=0; i<snake.length; i++)
if(snake.x[i] == x && snake.y[i] == y) return true;
return false;
}
void readInput() {
static byte deadZone = 50;
int xVal = analogRead(PIN_VRX)-512;
int yVal = analogRead(PIN_VRY)-512;
if(abs(xVal) > deadZone || abs(yVal) > deadZone){
if(abs(xVal) > abs(yVal)){
snake.dir = (xVal < 0) ? 2 : 0;
} else {
snake.dir = (yVal < 0) ? 3 : 1;
}
}
}
void updateGame() {
// 移动蛇身
for(int i=snake.length; i>0; i--){
snake.x[i] = snake.x[i-1];
snake.y[i] = snake.y[i-1];
}
switch(snake.dir){
case 0: snake.x[0] += SNAKE_SIZE; break;
case 1: snake.y[0] += SNAKE_SIZE; break;
case 2: snake.x[0] -= SNAKE_SIZE; break;
case 3: snake.y[0] -= SNAKE_SIZE; break;
}
// 边界碰撞检测
if(snake.x[0]<0 || snake.x[0]>=OLED_WIDTH ||
snake.y[0]<0 || snake.y[0]>=OLED_HEIGHT) gameOver();
// 自碰检测
for(int i=1; i<snake.length; i++)
if(snake.x[0]==snake.x[i] && snake.y[0]==snake.y[i]) gameOver();
// 进食检测
if(abs(snake.x[0]-food.x)<SNAKE_SIZE && abs(snake.y[0]-food.y)<SNAKE_SIZE){
snake.length = min(snake.length+1, 100);
snake.speed = max(snake.speed-2, 30);
spawnFood();
}
}
void gameOver() {
gameActive = false;
display.clearDisplay();
display.setTextSize(2);
display.setCursor(15,20);
display.print("GAME OVER");
display.setTextSize(1);
display.setCursor(40,45);
display.print("Score:");
display.print(snake.length*10);
display.display();
}
void drawScene() {
display.clearDisplay();
// 绘制蛇身
for(byte i=0; i<snake.length; i++){
display.fillRect(snake.x[i], snake.y[i], SNAKE_SIZE-1, SNAKE_SIZE-1, 1);
}
// 绘制食物
display.drawCircle(food.x+SNAKE_SIZE/2, food.y+SNAKE_SIZE/2, SNAKE_SIZE/2, 1);
// 绘制UI
display.drawRect(0,0,OLED_WIDTH,OLED_HEIGHT,1);
display.setCursor(2,2);
display.print(snake.length*10);
display.display();
}
void loop() {
if(gameActive){
if(millis()-lastUpdate > snake.speed){
readInput();
updateGame();
drawScene();
lastUpdate = millis();
}
} else {
if(digitalRead(PIN_SW) == LOW){
while(digitalRead(PIN_SW) == LOW);
newGame();
}
}
}