Jumping Dino Game Using Arduino UNO & OLED


🦖 I Built a Dino Game on Arduino UNO… and Somehow It Actually Works

If someone told me a week ago that I’d end up making a running dinosaur game on an Arduino, I would’ve laughed. I mean, Arduino is for blinking LEDs and random sensors, right? Not games.

But here I am, proudly staring at a tiny OLED screen where a pixelated dinosaur jumps over a cactus like it’s the Olympics.

Let me tell you how this madness started.


🐣 Where the Idea Came From

I was honestly bored. I’ve done the usual Arduino stuff — blinking LEDs, beeping buzzers, reading temperature sensors… the usual “I’ve seen this on YouTube” projects.

One day, while waiting for my Wi-Fi to come back, I played the Chrome Dino game (you know, the one that appears when the internet dies). That was the moment my brain went:

"Bro, what if… we build THIS on Arduino?"

And because I have zero control over my curiosity, here we are.


🧰 What I Needed

Nothing fancy. I didn’t even buy anything new:

  • Arduino UNO (my old faithful)

  • 0.96" OLED Display

  • 1 Push Button (for jumping)

  • 1 Buzzer (for sound effects)

  • A bunch of random jumper wires

The whole setup looks like a tiny gaming console made by a drunk engineer… but trust me, it works.


🎮 The Game Logic (Surprisingly Simple)

The dinosaur just stands there doing nothing. Then out of nowhere, a cactus comes charging from the right side of the screen.

Your one and only mission:

👉 Press the button at the right moment to jump.

Jump too early → cactus hits you
Jump too late → cactus hits you
Don’t jump at all → cactus definitely hits you

Either way, the cactus is committed to ruining your life.

Every time you avoid it, you score a point. Miss it once?

GAME. OVER.
(the screen literally screams it at you)


🖥 The OLED Made Me Smile Like a Kid

The screen is tiny, but seeing a dinosaur I coded with my own hands move around was… magical. I added:

✨ A little start animation
✨ A running dino sprite
✨ A cactus that scrolls like a boss
✨ A score counter
✨ A GAME OVER screen that hurts your soul

It honestly feels like a miniature Game Boy.


🔉 The Buzzer = Instant Emotions

I didn’t expect this, but sound makes the game feel real:

  • Jump → cute beep

  • Lose → depressing booong noise

It’s crazy how a ₹20 buzzer can break your heart.


😭 The Struggle

Let me be honest: getting everything to fit in Arduino UNO’s tiny memory was a nightmare. I had moments like:

“Why is this working yesterday and not today!?”
“OLED why you bully me?”
“Who designed cactus collision detection??”

But when it finally worked… oh man. That feeling is EVERYTHING.


🎓 What I Learned Without Realizing

  • How animations are basically fast drawings

  • That timing feels like real game physics

  • That 128×64 pixels is enough to express frustration, joy, and regret

  • That making stuff is way more satisfying than watching tutorials


⚡ What’s Next?

Now that I tasted game development on Arduino, I want more:

  • Nights and clouds like the real Dino game

  • Flying birds

  • EEPROM to save high scores

  • Maybe even multiplayer with two buttons 😎

At this point, I might accidentally build a gaming console.


🎯 Final Thoughts

People think Arduino is only for boring engineering tasks. Nah. It’s a playground. You can literally make a dinosaur run on a screen the size of your thumbnail.

This tiny project made me feel like a proper game developer — except my tools were jumper wires and a chip from 2005.

If you have an Arduino lying around, build this. Trust me:

You’ll press that button once…
the dino will jump…
and boom — you're hooked.


CODE:

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- DISPLAY CONFIGURATION ---
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1    // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // The I2C address for 128x64 typically 0x3C or 0x3D
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// --- HARDWARE PIN DEFINITIONS ---
#define BUTTON_PIN 2   // Jump button on Digital Pin 2
#define BUZZER_PIN 9   // Buzzer on Digital Pin 9

// --- GAME VARIABLES ---
bool isJumping = false;
bool gameOver = false;
int dinoY = 48; // Dino's vertical position (lower is better, 64-16)
const int groundY = 56; // The y-coordinate for the ground line
int velocity = 0;
const int gravity = 2;
const int jumpVelocity = -14;
const int dinoX = 10;
const int dinoSize = 8; // Dino is a simple 8x8 block for speed

// Cactus variables
int cactusX = SCREEN_WIDTH;
const int cactusWidth = 4;
const int cactusHeight = 8;
int gameSpeed = 3;
long score = 0;
unsigned long lastFrameTime = 0;
const int frameDelay = 30; // Milliseconds per frame (33ms is ~30 FPS)

// --- FUNCTION PROTOTYPES ---
void setupGame();
void drawDino();
void drawCactus();
void checkCollision();
void drawStartScreen();
void drawGameOverScreen();
void makeJumpSound();
void makeHitSound();

void setup() {
  // Initialize the OLED display
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Button setup (use internal pull-up resistor)
  pinMode(BUTTON_PIN, INPUT_PULLUP);
 
  // Buzzer setup
  pinMode(BUZZER_PIN, OUTPUT);
 
  // Show a splash screen (Adafruit logo by default)
  display.display();
  delay(1000);
 
  drawStartScreen();
}

