从弹球到双人对战:Arduino UNO R4 LED矩阵游戏开发进阶指南

当你第一次让那颗像素小球在12×8的LED矩阵上欢快弹跳时,那种成就感一定记忆犹新。但硬件爱好者的乐趣从不满足于基础演示——今天我们要把简单的弹球改造成完整的双人对战乒乓球游戏,让这块小小的LED矩阵变身竞技场。

1. 游戏架构设计:从单球到双人交互

在原始弹球代码中,我们只需要处理一个移动对象(小球)和四面固定边界。升级为乒乓球游戏意味着需要引入两个可控球拍、得分系统以及更复杂的碰撞逻辑。先来看看整体架构需要哪些改变:

  • 新增游戏对象

    • 左侧玩家球拍(2个LED高度)
    • 右侧玩家球拍(2个LED高度)
    • 得分显示区域(可复用部分LED)
  • 输入控制

    • 玩家1:使用电位器或按钮控制左侧球拍上下移动
    • 玩家2:使用另一个电位器或按钮控制右侧球拍
  • 游戏逻辑升级

    // 新增游戏状态结构体
    struct GameState {
      int ballX, ballY;
      int ballSpeedX, ballSpeedY;
      int paddleLeft, paddleRight;
      int scoreLeft, scoreRight;
      bool gameActive;
    };
    

2. 硬件准备与输入控制

UNO R4 WiFi版提供了多种输入选项。以下是两种实用的控制方案:

方案A:电位器控制(模拟输入)

void readPaddles() {
  paddleLeft = map(analogRead(A0), 0, 1023, YMIN, YMAX-1);
  paddleRight = map(analogRead(A1), 0, 1023, YMIN, YMAX-1);
}

方案B:按钮控制(数字输入)

#define BTN_UP1 2
#define BTN_DOWN1 3
#define BTN_UP2 4
#define BTN_DOWN2 5

void setup() {
  pinMode(BTN_UP1, INPUT_PULLUP);
  // 其他按钮类似...
}

void readPaddles() {
  if (digitalRead(BTN_UP1) == LOW) paddleLeft--;
  if (digitalRead(BTN_DOWN1) == LOW) paddleLeft++;
  // 右侧球拍类似...
}

提示:电位器控制更流畅,但需要额外硬件;按钮方案成本低但操作需要适应

3. 核心游戏逻辑实现

乒乓球游戏的核心在于碰撞检测和得分判定。我们需要重写原来的runOneStep函数:

void updateGame(GameState &game) {
  // 移动球
  game.ballX += game.ballSpeedX;
  game.ballY += game.ballSpeedY;
  
  // 上下边界碰撞
  if (game.ballY <= YMIN || game.ballY >= YMAX) {
    game.ballSpeedY = -game.ballSpeedY;
    game.ballY = constrain(game.ballY, YMIN, YMAX);
  }
  
  // 左侧球拍碰撞检测
  if (game.ballX == 1 && 
      game.ballY >= game.paddleLeft && 
      game.ballY <= game.paddleLeft+1) {
    game.ballSpeedX = 1; // 向右反弹
    // 增加随机角度变化
    game.ballSpeedY += random(-1, 2); 
  }
  
  // 右侧球拍碰撞检测(类似左侧)
  
  // 得分判定
  if (game.ballX < 0) {
    game.scoreRight++;
    resetBall(game);
  }
  if (game.ballX > XMAX) {
    game.scoreLeft++;
    resetBall(game);
  }
}

4. 显示系统优化与UI设计

12×8的LED矩阵空间有限,需要巧妙设计显示布局:

+--------------------+
|L 0 |        | 0 R | ← 得分显示(顶部)
|    |        |     |
| P1 |  球    | P2  | ← 游戏区域
|    |        |     |
+--------------------+

实现代码示例:

void renderGame(const GameState &game) {
  // 清空网格
  memset(grid, 0, sizeof(grid));
  
  // 绘制球
  grid[game.ballY][game.ballX] = 1;
  
  // 绘制球拍
  grid[game.paddleLeft][0] = 1;
  grid[game.paddleLeft+1][0] = 1;
  grid[game.paddleRight][XMAX] = 1;
  grid[game.paddleRight+1][XMAX] = 1;
  
  // 绘制得分(简化版)
  grid[0][2] = game.scoreLeft > 0;
  grid[0][XMAX-2] = game.scoreRight > 0;
  
  matrix.renderBitmap(grid, 8, 12);
}

5. 游戏节奏与难度调节

为了让游戏更具挑战性,可以加入这些增强功能:

  • 速度递增:每回合球速增加5%

    void resetBall(GameState &game) {
      game.ballSpeedX *= 1.05;
      game.ballSpeedY *= 1.05;
    }
    
  • 特殊击球效果

    • 球拍边缘击球会改变球的角度
    • 连续击球达到一定次数后球速暂时提升
  • 胜利条件

    void checkWinCondition(GameState &game) {
      if (game.scoreLeft >= 5 || game.scoreRight >= 5) {
        game.gameActive = false;
        showWinnerAnimation(game.scoreLeft > game.scoreRight);
      }
    }
    

6. 进阶优化技巧

当基本功能实现后,可以考虑这些提升体验的细节:

帧率控制优化

unsigned long lastFrame = 0;
void loop() {
  if (millis() - lastFrame >= frameDelay) {
    updateGame();
    renderGame();
    lastFrame = millis();
  }
  // 其他非实时关键任务
}

抗球拍闪烁技术

// 在renderGame函数中:
for (int i = 0; i <= 1; i++) {
  int y = constrain(game.paddleLeft + i, YMIN, YMAX);
  grid[y][0] = 1;
}

WiFi功能扩展(可选)

// 使用R4的WiFi功能实现分数上传
#include <WiFiS3.h>
void postScore(int score) {
  WiFiClient client;
  if (client.connect("yourserver.com", 80)) {
    client.print("GET /api/score?value=");
    client.print(score);
    client.println(" HTTP/1.1");
  }
}

7. 调试与问题排查

开发过程中可能会遇到这些典型问题:

  • LED显示异常

    • 检查grid数组维度是否匹配硬件(8行12列)
    • 确认renderBitmap参数顺序正确
  • 球拍控制不灵敏

    // 添加死区过滤抖动
    if (abs(analogRead(A0) - lastReading) > 20) {
      updatePaddle();
    }
    
  • 球速异常

    • 确保没有在loop()中使用delay()影响游戏节奏
    • 使用millis()进行基于时间的运动计算

移植到UNO R4时特别注意:

注意:R4的LED矩阵库与早期版本不同,必须使用#include <Arduino_LED_Matrix.h>和正确的初始化方式

8. 从原型到产品的关键改进

当游戏基本功能完成后,这些改进能让它更接近成品质量:

  1. 状态管理优化

    enum GameMode { MENU, PLAYING, GAME_OVER };
    GameMode currentMode = MENU;
    
  2. 添加开始界面

    void showMenu() {
      static const uint8_t menuAnim[][8][12] = { /*...*/ };
      matrix.loadAnimation(menuAnim);
    }
    
  3. 音效反馈(使用无源蜂鸣器)

    void playTone(int freq, int duration) {
      tone(BUZZER_PIN, freq, duration);
    }
    
  4. 游戏平衡性调整

    • 测试不同球速参数
    • 调整球拍大小对难度的影响

最终,你会得到一个完整的双人对战游戏,而不仅仅是技术演示。这个过程中学到的架构设计、状态管理和用户交互原则,可以应用到更复杂的Arduino项目中。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