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.
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"); } } } }