void loop() {
  if (gameOver) {
    // Wait for the button press to restart
    if (digitalRead(BUTTON_PIN) == LOW) {
      delay(50); // Debounce
      if (digitalRead(BUTTON_PIN) == LOW) {
        setupGame(); // Reset game
      }
    }
    return; // Stay in game over state until restart
  }

  // --- Game Loop Timing (Frame Rate Control) ---
  if (millis() - lastFrameTime < frameDelay) {
    return;
  }
  lastFrameTime = millis();
 
  // --- Input Handling (Jump) ---
  // Button is LOW when pressed due to INPUT_PULLUP
  if (digitalRead(BUTTON_PIN) == LOW && dinoY == groundY) {
    isJumping = true;
    velocity = jumpVelocity;
    makeJumpSound();
  }

  // --- Physics (Gravity) ---
  if (isJumping) {
    dinoY += velocity;
    velocity += gravity;
    if (dinoY >= groundY) {
      dinoY = groundY;
      isJumping = false;
      velocity = 0;
    }
  }

  // --- Move Cactus ---
  cactusX -= gameSpeed;
  if (cactusX < 0) {
    cactusX = SCREEN_WIDTH + random(30, 80); // Spawn new cactus randomly
    score += 10;
    // Increase speed every 100 points (10 successful jumps)
    if (score % 100 == 0 && gameSpeed < 8) {
        gameSpeed++;
    }
  }
 
  // --- Collision Detection ---
  checkCollision();

  // --- Drawing ---
  display.clearDisplay();
 
  // Draw Ground Line
  display.drawLine(0, groundY + dinoSize, SCREEN_WIDTH, groundY + dinoSize, SSD1306_WHITE);
 
  // Draw Dino (as a block)
  drawDino();
 
  // Draw Cactus (as a block)
  drawCactus();
 
  // Display Score
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Score: ");
  display.print(score);
  display.print(" Speed: ");
  display.print(gameSpeed);

  display.display();
}

// --- GAME FUNCTIONS ---

void setupGame() {
  randomSeed(analogRead(0)); // Good practice for better randomness
  isJumping = false;
  gameOver = false;
  dinoY = groundY;
  velocity = 0;
  cactusX = SCREEN_WIDTH;
  gameSpeed = 3;
  score = 0;
  display.clearDisplay();
  display.display();
}

void drawDino() {
    // Simple block dino. You could replace this with a bitmap array for animation.
    display.fillRect(dinoX, dinoY, dinoSize, dinoSize, SSD1306_WHITE);
}

void drawCactus() {
    // Simple block cactus.
    display.fillRect(cactusX, groundY + dinoSize - cactusHeight, cactusWidth, cactusHeight, SSD1306_WHITE);
}

void checkCollision() {
  // Check for horizontal overlap
  if (cactusX < dinoX + dinoSize && cactusX + cactusWidth > dinoX) {
    // Check for vertical overlap (dino's feet hit the top of the cactus)
    if (dinoY + dinoSize > groundY + dinoSize - cactusHeight) {
      gameOver = true;
      makeHitSound();
      drawGameOverScreen();
    }
  }
}

// --- ANIMATION/SCREEN FUNCTIONS ---

void drawStartScreen() {
    display.clearDisplay();
    display.setTextSize(2); // Large text
    display.setTextColor(SSD1306_WHITE);
   
    // Simple intro animation: Text scrolling in/out or a simple countdown
    for (int i = 0; i < 2; i++) {
        display.setCursor(20, 10);
        display.print("DINO JUMP");
        display.setTextSize(1);
        display.setCursor(15, 40);
        display.print("Press Button to Start");
        display.display();
        delay(500);
        display.clearDisplay();
        display.display();
        delay(500);
    }
   
    // Final screen before game starts
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(30, 10);
    display.print("READY!");
    display.setTextSize(1);
    display.setCursor(35, 40);
    display.print("JUMP with D2");
    display.display();
    delay(2000);
   
    setupGame(); // Initialize game variables
}

void drawGameOverScreen() {
    display.clearDisplay();
   
    // Animated Game Over: A simple vertical wipe effect
    for(int i = 0; i < SCREEN_HEIGHT / 2; i += 4) {
        display.fillRect(0, SCREEN_HEIGHT/2 - i, SCREEN_WIDTH, i*2, SSD1306_WHITE);
        display.display();
    }

    // Draw text inside the white box (need to set color to BLACK for text)
    display.setTextColor(SSD1306_BLACK);
    display.setTextSize(3);
    display.setCursor(20, 10);
    display.print("GAME");
    display.setCursor(20, 35);
    display.print("OVER");
   
    // Draw final score outside the box
    display.setTextColor(SSD1306_WHITE);
    display.setTextSize(1);
    display.setCursor(20, 58);
    display.print("Final Score: ");
    display.print(score);
   
    display.display();
}

// --- BUZZER SOUNDS ---

void makeJumpSound() {
  // Short, high-pitched tone
  tone(BUZZER_PIN, 800, 50);
}

void makeHitSound() {
  // Quick, descending two-tone sound for collision
  tone(BUZZER_PIN, 500, 100);
  delay(100);
  noTone(BUZZER_PIN);
  tone(BUZZER_PIN, 200, 200);
  delay(200);
  noTone(BUZZER_PIN);
}

SETUP:















Comments