Making a Web-Based Flappy Bird Clone with p5.js | Part 2

Jimmy Lam
JavaScript in Plain English
6 min readJun 24, 2021

--

Recap

This is part 2 of a tutorial series where I will be walking you through how to make a flappy bird clone with p5.js. In part 1, I introduced the basic concepts of p5.js and shown you how to render the background object into the p5.js canvas. You can find part 1 here if you have not completed it. If you are here to continue on then let’s get started.

The ground object — faking infinity

First, let’s get take a look at the ground object. In Flappy Bird, the ground is actually the object that moves horizontally backward, which creates the illusion of the bird flying forwards. Now the tricky part here is how to make it seems like the ground can go on infinitely.

Let’s think about all the ways that we can do this. Can we actually have an infinite ground going through the screen? Well, maybe but you need a really freaking long image. As one can imagine, an image infinitely long is not very practical in any game. So how do we make a finite object go on infinitely? Well, we actually don’t really need much because the frame that we define in part 1 is not infinite and we can hide what’s going on off the frame.

The first way we can fake infinity is that we can start with 2 ground objects that are both as wide as the canvas. The first one starts out wholly on the screen and the second is off the screen right next to the first. We can then start to move the ground until the second fits wholly on the screen and the first is off the screen. Then, we can remove the first and then add a new one off the screen behind the second one and then repeat… infinitely.

Now, this is great but we can do even better. Think about it, do we really need to remove the first one? That’s a lot of annoying array manipulation that I’m too lazy to do. Can we just make the first one appear right in the back as soon as it moves past the screen? Of course you can and that is exactly what we are going to do.

Make a new file called Ground.js and don’t forget to include the script inside of the HTML file — before the index script.

In index.js, let's define the GROUND_HEIGHT global variable since we will be using that variable in a couple of other places. We will also load up the ground image in the preload function — in this case, I defined it as BASE_IMG.

// index.js
const WIN_WIDTH = 400;
const WIN_HEIGHT = WIN_WIDTH * 1.62;
const GROUND_HEIGHT = 110;
let BG_IMG;
let BASE_IMG;
function preload() {
BG_IMG = loadImage("assets/bg.png");
BASE_IMG = loadImage("assets/base.png");
}

Now to make the actual ground object, in Ground.js define the object as follows:

// Ground.jsfunction Ground() {
this.velocity = 5;
this.x1 = 0;
this.x2 = width;
this.y = height — GROUND_HEIGHT;
// width and height references the canvas's dimension provided my p5
this.update = () => {
this.x1 -= this.velocity;
this.x2 -= this.velocity;
if (this.x1 <= -width) {
this.x1 = width;
}
if (this.x2 <= -width) {
this.x2 = width;
}
};
this.show = () => {
// Just as before, we must explicitly tell p5 to render the ground object
image(BASE_IMG, this.x1, this.y, width, BASE_IMG.height);
image(BASE_IMG, this.x2, this.y, width, BASE_IMG.height);
};
}

The y property of the ground (or the vertical position) is the height of the canvas minus the height of the ground. This is because p5 defines a coordinate system in which the bigger the y-position, the lower the object — kinda weird, I know. It should also be noted that the bigger the x-position, the more right the object will be on the canvas.

In the show function, the ground is rendered by calling the image function provided by p5. Here, we are showing both images at once. As you might be able to see, from how we call the function, only the x position of each object is updated while all the other variables stay the same.

In the update function — which again will be running every rendering cycle — updates the x-position by subtracting a variable we will be calling velocity. Note that velocity does not have to be 5, it’s just that from my own experimentation I think 5 seems to be a good speed but you can certainly experiment yourself by changing this variable. The interesting logic is that if one of the ground objects is fully off the screen, we just make it update back to the other side of the screen by setting the x-position equals to the width of the screen.

// index.js...let BG_IMG;
let BASE_IMG;
let ground; //the global ground variablefunction preload() {
BG_IMG = loadImage("assets/bg.png");
BASE_IMG = loadImage("assets/base.png");
}
function setup() {
createCanvas(WIN_WIDTH, WIN_HEIGHT);
ground = new Ground(); // Initialize the new ground here
}
function draw() {
background(BG_IMG);
ground.update();
ground.show();
}
What you should have now

The pipe object — another way of faking infinity

Now let’s move on to the pipe object. Again, here we also want to fake infinity as the pipes will keep coming out as long the game is not over. Of course, we can use a similar strategy as the ground. Here, however, we will be initializing the pipes at different heights to make the game more interesting so let’s use the first strategy discussed earlier where we will be removing the pipe as soon as it moves off-screen and add a “fresh” pipe to our pipe array.

// Pipe.js
function Pipe() {
this.SCALE = 1.75;
this.velocity = 5; //velocity should be the same as the ground
this.gap = 200; // distance between the top and bottom pipe
this.x = width;
this.y = random(200, 400);

//The height that the pipe will have should be random and it's x position will start out from the right of the screen
this.scaledWidth = this.SCALE * PIPE_UP_IMG.width;
this.scaledHeight = this.SCALE * PIPE_UP_IMG.height;
this.topPosition = this.y — this.scaledHeight — this.gap; this.show = () => {
image(PIPE_UP_IMG, this.x, this.y, this.scaledWidth, this.scaledHeight);

image(PIPE_DOWN_IMG, this.x, this.topPosition, this.scaledWidth, this.scaledHeight);
};
this.update = () => {
this.x = this.x — this.velocity;
};
this.isOff = () => {
return this.x < -this.scaledWidth;
};
}

In Pipe.js, we include the logic to update a single pipe object. The show function just shows the top and bottom pipe and the update method simply moves the pipes left by the velocity amount. Finally, we define an isOff method to tell us whether this particular pipe has moved off-screen on the left side.

Now that we have encapsulated the logic of a pipe, we can then work on showing multiple pipes. In this particular case, I wrote this logic in the index file but you can extract this same logic to a separate file called Pipes.js or so.

// index.js...
let PIPE_UP_IMG;
let PIPE_DOWN_IMG;
let ground; //the global ground variable
pipes = []; // the global pipes array
function preload() {
...
PIPE_UP_IMG = loadImage("assets/pipeUp.png");
PIPE_DOWN_IMG = loadImage("assets/pipeDown.png");
}
function setup() {
createCanvas(WIN_WIDTH, WIN_HEIGHT).touchStarted(jumpEvent);
ground = new Ground(); // Initialize the new ground here
}
function draw() {
background(BG_IMG);
pipesUpdate()
pipes.forEach(pipe => pipe.show())
//pipes must be shown before the ground so that the ground is rendered on top of the pipe
ground.update();
ground.show();
}
function pipesUpdate() {
if (frameCount % 60 === 0) {
const pipe = new Pipe();
pipes.push(pipe);
}
pipes.forEach((pipe, idx) => {
pipe.update();
if (pipe.isOff()) {
pipes.splice(idx, 1);
}
}

In the pipesUpdate function, you can see the if statement in which I add a new pipe to the array. frameCount is a p5 object which keeps track of how many frames have been rendered from the start of the canvas initialization. This is just a way of having a constant interval in which to add new pipes and a multiple of 60 frames seems to work the best for me but you can experiment with this value

Now we should have this

To be continued…

Alright, up next would be the bird itself, which will be quite a bit more complex than what we have done here. You can find part 3 here.

More content at plainenglish.io

--

--

Hi! I am a self-taught developer who love to play around with different web technologies