Project 5: Dino Game on LCD

Part 1: How to use LCD to display what we want and use of push button

Step 1: Overview

In this tutorial, we will create a simple circuit and code that shows a message on an LCD screen when a button is pressed. The message toggles between two different texts on the LCD each time the button is pressed.

Step 2: Components Required

  • ESP32 Development Board
  • 16x2 LCD Display (I2C Module — PCF8574)
  • Puss Button
  • Breadboard & Jumper Wires

Step 3: Circuit Connections

LCD Connections:

  • VCC: Connect to 5V on the ESP32.
  • GND: Connect to GND on the ESP32.
  • SDA: Connect to GPIO 21 (or the default SDA pin on your ESP32).
  • SCL: Connect to GPIO 22 (or the default SCL pin on your ESP32).

Button Connections:

  • One side of the button should be connected to GPIO 12 (or any other GPIO you choose).
  • The other side should be connected to 3v3.

Step 5: Upload the ESP32 Code

Copy and paste the following code into the Arduino IDE and upload it to your ESP32.

#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>

#define BUTTON_PIN 12 // GPIO pin for the button
#define LCD_ADDR 0x27

LiquidCrystal_PCF8574 lcd(LCD_ADDR);
bool buttonState = false; // Tracks button press

void setup() {
pinMode(BUTTON_PIN, INPUT_PULLDOWN); // Using internal pull-down
lcd.begin(16, 2);
lcd.setBacklight(255);
lcd.clear();

lcd.setCursor(0, 0);
lcd.print("Hello, Techsage!");
lcd.setCursor(0, 1);
lcd.print("Press Button...");
}

void loop() {
if (digitalRead(BUTTON_PIN) == HIGH) { // Button is pressed (HIGH due to pull-down)
delay(50); // Debounce
if (digitalRead(BUTTON_PIN) == HIGH) { // Still pressed
buttonState = !buttonState; // Toggle state

lcd.clear();
if (buttonState) {
lcd.setCursor(0, 0);
lcd.print("Button Pressed!");
} else {
lcd.setCursor(0, 0);
lcd.print("Hello, Techsage!");
}
delay(300); // Avoid multiple rapid changes
}
}
}

Explanation of Code:

Library Inclusions:

  • We include Wire.h for I2C communication and LiquidCrystal_PCF8574.h for controlling the LCD screen.

Pin Definitions:

  • BUTTON_PIN: Defines the GPIO pin (GPIO 12 in this case) where the push-button is connected.
  • LCD_ADDR: Defines the I2C address of the LCD (0x27 is commonly used for many LCDs with I2C).

Button and LCD Setup:

  • We set the button pin as an input with a pull-down resistor to ensure the button state is LOW when not pressed.
  • The LCD is initialized with 16 columns and 2 rows (lcd.begin(16, 2)).
  • Initially, the LCD shows the message “Hello, Techsage!”.

Main Loop:

  • Button press detection: When the button is pressed (i.e., the GPIO pin reads HIGH), a debounce delay is added to avoid multiple triggers.
  • Button state toggle: The buttonState variable toggles between true and false every time the button is pressed.
  • Message change: Based on the button state, the message on the LCD changes:
  • If buttonState is true, it shows "Button Pressed!".
  • If buttonState is false, it shows "Hello, Techsage!".

Debounce:

  • After detecting a button press, the code waits for 50ms to debounce the button, and a 300ms delay after toggling the message ensures the LCD doesn’t change too rapidly.

How It Works:

  1. When the ESP32 is powered on, the LCD displays “Hello, Techsage!” on the first row and “Press Button…” on the second row.
  2. Pressing the button once toggles the display to show “Button Pressed!”.
  3. Pressing the button again toggles back to “Hello, Techsage!”.
  4. This cycle continues each time the button is pressed.

Part 2 : Dino Game on LCD with ESP32

Overview

In this project, we will create a Dino Run Game on a 16x2 LCD using an ESP32. The game mimics the famous Chrome Dino Game, where the dinosaur jumps over obstacles (cacti) using a push button. The score increases as the Dino successfully avoids obstacles.

Features:

Dino jumps when the button is pressed
Cactus moves across the screen
Score increments when Dino avoids cacti
Game Over screen if Dino collides with a cactus

Just Upload the Code Given Below in same circuit used above:

#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>

/////// DEFINES ////////////////////////////////////

#define BASE_SPEED 70
#define INPUT_BUTTON_PIN 12 // Button at GPIO 12
#define LCD_ADDR 0x27
#define LCD_COLS 16
#define LCD_ROWS 2
#define LCD_TOP_ROW 0,0
#define LCD_BOTTOM_ROW 0,1
#define LCD_SCORE_POS 6,0
#define LCD_SDSCORE_OFFSET 14,0
#define START_STATE 0
#define GAME_STATE 1
#define GAME_OVER_STATE 2
#define DINO_FRAME_A_LCD 0
#define DINO_FRAME_B_LCD 1
#define CACTUS_LCD 2
#define DINO_AIR_TIME 20
#define INPUT_HOLD_TIME 25
#define SPEED_INCREASE_VAL 10

////////////////////////////////////////////////////

////// GLOBAL //////////////////////////////////////

