Making a game

Now that we can detect joystick presses and output to the OLED display, we’ve got all we need to make a simple game.

The code below is a simplified version of Flappy Bird where you have to push the joystick to jump through gaps in pipes that scroll across the screen.

#include <OLEDDisplay.h>
#include <SSD1306.h>

// connect to display using pins D1, D2 for I2C on address 0x3c
SSD1306  display(0x3c, D1, D2);

// flappy dot variables
int x, y, score;

float fy = 0;
float velocity = 0;
float gravity = 0.4;

// joystick pins and presses
byte joystick_pins[] = {D3, D5, D6, D7};
byte joystick_buttons[] = {0, 0, 0, 0};

// joystick pin order constants
const byte JOY_RIGHT = 0;
const byte JOY_PUSH = 1;
const byte JOY_DOWN = 2;
const byte JOY_UP = 3;

// Draws and manages a pipe obstacle (top and bottom)
class Pipe {
  public:
    int x,y,gap;
    
    // create a new pipe
    Pipe(int x, int y, int gap){
      this->x = x;
      this->y = y;
      this->gap = gap;
    }
    
  // draw the pipe to the OLED screen
  void draw() {
    // bottom pipe
    display.drawRect(x, y, 2, 63 - y);
    display.drawRect(x-2, y-2, 6, 2);    
  
    // top pipe
    display.drawRect(x, 0, 2, y - gap);
    display.drawRect(x-2, y-gap-2, 6, 2);
  }

  // move the pipe 1px left
  void move() {
    x--;
    if(x < 0) {
      x = 128;
      y = random(32, 64);
      gap = random(20, 40);
    }
  }

  // check if the dot has collided with this pipe
  bool hitTest(int birdX, int birdY) {
    return (x == birdX) && ((birdY > y) || (birdY < y - gap));
  }
};

void onJoystickChange() {
  Serial.print("Joystick: ");
  for(int i = 0; i < sizeof(joystick_pins); i++) {
    joystick_buttons[i] = !digitalRead(joystick_pins[i]);
    Serial.print(joystick_buttons[i]);
  }
  Serial.println();
}

// Create two pipes
Pipe *p1;
Pipe *p2;

void setup() {
  p1 = new Pipe(64, 25, 30);
  p2 = new Pipe(128, 64, 30);
  
  // init serial port
  Serial.begin(115200);
 
  // set the builtin LED pin to work as an output
  pinMode(LED_BUILTIN, OUTPUT);

  for(int i = 0; i < sizeof(joystick_pins); i++) {
    pinMode(joystick_pins[i], INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(joystick_pins[i]), onJoystickChange, CHANGE);
  }
 
  // init the display
  display.init();
  
  // set text x and y coordinate to start at 0
  x = 0;
  y = 0;
}

bool playing = true;

void loop() {
  // clear the screen
  display.clear();

  // display score
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);
  String str_score = "Score: ";
  str_score += score;
  display.drawString(0, 0, str_score);
  display.setPixel(x, y);
  
  // main game actions
  if(playing) {
    
    // push joystick to jump
    if(joystick_buttons[JOY_PUSH]) velocity = 5;
  
    // make dot fall
    velocity -= gravity;
    if(velocity < -10) velocity = -10;
    fy -= (velocity / 10);
    if(fy > 63) fy = 63;
    if(fy < 0) fy = 0;
    y = (int)fy;
   
    // make sure x and y are valid numbers
    if(x > 128) x = 128;
    if(x < 0) x = 0;
  
    // draw pipes
    p1->draw();
    if(p1->hitTest(x, y)) {
      playing = false;
    } else {
      p1->move();
    }

    p2->draw();
    if(p2->hitTest(x, y)) {
      playing = false;
    } else {
      p2->move();
    }

    score++;
    
  } else {
    // game over
    display.drawString(0, 20, "Game over");
  }
   
  // update the display
  display.display(); 

  // slow down loop
  delay(10);
}

If this code is useful or you have any questions, please leave a comment below.

Enjoy!