• 3
    回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

【 2025 DigiKey 杯】使用 M5CoreS3 设计一个体感游戏机

一、项目目标

打造一款基于 M5CoreS3 的便携式体感游戏机,玩家通过倾斜、摇晃、挥动设备等方式控制游戏,无需额外手柄。

二、核心硬件组成

1. 主控:M5CoreS3

ESP32-S3 双核处理器(主频高达 240MHz)内置 6 轴 IMU(ICM-42688-P 或类似):提供加速度和角速度数据2.0 英寸 320×240 IPS LCD 屏幕内置扬声器 + 麦克风(可用于音效或语音反馈)USB-C 供电 & 编程。

三、软件架构

开发环境

Arduino IDE + M5Unified 库

四、重力小球

游戏规则

屏幕上有一个小球,受“重力”影响会向设备倾斜方向滚动。玩家需通过倾斜设备,引导小球避开障碍物,收集星星。若撞到障碍物,游戏结束。

/*
 * 使用 M5CoreS3 的 6 轴 IMU 控制小球收集星星,避开障碍物
 */

#include <M5Unified.h>

// 小球参数
int ballX = 160, ballY = 120;
float ballVX = 0, ballVY = 0;

// 星星(目标)
struct Star {
  int x, y;
  bool collected;
} stars[5];

// 障碍物
struct Obstacle {
  int x, y;
  int size = 20;
} obstacles[3];

// 游戏状态
int score = 0;
bool gameRunning = true;
unsigned long lastStarTime = 0;
const unsigned long starInterval = 8000; // 每8秒生成新星星(实际是重置未收集的)

// 滤波系数(用于平滑 IMU 数据)
const float alpha = 0.2;
float filteredAx = 0, filteredAy = 0;

// 初始化星星和障碍物
void initGame() {
  score = 0;
  ballX = 160; ballY = 120;
  ballVX = ballVY = 0;

  // 初始化星星(随机位置)
  for (int i = 0; i < 5; i++) {
    stars[i].x = random(20, 300);
    stars[i].y = random(20, 220);
    stars[i].collected = false;
  }

  // 初始化障碍物(固定位置,避免太难)
  obstacles[0] = {80, 60};
  obstacles[1] = {240, 180};
  obstacles[2] = {160, 120};

  gameRunning = true;
  lastStarTime = millis();
}

// 检测点是否在矩形内
bool pointInRect(int px, int py, int rx, int ry, int size) {
  return (px > rx && px < rx + size && py > ry && py < ry + size);
}

// 检测点是否在圆内(小球半径=8)
bool pointInBall(int px, int py, int bx, int by) {
  int dx = px - bx;
  int dy = py - by;
  return (dx * dx + dy * dy) < (8 * 8);
}

void setup() {
  auto cfg = M5.config();
  cfg.clear_display = true;
  M5.begin(cfg);

  // 初始化 IMU
  if (!M5.IMU.Init()) {
    M5.Lcd.println("IMU Init Failed!");
    while (1) delay(100);
  }

  M5.Speaker.setVolume(3);
  randomSeed(analogRead(0));

  initGame();
}

