Dodge the Blocks: Build a Joystick-Controlled Game with ESP32, Buzzer and OLED Display

Creating a simple game using the ESP32, Joystick, and an OLED display can be a fun project! Let’s create a basic “Dodge the Block” game where a player controls a character (represented by a square) on the screen using a joystick, and they must avoid falling blocks. The blocks will fall from the top of the screen, and the player must dodge them using the joystick. If a block hits the player, the game ends.

Components:

  1. ESP32 (Development board)
  2. Joystick Module (With X and Y analog outputs)
  3. OLED Display (128×64 I2C display)

Wiring Connections:

  • Joystick:
    • VCC → 3.3V (on ESP32)
    • GND → GND (on ESP32)
    • X-AxisGPIO34 (or any analog input pin)
    • Y-AxisGPIO35 (or any analog input pin)
  • OLED Display (I2C):
    • VCC → 3.3V (on ESP32)
    • GND → GND (on ESP32)
    • SDAGPIO21 (default I2C SDA on ESP32)
    • SCLGPIO22 (default I2C SCL on ESP32)

    Buzzer Connections:

    • Positive leg (long leg) of the buzzer to GPIO 14.
    • Negative leg (short leg) of the buzzer to GND on the ESP32.

    Libraries to Include:

    1. Adafruit_SSD1306 (for OLED display)
    2. Wire (for I2C communication)
    3. Adafruit_GFX (for basic graphics)

    Code:

    #include <Wire.h>
    #include <Adafruit_SSD1306.h>
    #include <Adafruit_GFX.h>
    
    // OLED display setup
    #define SCREEN_WIDTH 128
    #define SCREEN_HEIGHT 64
    #define OLED_RESET -1
    #define OLED_I2C_ADDRESS 0x3C  // Define the I2C address here
    Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
    
    // Joystick Pins
    #define JOY_X_PIN 34
    #define JOY_Y_PIN 35
    
    // Buzzer Pin
    #define BUZZER_PIN 14  // Pin for the buzzer
    
    // Game variables
    int playerX = SCREEN_WIDTH / 2;  // Player's initial X position
    int playerY = SCREEN_HEIGHT - 10; // Player's Y position (bottom of the screen)
    int playerSize = 10;             // Size of the player (square)
    
    int blockX[5];  // X positions of the blocks
    int blockY[5];  // Y positions of the blocks
    int blockSpeed = 1; // Speed of falling blocks
    int blockWidth = 10; // Block width
    int blockHeight = 10; // Block height
    int score = 0; // Player's score
    
    void setup() {
      // Initialize Serial Monitor
      Serial.begin(115200);
    
      // Initialize OLED
      if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDRESS)) { // Using the correct I2C address
        Serial.println(F("SSD1306 allocation failed"));
        for (;;); // Infinite loop if OLED initialization fails
      }
      display.display();
      delay(2000); // Wait for 2 seconds
    
      // Initialize joystick pins as analog inputs
      pinMode(JOY_X_PIN, INPUT);
      pinMode(JOY_Y_PIN, INPUT);
    
      // Initialize buzzer pin
      pinMode(BUZZER_PIN, OUTPUT);
      
      // Initialize block positions
      for (int i = 0; i < 5; i++) {
        blockX[i] = random(0, SCREEN_WIDTH - blockWidth);
        blockY[i] = random(-200, -50); // Blocks start off-screen
      }
    }
    
    void loop() {
      // Read joystick values (map to player movement)
      int joyX = analogRead(JOY_X_PIN);
      int joyY = analogRead(JOY_Y_PIN);
      
      // Map joystick values to player movement (X axis)
      playerX = map(joyX, 0, 4095, 0, SCREEN_WIDTH - playerSize);
      playerY = map(joyY, 0, 4095, 0, SCREEN_HEIGHT - playerSize);
    
      // Update block positions
      for (int i = 0; i < 5; i++) {
        blockY[i] += blockSpeed;  // Blocks fall down
        if (blockY[i] >= SCREEN_HEIGHT) {
          blockY[i] = random(-50, -10); // Reset block to top
          blockX[i] = random(0, SCREEN_WIDTH - blockWidth); // Random new X position
          score++; // Increase score when block is passed
        }
      }
    
      // Check for collision with blocks
      for (int i = 0; i < 5; i++) {
        if (playerX < blockX[i] + blockWidth &&
            playerX + playerSize > blockX[i] &&
            playerY < blockY[i] + blockHeight &&
            playerY + playerSize > blockY[i]) {
          playBuzzer();  // Play buzzer sound when collision occurs
          gameOver();  // End the game if collision occurs
        }
      }
    
      // Display everything
      display.clearDisplay();
      display.fillRect(playerX, playerY, playerSize, playerSize, WHITE); // Draw player
      
      // Draw falling blocks
      for (int i = 0; i < 5; i++) {
        display.fillRect(blockX[i], blockY[i], blockWidth, blockHeight, WHITE); // Draw block
      }
      
      // Display score
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 0);
      display.print("Score: ");
      display.print(score);
      display.display();
      
      delay(10); // Add delay for smooth gameplay
    }
    
    void gameOver() {
      display.clearDisplay();
      display.setTextSize(2);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 2);
      display.print("GAME OVER");
      display.setTextSize(1);
      display.setCursor(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 2 + 16);
      display.print("Score: ");
      display.print(score);
      display.display();
      delay(2000); // Show game over screen for 2 seconds
      resetGame();
    }
    
    void resetGame() {
      playerX = SCREEN_WIDTH / 2;
      playerY = SCREEN_HEIGHT - 10;
      score = 0;
      for (int i = 0; i < 5; i++) {
        blockX[i] = random(0, SCREEN_WIDTH - blockWidth);
        blockY[i] = random(-200, -50);
      }
    }
    
    void playBuzzer() {
      tone(BUZZER_PIN, 1000, 200); // Play sound at 1000Hz for 200 milliseconds
    }
    

    Explanation:

    1. Joystick Movement:
      • The X and Y axes of the joystick control the position of the player (a square on the screen). The values from the joystick are mapped to the screen dimensions.
    2. Falling Blocks:
      • The blocks fall from the top of the screen. When they reach the bottom, they reset to the top with new random X positions. The player needs to avoid these blocks.
    3. Game Over:
      • If a block collides with the player, the game ends, and the score is displayed. After 2 seconds, the game resets.
    4. Score:
      • Every time a block passes the player, the score increases.
    5. Display:
      • The OLED is used to display the game state, including the player, falling blocks, and score.

    Explanation of Buzzer Functionality:

    1. Buzzer Pin Setup:
      • Defined a pin for the buzzer (BUZZER_PIN) and set it as an OUTPUT pin in the setup() function.
    2. playBuzzer() Function:
      • This function is used to generate a tone on the buzzer when called. We use the tone() function, which plays a tone on the specified pin.
      • It plays a 1000 Hz sound for 200 milliseconds when the player collides with a block.
    3. Collision Detection:
      • When a collision between the player and a block is detected, the playBuzzer() function is called to sound the buzzer, followed by the gameOver() function.

    This game is simple but can be expanded upon with more features. It will give you a solid foundation for working with the ESP32, joystick, and OLED.