#include <M5Unified.h> #include <esp_now.h> #include <WiFi.h> #include <cmath> // --- 色定義 --- #define BLACK 0x0000 #define WHITE 0xFFFF #define GREEN 0x07E0 #define RED 0xF800 #define GRAY 0x8410 // おじゃまブロック用の色 #define SCREEN_W 240 #define SCREEN_H 135 // --- バー設定 --- int barW = 40; int barH = 5; int barX = (SCREEN_W - barW) / 2; int barY = SCREEN_H - 10; // --- ボール設定 --- float ballX = SCREEN_W / 2; float ballY = SCREEN_H / 2; float ballVX = 2.5; float ballVY = -2.0; int ballR = 3; // --- ブロック設定 --- const int BLOCK_ROWS = 4; const int BLOCK_COLS = 8; int blockW = SCREEN_W / BLOCK_COLS; int blockH = 12; // ブロックの状態: 0 = なし, 1 = 通常, 2 = おじゃま int blocks[BLOCK_ROWS][BLOCK_COLS]; // --- ゲーム状態 --- bool gameWon = false; bool gameOver = false; int life = 3; // --- 通信とおじゃまブロック用のスタック --- int sent_stack = 0; // 送信用のスタック volatile int recv_stack = 0; // 受信したおじゃまブロックのスタック (ISRから変更されるためvolatile) // --- ESP-NOW関連 --- // ブロードキャストアドレス(すべてのESP-NOWデバイスに送信) uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; esp_now_peer_info_t peerInfo; // 送受信するデータの構造体 typedef struct struct_message { char type; // 'A' = Attack (おじゃまブロック送信) } struct_message; struct_message myData; // --- 関数プロトタイプ --- void Init_ESPNOW(); void onESPNOWSent(const uint8_t *mac_addr, esp_now_send_status_t status); // ★★★ エラー修正箇所 ★★★ void onESPNOWReceive(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len); void sendAttack(); void addOjamaBlock(); void resetBlocks(); void resetBall(); // ESP-NOW データ送信後のコールバック void ICACHE_RAM_ATTR onESPNOWSent(const uint8_t *mac_addr, esp_now_send_status_t status) { // 送信結果はここでは特に処理しない } // ESP-NOW データ受信時のコールバック // ★★★ ESP32コアのバージョンアップに対応した引数に変更 ★★★ void ICACHE_RAM_ATTR onESPNOWReceive(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len) { struct_message msg; memcpy(&msg, incomingData, sizeof(msg)); if (msg.type == 'A') { recv_stack++; // おじゃまスタックを増やす } } // ESP-NOW初期化 void Init_ESPNOW() { WiFi.mode(WIFI_STA); WiFi.disconnect(); if (esp_now_init() == ESP_OK) { M5.Display.println("ESP-Now Init Success"); esp_now_register_send_cb(onESPNOWSent); // ★★★ ここで呼び出す関数のシグネチャがコンパイルエラーの原因だった ★★★ esp_now_register_recv_cb(onESPNOWReceive); } else { M5.Display.println("ESP-Now Init Failed"); return; } memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK){ M5.Display.println("Failed to add peer"); return; } M5.Display.println("Peer added"); } // 攻撃(おじゃまブロック)を送信 void sendAttack() { myData.type = 'A'; esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); } // おじゃまブロックを追加 void addOjamaBlock() { // 下の行から空いている場所を探す for (int i = BLOCK_ROWS - 1; i >= 0; --i) { for (int j = 0; j < BLOCK_COLS; ++j) { if (blocks[i][j] == 0) { // 空のマスを見つけたら blocks[i][j] = 2; // おじゃまブロック(2)を設置 return; // 1つ追加したら終了 } } } } void resetBlocks() { for (int i = 0; i < BLOCK_ROWS; ++i) for (int j = 0; j < BLOCK_COLS; ++j) blocks[i][j] = 1; // 通常ブロック(1)で初期化 gameWon = false; gameOver = false; life = 3; sent_stack = 0; recv_stack = 0; } bool allBlocksCleared() { for (int i = 0; i < BLOCK_ROWS; ++i) for (int j = 0; j < BLOCK_COLS; ++j) if (blocks[i][j] != 0) return false; // ブロックが1つでも残っていたらfalse return true; } void showMessage(const char* msg) { M5.Display.fillScreen(BLACK); M5.Display.setTextColor(WHITE); M5.Display.setCursor(60, SCREEN_H / 2 - 8); M5.Display.println(msg); delay(1500); } void resetBall() { ballX = SCREEN_W / 2; ballY = SCREEN_H / 2; ballVX = 2.5; ballVY = -2.0; } // 通信初期化用のタスク void task0(void *pvParameters) { Init_ESPNOW(); vTaskDelete(NULL); // 初期化が終わったらタスクを削除 } void setup() { auto cfg = M5.config(); M5.begin(cfg); M5.Display.setRotation(3); M5.Display.setTextFont(1); M5.Display.fillScreen(BLACK); // Core1でESP-NOWの初期化タスクを実行 xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 1); delay(500); // 通信初期化のための待機 M5.Display.clear(); resetBlocks(); } void loop() { M5.update(); if (gameWon) { showMessage("You Win!"); resetBlocks(); resetBall(); return; } if (gameOver) { showMessage("You Lose..."); resetBlocks(); resetBall(); return; } // 受信したおじゃまブロックを処理 if (recv_stack > 0) { addOjamaBlock(); recv_stack--; } // 操作(傾き) if (M5.Imu.update()) { auto data = M5.Imu.getImuData(); float tilt = -data.accel.y; float speed = tilt * 10.0; barX += (int)speed; if (barX < 0) barX = 0; if (barX > SCREEN_W - barW) barX = SCREEN_W - barW; } // ボール移動 ballX += ballVX; ballY += ballVY; // 壁反射 if (ballX <= ballR) { ballX = ballR + 1; ballVX *= -1; } if (ballX >= SCREEN_W - ballR) { ballX = SCREEN_W - ballR - 1; ballVX *= -1; } if (ballY <= ballR) { ballY = ballR + 1; ballVY *= -1; } // バー反射 if (ballY + ballR >= barY && ballY + ballR <= barY + barH && ballX >= barX && ballX <= barX + barW) { float offset = (ballX - (barX + barW / 2)) / (barW / 2); float angle_deg = 15 + 60 * fabs(offset); float angle_rad = angle_deg * M_PI / 180.0; float speed = sqrt(ballVX * ballVX + ballVY * ballVY); ballVX = speed * sin(offset >= 0 ? angle_rad : -angle_rad); ballVY = -speed * cos(angle_rad); ballY = barY - ballR - 1; if (ballX <= ballR) ballX = ballR + 1; if (ballX >= SCREEN_W - ballR) ballX = SCREEN_W - ballR - 1; } // ブロックとの衝突 for (int i = 0; i < BLOCK_ROWS; ++i) { for (int j = 0; j < BLOCK_COLS; ++j) { if (blocks[i][j] > 0) { // ブロックが存在すれば int bx = j * blockW; int by = i * blockH; if (ballX + ballR > bx && ballX - ballR < bx + blockW && ballY + ballR > by && ballY - ballR < by + blockH) { if(blocks[i][j] == 1) { // 通常ブロックの場合 blocks[i][j] = 0; // ブロックを消す sent_stack++; // 送信用スタックを+1 if (sent_stack >= 3) { sendAttack(); // 3つ溜まったら攻撃 sent_stack = 0; // スタックリセット } } else if (blocks[i][j] == 2) { // おじゃまブロックの場合 blocks[i][j] = 1; // 通常ブロックに変化させる } // 反射方向の計算 float cx = bx + blockW / 2; float cy = by + blockH / 2; float dx = ballX - cx; float dy = ballY - cy; if (fabs(dx / blockW) > fabs(dy / blockH)) { ballVX *= -1; } else { ballVY *= -1; } goto block_hit_done; } } } } block_hit_done: // ボールが下に落ちたらライフ減 if (ballY > SCREEN_H) { life--; if (life <= 0) { gameOver = true; return; } else { resetBall(); } } if (allBlocksCleared()) { gameWon = true; return; } // === 描画 === M5.Display.fillScreen(BLACK); // ブロック for (int i = 0; i < BLOCK_ROWS; ++i) { for (int j = 0; j < BLOCK_COLS; ++j) { int bx = j * blockW; int by = i * blockH; if (blocks[i][j] == 1) { // 通常ブロック M5.Display.fillRect(bx + 1, by + 1, blockW - 2, blockH - 2, GREEN); } else if (blocks[i][j] == 2) { // おじゃまブロック M5.Display.fillRect(bx + 1, by + 1, blockW - 2, blockH - 2, GRAY); } } } // バー M5.Display.fillRect(barX, barY, barW, barH, WHITE); // ボール M5.Display.fillCircle((int)ballX, (int)ballY, ballR, RED); // ライフ表示 M5.Display.setTextColor(WHITE); M5.Display.setCursor(5, 5); M5.Display.printf("Life: %d", life); delay(16); }