void loop() {
  M5.update();

  if (!gameRunning) {
    // Game Over 画面
    M5.Lcd.fillScreen(TFT_RED);
    M5.Lcd.setTextColor(TFT_WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.setCursor(40, 80);
    M5.Lcd.println("GAME OVER");
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(90, 130);
    M5.Lcd.printf("Score: %d", score);
    M5.Lcd.setTextSize(1);
    M5.Lcd.setCursor(60, 180);
    M5.Lcd.println("Press Btn A to Restart");

    if (M5.BtnA.wasPressed()) {
      initGame();
    }
    delay(50);
    return;
  }

  // === 读取并滤波 IMU 数据 ===
  float ax, ay, az;
  M5.IMU.getAccelData(&ax, &ay, &az);

  // 低通滤波(减少抖动)
  filteredAx = alpha * ax + (1 - alpha) * filteredAx;
  filteredAy = alpha * ay + (1 - alpha) * filteredAy;

  // === 更新小球速度与位置 ===
  ballVX = -filteredAx * 80;   // X 轴反向:右倾 → 小球左移
  ballVY = filteredAy * 80;    // Y 轴:前倾 → 小球下移(符合直觉)

  ballX += ballVX * 0.02;
  ballY += ballVY * 0.02;

  // 边界限制(留出小球半径)
  ballX = constrain(ballX, 8, 312);
  ballY = constrain(ballY, 8, 232);

  // === 碰撞检测:障碍物 ===
  for (int i = 0; i < 3; i++) {
    if (pointInRect(ballX, ballY, obstacles[i].x, obstacles[i].y, obstacles[i].size)) {
      // 播放爆炸音效(简单 beep)
      M5.Speaker.tone(200, 200);
      gameRunning = false;
      break;
    }
  }

  // === 收集星星 ===
  for (int i = 0; i < 5; i++) {
    if (!stars[i].collected && pointInBall(stars[i].x, stars[i].y, ballX, ballY)) {
      stars[i].collected = true;
      score += 10;
      M5.Speaker.tone(1000, 100); // 收集音效
    }
  }

  // === 自动刷新未收集的星星(每8秒)===
  if (millis() - lastStarTime > starInterval) {
    for (int i = 0; i < 5; i++) {
      if (!stars[i].collected) {
        stars[i].x = random(20, 300);
        stars[i].y = random(20, 220);
      }
    }
    lastStarTime = millis();
  }

  // === 绘制画面 ===
  M5.Lcd.fillScreen(TFT_BLACK);

  // 绘制障碍物(红色方块)
  for (int i = 0; i < 3; i++) {
    M5.Lcd.fillRect(obstacles[i].x, obstacles[i].y, obstacles[i].size, obstacles[i].size, TFT_RED);
  }

  // 绘制星星(黄色 ★ 符号,用小三角模拟)
  for (int i = 0; i < 5; i++) {
    if (!stars[i].collected) {
      int x = stars[i].x, y = stars[i].y;
      M5.Lcd.fillCircle(x, y, 3, TFT_YELLOW);
      // 简单星形(可选)
      M5.Lcd.drawPixel(x, y - 4, TFT_YELLOW);
      M5.Lcd.drawPixel(x, y + 4, TFT_YELLOW);
      M5.Lcd.drawPixel(x - 4, y, TFT_YELLOW);
      M5.Lcd.drawPixel(x + 4, y, TFT_YELLOW);
    }
  }

  // 绘制小球(白色)
  M5.Lcd.fillCircle(ballX, ballY, 8, TFT_WHITE);

  // 显示分数
  M5.Lcd.setTextColor(TFT_GREEN);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.printf("Score: %d", score);

  delay(20); // ~50 FPS
}

玩法说明

控制方式:倾斜 M5CoreS3 控制白色小球移动。目标:收集黄色星星(★),每个 +10 分。失败条件:碰到红色方块即 Game Over。重启:Game Over 后按 按钮 A 重新开始。

烧录步骤

在 Arduino IDE 中选择开发板:

Tools → Board → M5Stack → M5CoreS3

选择端口(插入 USB-C 后自动识别)点击 上传(Upload)。

全部回复(3)
正序查看
倒序查看
fzwwj95
LV.7
2
03-24 17:23

【DigiKey杯点评】利用 M5CoreS3 自带 IMU、屏幕和音频资源做体感游戏机,是很典型的“顺着平台能力去做设计”的好思路。交互方式也比较自然,倾斜、摇晃这些动作很容易让作品在演示时快速抓住大家注意力。

0
回复
only one
LV.8
3
03-26 23:54

ESP32-S3 双核处理器(主频高达 240MHz)内置 6 轴 IMU(ICM-42688-P 或类似):提供加速度和角速度数据2.0 英寸 320×240 IPS LCD 屏幕内置扬声器 + 麦克风,是怎么使用

0
回复
abc9981
LV.3
4
03-31 15:13

【DigiKey杯点评】

该项目基于 M5CoreS3 打造便携式体感游戏机,设计思路巧妙且贴合硬件原生能力,是创客级硬件与趣味应用结合的优秀尝试。充分利用主控自带的 6 轴 IMU、LCD 屏幕、扬声器等资源,无需额外外设即可实现体感操控,硬件利用效率高;以重力小球游戏为载体,完成了 IMU 数据滤波、体感控制、碰撞检测、音效反馈等完整功能,代码逻辑清晰且有细节优化,还设计了游戏重启、星星刷新等交互机制,体验完整;开发环境和烧录步骤明确,复刻门槛低,兼具趣味性与实操性。整体完成度高,若后续拓展多款体感游戏、优化 IMU 操控的灵敏度适配,或增加分数存档功能,可玩性和体验感会进一步提升。

1
回复
立
即
发
帖