基于Arduino Uno的PS2摇杆控制贪吃蛇游戏开发全攻略

一、项目核心亮点

  • 复古游戏现代实现:经典贪吃蛇移植到微型硬件平台

  • 高精度控制:PS2摇杆提供街机级操作体验

  • 视觉优化:OLED屏幕实现60FPS流畅动画

  • 智能算法:自适应游戏难度与碰撞预测

  • 即插即用:无需额外供电,USB直连供电


二、硬件深度解析

1. 器件选型指南

组件关键参数选型要点
Arduino UnoATmega328P/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();
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值