Utility Bar

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

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

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

Please note: comments must be approved before they are published.