ESP32で反射神経ゲームを作ろう!LEDとボタンで反応時間を測定【Arduino】

ESP32

ESP32を使って、簡単なゲームを作ってみませんか?

今回は、LEDが点灯した瞬間にボタンを押し、**何ミリ秒で反応できたかを測定する「反射神経ゲーム」**を作ります。

使用するのはLEDとタクトスイッチだけ。比較的簡単な構成ですが、GPIOの入出力や時間計測、乱数、状態管理など、組み込みプログラミングの基本をまとめて学べます。

ESP32-C3を搭載したKUMITATE-C3なら、LEDとスイッチが基板に搭載されているため、追加の配線なしで挑戦できます。

今回作る反射神経ゲーム

ゲームのルールはとてもシンプルです。

  1. ゲームを開始すると、2〜5秒のランダムな待ち時間が発生する
  2. LEDが点灯する
  3. LEDが点灯したら、できるだけ早くボタンを押す
  4. LEDの点灯からボタンを押すまでの時間を測定する
  5. 測定結果をシリアルモニターに表示する

例えば、LEDが点灯してから250ミリ秒後にボタンを押した場合、反応時間は250msと表示されます。

また、LEDが点灯する前にボタンを押した場合は、フライングと判定します。

毎回ランダムな待ち時間を設けることで、タイミングを予測してボタンを押すことを難しくしています。

必要なもの

今回使用するものは次のとおりです。

部品数量
ESP32開発ボード1
LED1
タクトスイッチ1
LED用電流制限抵抗1
USBケーブル1
パソコン(Arduino IDE)1

※KUMITATE-C3を使用する場合、LEDとスイッチは基板に搭載されているため、外付け部品は不要です。

ESP32とLED・スイッチの接続

今回のサンプルプログラムでは、KUMITATE-C3に合わせて次のGPIOを使用します。

GPIO接続するもの
GPIO0LED
GPIO21タクトスイッチ

KUMITATE-C3ではGPIO0をHIGHにするとLEDが点灯し、GPIO21に接続されたSW1を押すとLOWになります。

一般的なESP32開発ボードを使う場合は、使用するGPIO番号を変更してください。

外付けLEDには適切な電流制限抵抗を直列に接続します。スイッチは、GPIOとGNDの間に接続し、内部プルアップを利用する構成とします。

ESP32の反射神経ゲームのプログラム

Arduino IDEで新しいスケッチを作成し、次のプログラムを書き込みます。

const int LED_PIN = 0;
const int SW_PIN = 21;

// ゲームの状態
enum GameState {
  WAITING,   // LED点灯待ち
  REACTION,  // ボタン入力待ち
  RESULT     // 結果表示中
};

GameState state = WAITING;

unsigned long stateStartTime = 0;
unsigned long reactionStartTime = 0;
unsigned long waitTime = 0;

bool lastButtonState = HIGH;

// 次のゲームを開始
void startGame() {
  digitalWrite(LED_PIN, LOW);

  waitTime = random(2000, 5001);
  stateStartTime = millis();
  state = WAITING;

  Serial.println("準備してください...");
}

void setup() {
  Serial.begin(115200);

  pinMode(LED_PIN, OUTPUT);
  pinMode(SW_PIN, INPUT_PULLUP);

  randomSeed(micros());

  startGame();
}

void loop() {
  unsigned long now = millis();

  bool buttonState = digitalRead(SW_PIN);

  // 押した瞬間を検出
  bool pressed =
    (lastButtonState == HIGH &&
     buttonState == LOW);

  lastButtonState = buttonState;

  switch (state) {

    case WAITING:
      // LEDが点灯する前に押した場合
      if (pressed) {
        Serial.println("フライング!");
        state = RESULT;
        stateStartTime = now;
      }
      // ランダムな待ち時間が経過
      else if (now - stateStartTime >= waitTime) {
        digitalWrite(LED_PIN, HIGH);
        reactionStartTime = millis();
        state = REACTION;
      }
      break;

    case REACTION:
      if (pressed) {
        unsigned long reactionTime =
          now - reactionStartTime;

        digitalWrite(LED_PIN, LOW);

        Serial.print("反応時間:");
        Serial.print(reactionTime);
        Serial.println(" ms");

        state = RESULT;
        stateStartTime = now;
      }
      break;

    case RESULT:
      // 3秒後に次のゲームへ
      if (now - stateStartTime >= 3000) {
        startGame();
      }
      break;
  }

  delay(1);
}

書き込みが完了したら、Arduino IDEのシリアルモニターを開き、通信速度を115200bpsに設定してください。

KUMITATE-C3でUSBシリアルが表示されない場合は、ボード設定やUSB CDC On Bootの設定も確認しましょう。

実行結果

プログラムが起動すると、シリアルモニターに次のように表示されます。

準備してください...
反応時間:284 ms
準備してください...
反応時間:231 ms
準備してください...
フライング!
準備してください...
反応時間:205 ms

