Sunday, September 7, 2014

[Minix][Tutorial 11] Adding flappy and the mario pipes

Adding the bird

Download bird-0x114.bmp, edit it and place it inside the res/images folder.

Create a new class named Bird, I mean: create two files: Bird.c and Bird.h. Declare it in the Makefile.

For now, let's just try to make our little bird appear and make it fall.
Our bird will have a x and y coordinates, width, height, a vertical velocity and of course a bitmap image.
To make the little bird fall, I have set a GRAVITY. At each update, the gravity is added to the velocity, which will then be added to the bird's current y location:

Now we need to add a bird to our game state, initialize it and add the respective update, draw and delete method calls:

If you compile, install and run, you should now see flappy falling!

Making flappy jump

Now we need to make little flappy jump. You may have noticed that the bird update method receives an integer named jump. Well, my idea is: when the player presses the space bar on the keyboard, we will call this method with jump = 1, this tells flappy to jump. Otherwise, we just call flappy's update method with jump = 0.

In order to accomplish this, let's modify flappy's update method - if jump == 1, let's modify it's velocity:

Now, let's modify the game state's update method to send jump == 1 when the space bar is released:

By the way, let's change flappy's default start position:

Quick preview

Compile and run. Now when you press the space bar, flappy flies! This is how the game looks like now. Cool isn't it?

Increasing FPS

Our game might be running with a little lag. Let's change that.

In FlappyNix.c:
const int FPS = 60;
const int mouseFPSmult = 1;
In Bird.c:
const int GRAVITY = 1;
const int JUMP_VEL = -12.5;
If you now compile and run the game, it should be much smoother.

Making flappy lose

Since everything is working so good, let's go further and make flappy die when it touches the ground. In order to do that, after each update, we need to check if the bottom of the bird's sprite is below the top of the ground sprite/image - easy, right?

To start, I have created an integer groundY, because we were calculating it in the draw method - and draw methods should not contain any calculation what so ever. I initialize groundY in the constructor:

Furthermore, to check flappy's collision with the ground, I have implemented the gameOver function. For now it only checks if flappy hit the ground - I have created another function for that as well - but once we implement the moving pipes, we will also check if flappy collided with any of them in this function.

Since we are modifying the update function, and I would like to make flappy fly by pressing either the space bar or the left mouse button, let's add that option:

Compile and run the game. You should now be able to use either the space bar or the left mouse button to make flappy fly. If flappy touches the ground, you should lose and go back to the main menu.

Adding the pipes

So we are pretty much almost done! We just need to add the pipes now!

So let's do it! Create Pipe.c, Pipe.h and declare it in the Makefile. This class will represent the pipe with the little gap through which flappy is supposed to fly.

Download top-pipe-0x114.bmp and bottom-pipe-0x114.bmp. Edit and place them inside your res/images folder.

Every pipe will be described by four variables: the x and y coordinates of the top left corner of the gap, and the width and height of each half of the pipe - this is for future convenience. The pipe constructor will have two parameters: the x where the pipe should be created and the ground y coordinate - because the pipe gap location will be randomly generated, we need to know the ground location to set the limit for the random function.

Since we are going to create a lot of pipes, it is not a good idea for each pipe to have it's own images of the top and bottom parts loaded. A better approach is to load those images only once, and use them to draw every pipe, only at different positions - this is known as flyweight, yet another design pattern. We can resolve this using something like a singleton, just like we did for the mouse.

Here is what everything described above looks like:

Ok, now it's time to implement the update, draw and delete methods. I have decided to put some global variables in Utilities.h, so yeah, I have made some changes to the rest of the code - you should be just fine without even having to do them, or if you do have to do them, it will be easy. Here is how the methods I told you to implement look like, as well as the global variables I created:

Ok, let's try to test our game by adding some pipes! Add an array of three pipe pointers in the GameState struct and initialize it like so:

Create a separate function to update and draw the pipes:

Do the same to delete them. Do not forget to delete the bitmaps as well, and even more important, to NULL reassign them:

Quick preview

Compile, install and run. This is what we got so far. How awesome is that?
P.S. - I am terrible at playing this game.

Implementing the pipes generator

We have almost, almost done. We have some pipes moving, but now we have to keep them coming. After that we need to do something about the bird colliding with the pipes, and then our project is finished! So, let's go!

The first step is to change the size of the pipes array. Since the maximum number of visible pipes on the screen is four, let's make the array have five pipes.

Now we have to edit the updatePipes function: when the left most pipe moves off the screen, we have to delete it, shift every pipe on the pipes array one time to the left and finally create a new pipe at the last position of the array. It is that simple!

Try to code this for yourself and only then compare with the screenshot below.

Adding pipe collisions

Fist things first: we need a simple function to detect AABB collisions. I implemented mine in Rectangle:

After that, we need to check if flappy really hit any pipe. Remember the gameOver function where I have previously told you we were going to do that? Well, let's actually do that there and now.

I created an integer variable called flappyHitPipe that is initialized with zero (false).
Then I created a rectangle which corresponds to the bird's image limits - bRect.
I am not quite sure about this one, but I guess we need to check collisions on the first two pipes, because although the first one might be a bit off the screen, flappy might be able to hit the second pipe before the first one is deleted. I do this with a simple for cycle.
Inside the for cycle we will need to check if our flappyHitPipe flag is already true - if it is, there is no need to check for any other collisions - therefore the continue;. If the flag is still false, we create two rectangles - pRect1 and pRect2 - corresponding to the top and bottom halves of the pipe being analysed; we then use the colliding function from Rectangle to check if the bird rectangle - bRect - is colliding either with pRect1 or pRect2 and update the flappyHitPipe flag accordingly. Then, inside the for cycle, I delete pRect1 and pRect2; outside the for cycle I delete the bRect. Finally, I return the flappyHitPipe flag. Here is everything explained above translated to code:

And here is a demonstration of flappy going through the pipes, against the pipes and against the floor. The collisions are working marvelously:

Although, you may have noticed a bug! If we press fast enough to make flappy fly really high, it won't hit the pipes because the pipes are not that high, and you will be able to keep flappy going as long as you wish:

There is a really simple way to solve this: make the collision rectangle of the top half of the pipe start for example at y = -500 and do not let flappy fly higher than this. Highlighted in the following screenshot are the parts of the code I had to modify to resolve this bug:

The end

Phew, what a long and dangerous journey this has been!

These tutorials were great to write and I have learned a lot from them. I really like writing these tutorials, they test my patience. I just hope you have learned anything from them, even if it was just a little bit. The time has come for me to say good bye to this tutorial series and ship on to other projects.

If you would like to get in touch with me directly, go here.

Back to index

Click here to go back to the index post.

No comments:

Post a Comment