Notifications
Clear all

A Little M5CoreS3 Motion-Sensing Game Project (Just for Fun!)

1 Posts
1 Users
0 Reactions
134 Views
Yassin
(@yassin)
Member
Joined: 3 months ago
Posts: 29
Topic starter  
Hey everyone,
 
I’ve had some free time lately and decided to tinker around with the M5CoreS3 to build a simple motion-sensing gravity ball game—nothing fancy, just a little toy project to make the most of the board’s built-in hardware. It uses the onboard 6-axis IMU for tilt control, the IPS screen for the game display, and the speaker for basic sound feedback, no extra modules needed. Figured I’d share the details, code, and how it all works for anyone who might want to mess around with something similar!
 

What I Used

 
Main Board: M5CoreS3 (ESP32-S3 dual-core, 240MHz)
 
  • 6-axis IMU (ICM-42688-P) for acceleration/gyro data (core for tilt control)
  • 2.0" 320×240 IPS LCD (game graphics & UI)
  • Built-in speaker (sound effects for collecting stars/crashing)
  • USB-C for power & programming
     

    Dev Env: Arduino IDE + M5Unified Library (super handy for accessing all the board’s features in one place)

 

How the Game Works

 
It’s a classic gravity ball concept: tilt the M5CoreS3 to move a white ball around the screen, collect yellow stars (10 points each) and avoid red square obstacles. Hit an obstacle, and it’s game over—just press Button A to restart. Uncollected stars refresh their positions every 8 seconds to keep it a little interesting, and there’s basic sound feedback for key actions (a high tone for stars, low tone for crashes).
 

Key Code Logic

 
  1. Hardware Init: Fire up the M5CoreS3, IMU, and speaker with the M5Unified Library—super straightforward, minimal setup needed.
  2. IMU Data Smoothing: Read acceleration data from the IMU and apply a low-pass filter to reduce jitter, making the ball movement smooth instead of jumpy.
  3. Ball Physics: Convert filtered IMU X/Y acceleration values to ball speed/direction, update the ball position, and add screen boundary limits so it doesn’t fly off-screen.
  4. Collision Detection: Simple checks for the ball hitting obstacles (game over) or collecting stars (score + sound).
  5. Graphics & UI: Redraw the screen at ~50FPS with obstacles, stars, the ball, and a score counter; a red game over screen with restart prompt for when you crash.
3fcdb202601050934193984
f0b95202601050934444290

Full Code

37e2e202601050933436755

============================Split line================================

/
* 使用 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
}

============================Split line================================

25b97202601051035048722

How to Upload & Run

 
  1. Open Arduino IDE, install the M5Unified Library (Library Manager → search "M5Unified").
  2. Create a new sketch, paste the full code above.
  3. Select the board: Tools → Board → M5Stack → M5CoreS3.
  4. Connect the M5CoreS3 to your PC via USB-C—IDE will auto-detect the port.
  5. Click Upload; the board will restart automatically, and the game starts right away!
 

Quick Tweaks for Fun

 
  • Adjust the IMU filter alpha (0.1-0.3) for more/less ball smoothness.
  • Change the ball speed multiplier (80) to make the ball faster/slower.
  • Randomize obstacle positions (instead of fixed) to up the difficulty.
  • Add more stars/obstacles, or a timer for a time challenge mode.
  • The onboard mic is unused here—easily add voice control (e.g., voice restart) with a little extra code!
 
This was just a fun little project to play with the M5CoreS3’s built-in features, and it’s perfect for beginners to get familiar with IMU data, basic game logic, and the M5Unified Library. It’s nothing groundbreaking, but it’s a cool way to turn the board into a little handheld game console with zero extra hardware.
 
During development, I got it working smoothly, but I also ran into a few technical questions that I think this community might have great insight into. I’d really appreciate your thoughts:
 
  1. I used a basic low-pass filter for IMU data smoothing. What’s your preferred method to balance control responsiveness vs. jitter in real-time motion projects?
  2. For lightweight 2D collision detection on ESP32, are there better CPU-efficient alternatives to basic circle/rectangle checks?
  3. If I later want to send IMU data over WiFi to a drone or robot, what’s the best way to reduce transmission lag while keeping movement smooth?
If anyone tweaks the code or adds new features, I’d love to see what you come up with! Feel free to ask questions or share your mods in the comments.
 
Cheers,
 
Yassin 

Yassin | Building Compact, High-Current Connections for Drones & Robots


   
Quote