LEDが点灯した瞬間にボタンを押してみましょう。

なお、測定結果にはプログラムの処理周期やスイッチの検出遅れなども含まれるため、厳密な人間の反応時間を測定する装置ではありません。

プログラムの仕組みを解説

① millis()で経過時間を測定する

今回のプログラムで重要なのが、millis()です。

millis()は、プログラムの動作開始からの経過時間をミリ秒単位で取得するArduinoの関数です。

例えば、LEDが点灯した時刻とボタンを押した時刻が次のようになっていたとします。

LED点灯:10000 ms
ボタン入力:10250 ms

この場合、反応時間は次の計算で求められます。

10250 − 10000 = 250ms

プログラムでは次のように記述しています。

unsigned long reactionTime =
  now - reactionStartTime;

このように、2つの時刻の差を求めるだけで経過時間を測定できます。

② random()で待ち時間を変える

毎回同じ時間でLEDが点灯すると、プレイヤーはタイミングを予測できてしまいます。

そこで、random()を使って待ち時間をランダムに変更します。

waitTime = random(2000, 5001);

これにより、2000〜5000ミリ秒の範囲で待ち時間が決まります。

③ スイッチを押した瞬間を検出する

スイッチの状態は、digitalRead()で取得できます。

ただし、単純にLOWかどうかだけを確認すると、押している間ずっと押下状態と判定されます。

今回は、前回がHIGHで今回がLOWになった瞬間を検出しています。

bool pressed =
  (lastButtonState == HIGH &&
   buttonState == LOW);

これを立ち下がりエッジの検出と呼びます。

ゲームでは「ボタンを押しているか」ではなく、「いつ押したか」が重要なので、このような処理を使います。

なお、実際のスイッチにはチャタリングが発生することがあります。必要に応じてデバウンス処理を追加すると、さらに安定した入力判定ができます。

④ 状態遷移でゲームを管理する

今回のゲームでは、3つの状態を使用しています。

状態動作
WAITINGLEDが点灯するまで待つ
REACTIONボタンが押されるまで待つ
RESULT結果を表示して次のゲームを待つ

これらの状態をswitch文で切り替えています。

switch (state) {
  case WAITING:
    // LEDの点灯待ち
    break;

  case REACTION:
    // ボタンの入力待ち
    break;

  case RESULT:
    // 結果表示中
    break;
}

このように、現在どの処理を実行するかを状態によって切り替える設計を「状態遷移」と呼びます。

状態遷移は、家電製品や産業機器などの組み込みシステムでも広く使用される重要な考え方です。

さらに改造してみよう

基本的な反射神経ゲームが完成したら、次のような機能を追加してみましょう。

OLEDに結果を表示する

シリアルモニターではなくOLEDディスプレイに反応時間を表示すれば、パソコンの画面を見なくても遊べるようになります。

ベストタイムを記録する

これまでに計測した反応時間の最小値を記録します。

複数回の平均を求める

5回連続でプレイし、平均反応時間を計算します。配列や繰り返し処理の学習にもなります。

ブザーでスタートを知らせる

LEDの代わりに音を合図にしたり、光と音の両方を使ったゲームに発展させたりできます。

KUMITATE-C3なら配線なしで挑戦できる

KUMITATE-C3はESP32-C3を搭載した組み込みプログラミング学習用ボードです。

LEDやスイッチ、OLED、ブザーなどを1枚の基板に搭載しているため、今回のようなゲームを外付け配線なしで製作できます。

クミタテでは、GPIOの制御からWi-FiやI2Cなどの通信まで、実機を使って学習できる教材を提供しています。

KUMITATE-C3の詳細はこちら

また、ブザーの音を使った反応速度ゲームのサンプルプログラムもGitHubで公開しています。

GitHub:KUMITATE-C3 反応速度ゲーム

まとめ

今回はESP32を使って、LEDとボタンによる反射神経ゲームを製作しました。

学習した主な内容は次のとおりです。

  • GPIOによるLEDの点灯制御
  • タクトスイッチの入力
  • millis()による経過時間の測定
  • random()によるランダムな待ち時間
  • スイッチの押下エッジ検出
  • 状態遷移によるプログラム設計

シンプルなゲームですが、組み込みプログラミングで使用するさまざまな技術が含まれています。

まずはプログラムをそのまま動かし、次に待ち時間やゲームのルールを変更してみてください。

KUMITATE

読むだけでなく、
実際に動かして学びませんか?

クミタテは、組み込み開発や電子工作を 実践しながら学べる学習プラットフォームです。 ESP32を使ったプログラミングから、電子回路、センサー、 通信、基板設計まで、自分のペースで学習できます。

  • 無料で学べる実践的な教材を掲載
  • すぐに試せるサンプルプログラム付き
  • Googleアカウントですぐに登録可能
クミタテに無料登録する
ESP32
スポンサーリンク