LiquidCrystal_PCF8574 lcd(LCD_ADDR);
int currState = START_STATE;
int gameSpeed = BASE_SPEED;
int inputState = 0;
int inputHoldTimer = 0;
int score = 0;
int dinoY = 1;
int dinoCurrAirTime = 0;
char dinoCurrFrame = 'A';
int currCactusX = 16;
bool buttonPressed = false; // Track button state

const char* startScreenTopStr = "ESP32 Dino Run";
const char* startScreenBottomStr = "Press To Start!";
const char* gameOverTopStr = "Game Over!";
const char* scoreText = "Score:";

// Graphics

byte dinoGfxFrameA[8] =
{
0b00000,0b01110,0b10101,0b10001,
0b10010,0b11110,0b10100,0b01100
};

byte dinoGfxFrameB[8] =
{
0b00000,0b01110,0b10101,0b10001,
0b10010,0b11110,0b10100,0b10010
};

byte cactusGfx[8] =
{
0b00100,0b10101,0b10101,0b10101,
0b01110,0b01110,0b01110,0b01110
};

////////////////////////////////////////////////////

///// FUNCTIONS ////////////////////////////////////

void _initHardware()
{
pinMode(INPUT_BUTTON_PIN, INPUT_PULLDOWN); // Use internal pull-down resistor
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.setBacklight(255);
lcd.clear();
}

void _initGraphics()
{
lcd.createChar(DINO_FRAME_A_LCD, dinoGfxFrameA);
lcd.createChar(DINO_FRAME_B_LCD, dinoGfxFrameB);
lcd.createChar(CACTUS_LCD, cactusGfx);
}

// Function to check button press with debounce
bool isButtonPressed() {
if (digitalRead(INPUT_BUTTON_PIN) == HIGH) {
delay(50); // Debounce delay
if (digitalRead(INPUT_BUTTON_PIN) == HIGH) {
return true;
}
}
return false;
}

void _startStateProcess()
{
if (isButtonPressed())
{
currState = GAME_STATE;
}
}

void _gameStateProcess()
{
// Check if button is pressed to jump
if (isButtonPressed() && dinoCurrAirTime == 0 && inputHoldTimer <= 0)
{
dinoY = 0; // Move dino to the air
dinoCurrAirTime = DINO_AIR_TIME;
inputHoldTimer = INPUT_HOLD_TIME;
}

// Move the cactus
if(currCactusX > 1)
{
currCactusX--;
}
else
{
if(dinoY == 0)
{
score++;
if(score % 10 == 0)
{
gameSpeed -= SPEED_INCREASE_VAL;
}
currCactusX = 16;
}
else
{
gameSpeed = BASE_SPEED;
currState = GAME_OVER_STATE;
return;
}
}

if(dinoCurrAirTime == 1)
{
dinoY = 1;
}

if(dinoCurrAirTime > 0)
{
dinoCurrAirTime--;
}

if(inputHoldTimer > 0)
{
inputHoldTimer--;
}
}

void _gameOverStateProcess()
{
if (isButtonPressed())
{
delay(250);
_resetGame();
}
}

void _resetGame()
{
score = 0;
dinoY = 1;
dinoCurrAirTime = 0;
dinoCurrFrame = 'A';
currCactusX = 16;
currState = START_STATE;
}

// Graphics

void _drawStartScreen()
{
lcd.clear();
lcd.setCursor(LCD_TOP_ROW);
lcd.print(startScreenTopStr);
lcd.setCursor(LCD_BOTTOM_ROW);
lcd.print(startScreenBottomStr);
}

void _drawGameOverScreen()
{
lcd.clear();
lcd.setCursor(LCD_TOP_ROW);
lcd.print(gameOverTopStr);
lcd.setCursor(LCD_BOTTOM_ROW);
lcd.print(scoreText);
lcd.print(score);
}

void _drawGameGraphics()
{
lcd.clear();

lcd.setCursor(0, dinoY);
if(dinoCurrFrame == 'A')
{
lcd.write(DINO_FRAME_A_LCD);
dinoCurrFrame = 'B';
}
else
{
lcd.write(DINO_FRAME_B_LCD);
dinoCurrFrame = 'A';
}

lcd.setCursor(currCactusX, 1);
lcd.write(CACTUS_LCD);

lcd.setCursor(LCD_SCORE_POS);
lcd.print(scoreText);
if(score > 9)
{
lcd.print(score);
}
else
{
lcd.setCursor(LCD_SDSCORE_OFFSET);
lcd.print(score);
}
}

////////////////////////////////////////////////////

/////// MAIN CODE //////////////////////////////////

void setup()
{
_initHardware();
_initGraphics();
}

void loop()
{
switch(currState)
{
case START_STATE:
_startStateProcess();
_drawStartScreen();
break;
case GAME_STATE:
_gameStateProcess();
_drawGameGraphics();
break;
case GAME_OVER_STATE:
_gameOverStateProcess();
_drawGameOverScreen();
break;
default:
break;
}
delay(gameSpeed);
}

How It Works

Game Start: The LCD displays `”ESP32 Dino Run”`. Pressing the button starts the game.

Gameplay:

The Dino jumps when you press the button.
The Cactus moves from right to left.
If the Dino avoids the cactus, the score increases.
Game Over: If the Dino hits a cactus, the LCD displays ”Game Over”. Pressing the button restarts the game.

Back to blog

Leave a comment