Extra-credit assignment: Flappy Bird

Description:

For this assignment, I tried to recreate an Arduino version of flappy bird using pushbuttons as primary controllers.  The main idea is that the avatar (bird), which continuously moves to the right, has to move through the equally sized gaps (between the pairs of obstacles aka pipes) that are placed at random heights. The bird is affected by gravity and is always pulled down to the bottom, however, the push button allows it to make a sort of jump in the air and therefore move a bit to the top. After every successful passing through the gap, the score is incremented by one. When the avatar hits the obstacle, the game ends, the score is displayed and the user can restart the game by clicking on the second push button.

Process:
Obstacles:

To generate obstacles I have used a class that includes three parameters (float xcoord for the x-coordinate, float first and second for the y-coordinates of the obstacles) and a couple of functions. Generate() generates random values for the y-coordinate of the first obstacle, then adds 140 (for the gap) and gets the y-coordinate of the second obstacle. Create() draws the rectangles on the display window, and whenever the obstacle goes out of the screen, it gets generated again on the other side with different y-coordinates.

class Obstacle {

  float xcoord; // x coordinate
  float first = random(150,450); // top
  float second = first+140; // bottom
  
  void generate(){
    first = random(150,450);
    second = first+140;
  }
 
  void create(){
    fill(#4caf50);
    noStroke();
    rect(xcoord, 0, 80, first);
    rect(xcoord, second, 80, height);
    fill(#9bf09b);
    rect(xcoord+5, 0, 80-10, first-5);
    rect(xcoord+5, second+5, 80-10, height);
   
    if(xcoord < -80) { // if out of screen
      xcoord = width+230;
      generate();
      score++;
    }
  }
}
Movement:

Instead of making the bird move inside the display window, I used an infinite side-scrolling in which the character is static whereas the background moves from the right to the left. 

To add movement to the obstacles, I am decreasing their x-coordinate by a constant amount (speed) inside the draw() loop.

Obstacle obstacle1= new Obstacle(); // generate first obstacle
Obstacle obstacle2= new Obstacle(); // generate second obstacle
Obstacle obstacle3= new Obstacle(); // generate third obstacle
Obstacle obstacle4= new Obstacle(); // generate fourth obstacle

  obstacle1.xcoord=width+(width/3)/2-40; // x coordinate of the first obstacle
  obstacle2.xcoord=width+((width/3)/2-40)+(width/3);  // x coordinate of the second obstacle
  obstacle3.xcoord=width+((width/3)/2-40)+(width/3)*2;  // x coordinate of the third obstacle
  obstacle4.xcoord=width+((width/3)/2-40)+(width/3)*3;  

// create all obstacles
    obstacle1.create();
    obstacle2.create();
    obstacle3.create();
    obstacle4.create();
    // move obstacles
    obstacle1.xcoord -= speed;
    obstacle2.xcoord -= speed;
    obstacle3.xcoord -= speed;
    obstacle4.xcoord -= speed;
Collisions:

To flag when the avatar touches one of the obstacles, I first used an ellipse to limit the area of the avatar, then checked if it overlapped with the obstacle. Meaning that the x and y coordinates of the avatar would be inside the obstacle area.

// check if avatar touches the obstacles
  if((avatar_ycoord-25<=obstacle1.first && obstacle1.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle1.second&& obstacle1.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle2.first && obstacle2.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle2.second && obstacle2.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle3.first && obstacle3.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle3.second && obstacle3.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle4.first && obstacle4.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle4.second && obstacle4.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  // check if avatar goes out of display window
  if(avatar_ycoord>height || avatar_ycoord<0){
    stop=true;
  }
Gravity:

The gravity is set to 3 and pulls the avatar down constantly. But when the user clicks on the button, the avatar makes a quick jump and its y-coordinate is affected.

Arduino:

On the Arduino ide, I am reading the state of the buttons, and sending them to processing using Serial.println().

int inPin = 3;  // green pushbutton
int inPin2 = 4; // yellow pushbutton
int val = 0;
int val2 = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("0,0");
}
void loop() {
  while (Serial.available()) {
    if (Serial.read() == '\n') {
       val = digitalRead(inPin); // green push button state
       val2 = digitalRead(inPin2); // yellow push button state
       // send the state of the push button to processing
       if (val == HIGH && val2== HIGH) {         
          Serial.println("1,1");
       }
       else if (val == HIGH && val2== LOW) {         
          Serial.println("1,0");
       }
       else if (val == LOW && val2== LOW) {         
          Serial.println("0,0");
       }
       else if (val == LOW && val2== HIGH) {    
          Serial.println("0,1");
       }
    }
  }
}
End game: 

When a collision occurs, or the avatar goes out of the screen, the game ends.

Restart game: 

To restart the game, the user has to click on the second push button.

View post on imgur.com

Code:
Processing:
import processing.serial.*;
Serial myPort;
PImage img; // avatar img

int speed = 3; // speed of game
int avatar_xcoord = 70; // avatar's x coordinate
int avatar_ycoord = 0;  // avatar's y coordinate
int gravity = 3; // gravity effect
boolean stop = false; // boolean var to end game
int score; // score
boolean clicked= false; // boolean var to restard game

Obstacle obstacle1= new Obstacle(); // generate first obstacle
Obstacle obstacle2= new Obstacle(); // generate second obstacle
Obstacle obstacle3= new Obstacle(); // generate third obstacle
Obstacle obstacle4= new Obstacle(); // generate fourth obstacle

void setup(){
  size(900,600);
  obstacle1.xcoord=width+(width/3)/2-40; // x coordinate of the first obstacle
  obstacle2.xcoord=width+((width/3)/2-40)+(width/3);  // x coordinate of the second obstacle
  obstacle3.xcoord=width+((width/3)/2-40)+(width/3)*2;  // x coordinate of the third obstacle
  obstacle4.xcoord=width+((width/3)/2-40)+(width/3)*3;  // x coordinate of the fourth obstacle
  // Get response from Arduino
  printArray(Serial.list());
  String portname=Serial.list()[1];
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  // load image for avatar
  img = loadImage("avatar.png");
}

void serialEvent(Serial myPort){
  String s=myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null){
    int values[]=int(split(s,','));
    // jump effect for the avatar
    if (stop==false && values[0]==1) {avatar_ycoord -= gravity*0.6;}
    // restart game
    else if (stop==true && values[1]==1) {clicked=true; print("hi");}
  }
  myPort.write("\n");
}

void draw(){
  // check if avatar touches the obstacles
  if((avatar_ycoord-25<=obstacle1.first && obstacle1.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle1.second&& obstacle1.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle2.first && obstacle2.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle2.second && obstacle2.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle3.first && obstacle3.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle3.second && obstacle3.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle4.first && obstacle4.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle4.second && obstacle4.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  // check if avatar goes out of display window
  if(avatar_ycoord>height || avatar_ycoord<0){
    stop=true;
  }
  
  background(#cdeefd);
  // if game ends
  if (stop==true){
    background(#cdeefd);
    textSize(25);
    stroke(#9bf09b);
    noFill();
    strokeWeight(8);
    rect(width/2-200, height/2-150, 400, 300);
    strokeWeight(4);
    // display score
    text("Game Over!", width/2-60, height/2-70);
    text("Score: " + score, width/2-40, height/2-70 + 40);
    text("Click on yellow", width/2-75, height/2-70 + 80);
    text("button to replay!", width/2-85, height/2-70 + 120);
    // restart game and reset all parameters
    if (clicked){
      stop=false;  // reset boolean var
      clicked=false; // reset boolean var
      score=0; // reset score
      obstacle1.xcoord=width+(width/3)/2-40; // x coordinate of he first obstacle
      obstacle2.xcoord=width+((width/3)/2-40)+(width/3); // x coordinate of he second obstacle
      obstacle3.xcoord=width+((width/3)/2-40)+(width/3)*2; // x coordinate of he third obstacle
      obstacle4.xcoord=width+((width/3)/2-40)+(width/3)*3; // x coordinate of he fourth obstacle
      avatar_xcoord= 70; // x coordinate of avatar
      avatar_ycoord= 0; // y coordinate of avatar
    }
  }
  // if game starts
  if (stop==false){
    // create all obstacles
    obstacle1.create();
    obstacle2.create();
    obstacle3.create();
    obstacle4.create();
    // move obstacles
    obstacle1.xcoord -= speed;
    obstacle2.xcoord -= speed;
    obstacle3.xcoord -= speed;
    obstacle4.xcoord -= speed;
    // display the avatar
    image(img, avatar_xcoord-30, avatar_ycoord-35, 80, 70);
    avatar_ycoord += gravity;
    fill(0);
    textSize(12);
    // display the score
    text("Score: " + score, 820, 20);
  }
}

class Obstacle {

  float xcoord; // x coordinate
  float first = random(150,450); // top
  float second = first+140; // bottom
  
  void generate(){
    first = random(150,450);
    second = first+140;
  }
 
  void create(){
    fill(#4caf50);
    noStroke();
    rect(xcoord, 0, 80, first);
    rect(xcoord, second, 80, height);
    fill(#9bf09b);
    rect(xcoord+5, 0, 80-10, first-5);
    rect(xcoord+5, second+5, 80-10, height);
   
    if(xcoord < -80) { // if out of screen
      xcoord = width+230;
      generate();
      score++;
    }
  }
}
Arduino:
int inPin = 3;  // green pushbutton
int inPin2 = 4; // yellow pushbutton
int val = 0;
int val2 = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("0,0");
}
void loop() {
  while (Serial.available()) {
    if (Serial.read() == '\n') {
       val = digitalRead(inPin); // green push button state
       val2 = digitalRead(inPin2); // yellow push button state
       // send the state of the push button to processing
       if (val == HIGH && val2== HIGH) {         
          Serial.println("1,1");
       }
       else if (val == HIGH && val2== LOW) {         
          Serial.println("1,0");
       }
       else if (val == LOW && val2== LOW) {         
          Serial.println("0,0");
       }
       else if (val == LOW && val2== HIGH) {    
          Serial.println("0,1");
       }
    }
  }
}

 

Leave a Reply