Utility Bar

Using ESP32 Communication for a Multiplayer Pong Game Using WiFi (UDP)

Using ESP32 Communication for a Multiplayer Pong Game Using WiFi (UDP)

Ethan Zaitchik |

Table of Contents

    Introduction

    This guide will walk you through creating a multiplayer Pong game using two ESP32 boards. The boards communicate over Wi-Fi using UDP, chosen for its low latency, making it ideal for real-time applications like gaming. You’ll learn how to connect OLED displays, read input from buttons, and establish a communication protocol using UDP.

    Components Required

    To complete this project, you’ll need the following components:

     

    1. Hardware Setup

    Connecting the OLED Displays

    The SH1106 OLED displays will connect to the ESP32 boards via I2C. The wiring is as follows:

    • SDA (Data Line): Connect to ESP32 pin 21
    • SCL (Clock Line): Connect to ESP32 pin 22
    • VCC: Connect to 3.3V on ESP32
    • GND: Connect to GND on ESP32

    Connecting the Push Buttons

    Each ESP32 will have two buttons to control the paddles. Connect them as follows:

    • Button 1 (Up): Connect one leg to GPIO 2 and the other leg to GND.
    • Button 2 (Down): Connect one leg to GPIO 4 and the other leg to GND.

    Note: You can use pull-up resistors or enable the internal pull-ups in the code to ensure stable button inputs.

     

    2. Software Setup

    Installing Required Libraries

    Ensure the following libraries are installed in your Arduino IDE:

    1. Adafruit_GFX: For graphics support on the OLED display.
    2. Adafruit_SH1106: For interfacing with the SH1106 OLED display. (Use Adafruit_SSD1306 if you're using the SSD1306 display instead).
    3. WiFi: For handling Wi-Fi connectivity.
    4. WiFiUdp: For UDP communication.

    You can install these libraries by navigating to Sketch > Include Library > Manage Libraries in the Arduino IDE.

     

    3. Writing the Code

    You will create two separate programs: one for the server (ESP32 #1) and one for the client (ESP32 #2). The server will manage the game logic, while the client will send paddle movements and receive game state updates.

     

    Program 1: Server (ESP32 #1)

    The server listens for UDP packets from the client and updates its game state accordingly.

    #include <WiFi.h>
    #include <WiFiUdp.h>
    #include <U8g2lib.h>
    
    #define BUTTON_UP 2
    #define BUTTON_DOWN 4
    
    const char* ssid = "PongGame";  // SSID of Wi-Fi network
    const char* password = "12345678";  // Password of Wi-Fi network
    
    WiFiUDP udp;
    const unsigned int localUdpPort = 4210;  // Local UDP port for listening
    char incomingPacket[255];  // Buffer for incoming packets
    IPAddress clientIP;
    
    U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
    
    int paddle1Y = 30, paddle2Y = 30;
    int ballX = 64, ballY = 32;
    int ballVelX = 1, ballVelY = 1;
    
    void setup() {
      Serial.begin(115200);
      
      pinMode(BUTTON_UP, INPUT_PULLUP);
      pinMode(BUTTON_DOWN, INPUT_PULLUP);
    
      u8g2.begin();
      u8g2.clearBuffer();
    
      // Start Wi-Fi as an access point
      WiFi.softAP(ssid, password);
      Serial.println("WiFi AP started");
    
      udp.begin(localUdpPort);
      Serial.printf("Now listening on UDP port %d\n", localUdpPort);
    }
    
    void loop() {
      handleButtons();
      updateGame();
      drawGame();
      sendGameState();
      receivePaddlePosition();
      delay(20);  // Adjust delay for game speed
    }
    
    void handleButtons() {
      if (digitalRead(BUTTON_UP) == LOW) {
        paddle1Y -= 2;
        if (paddle1Y < 0) paddle1Y = 0;
      }
      if (digitalRead(BUTTON_DOWN) == LOW) {
        paddle1Y += 2;
        if (paddle1Y > 48) paddle1Y = 48;  // Limit paddle position
      }
    }
    
    void updateGame() {
      ballX += ballVelX;
      ballY += ballVelY;
    
      // Ball collision with top/bottom
      if (ballY <= 0 || ballY >= 64) ballVelY = -ballVelY;
    
      // Ball collision with paddles
      if (ballX <= 4 && ballY >= paddle1Y && ballY <= paddle1Y + 16) ballVelX = -ballVelX;
      if (ballX >= 124 && ballY >= paddle2Y && ballY <= paddle2Y + 16) ballVelX = -ballVelX;
    
      // Ball out of bounds
      if (ballX <= 0 || ballX >= 128) {
        ballX = 64;
        ballY = 32;
      }
    }
    
    void drawGame() {
      u8g2.clearBuffer();
      u8g2.drawBox(2, paddle1Y, 2, 16);  // Draw paddle 1
      u8g2.drawBox(124, paddle2Y, 2, 16);  // Draw paddle 2
      u8g2.drawDisc(ballX, ballY, 2);  // Draw ball
      u8g2.sendBuffer();
    }
    
    void sendGameState() {
      String gameState = String(paddle1Y) + "," + String(ballX) + "," + String(ballY);
      
      // Convert the string to a byte array
      const char* gameStateCStr = gameState.c_str();
      udp.beginPacket(clientIP, localUdpPort);
      udp.write((const uint8_t*)gameStateCStr, strlen(gameStateCStr));  // Send game state to client
      udp.endPacket();
    }
    
    void receivePaddlePosition() {
      int packetSize = udp.parsePacket();
      if (packetSize) {
        int len = udp.read(incomingPacket, 255);
        if (len > 0) {
          incomingPacket[len] = 0;
          paddle2Y = atoi(incomingPacket);  // Get paddle 2 position from client
          clientIP = udp.remoteIP();  // Get client IP address
        }
      }
    }
    

     

    Program 2: Client (ESP32 #2)

    The client sends its paddle position to the server and receives the game state to update its display.

    #include <WiFi.h>
    #include <WiFiUdp.h>
    #include <U8g2lib.h>
    
    #define BUTTON_UP 2
    #define BUTTON_DOWN 4
    
    const char* ssid = "PongGame";  // SSID of Wi-Fi network
    const char* password = "12345678";  // Password of Wi-Fi network
    
    WiFiUDP udp;
    const char* serverIP = "192.168.4.1";  // IP of the server (Player 1)
    const unsigned int localUdpPort = 4210;  // Local UDP port for listening
    char incomingPacket[255];  // Buffer for incoming packets
    
    U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
    
    int paddle2Y = 30;
    int ballX, ballY, paddle1Y;
    
    void setup() {
      Serial.begin(115200);
      
      pinMode(BUTTON_UP, INPUT_PULLUP);
      pinMode(BUTTON_DOWN, INPUT_PULLUP);
    
      u8g2.begin();
      u8g2.clearBuffer();
    
      // Connect to Wi-Fi network
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
      }
      Serial.println("Connected to WiFi");
    
      udp.begin(localUdpPort);
    }
    
    void loop() {
      handleButtons();
      sendPaddlePosition();
      receiveGameState();
      drawGame();
      delay(20);  // Adjust delay for game speed
    }
    
    void handleButtons() {
      if (digitalRead(BUTTON_UP) == LOW) {
        paddle2Y -= 2;
        if (paddle2Y < 0) paddle2Y = 0;
      }
      if (digitalRead(BUTTON_DOWN) == LOW) {
        paddle2Y += 2;
        if (paddle2Y > 48) paddle2Y = 48;  // Limit paddle position
      }
    }
    
    void sendPaddlePosition() {
      String paddlePos = String(paddle2Y);
      const char* paddlePosCStr = paddlePos.c_str();
      udp.beginPacket(serverIP, localUdpPort);
      udp.write((const uint8_t*)paddlePosCStr, strlen(paddlePosCStr));  // Send paddle position to server
      udp.endPacket();
    }
    
    void receiveGameState() {
      int packetSize = udp.parsePacket();
      if (packetSize) {
        int len = udp.read(incomingPacket, 255);
        if (len > 0) {
          incomingPacket[len] = 0;
          sscanf(incomingPacket, "%d,%d,%d", &paddle1Y, &ballX, &ballY);  // Update game state
        }
      }
    }
    
    void drawGame() {
      u8g2.clearBuffer();
      u8g2.drawBox(2, paddle1Y, 2, 16);  // Draw paddle 1
      u8g2.drawBox(124, paddle2Y, 2, 16);  // Draw paddle 2
      u8g2.drawDisc(ballX, ballY, 2);  // Draw ball
      u8g2.sendBuffer();
    }
    

     

    4. Testing and Troubleshooting

    1. Upload the Code: Upload the server code to one ESP32 and the client code to the other.
    2. Connect to Wi-Fi: Power on the ESP32 boards. The server will create a Wi-Fi access point, and the client will connect to it.
    3. Check Serial Monitor: Open the serial monitor on both boards to ensure they connect correctly.
    4. Adjust Timing: If the game is too fast or too slow, adjust the delay(20) in both codes to fine-tune the game speed.

     

    5. Conclusion

    You’ve now built a basic multiplayer Pong game using two ESP32 boards. This project is a great way to explore the capabilities of the ESP32 for real-time applications and communication over Wi-Fi. You can expand on this project by adding features like scoring, sound effects, or even a more sophisticated graphical interface.

    Enjoy your game!

    Leave a comment

    // Table of contents