Pages

Showing posts with label Minix. Show all posts
Showing posts with label Minix. Show all posts

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.

Saturday, September 6, 2014

[Minix][Tutorial 10] Creating the game state, adding a moving background and ground

In this tutorial we are going to focus on the development of the game itself, the game state.

Creating the game state

Start by creating GameState.h and GameState.c - do not forget to declare it in the Makefile.

Let's start with small goals, ok? First let's try to put the background scrolling correctly through the screen.
Download this background, export it as a .bmp using R5 G6 B5 with GIMP and place it inside your res/images folder.

The game state should have four methods that every state should have - new, update, draw and delete. Our game state object, for now, should have an integer telling if the state is done/finished, a bitmap pointer to the background and an integer corresponding to the background horizontal position - because the background is going to be moving to the left. Knowing this, try to code GameState.h for yourself. Afterwards, compare it to the my GameState.h below.



Now open GameState.c and try to code everything for yourself - come on, you can do it!

I'll give a little help: in the initialization you have to initialize the background position and actually load the background image; in the update method, you have to update the background position, to make the background slowly move left, and when it has moved enough, reset the position to zero; in the draw method, you just need to draw the background at the right position; and finally in the delete method just delete the background image and free the state.

Easy, right? You should end up having something like this:



Ok, so now we have the game state ready, we just need to add it to every mechanism (function) of our state machine.

Add a new state identifier:



In FlappyNix.c, include the state header file:



Add the state update and draw method calls to each switch case:



Update the change state function as well:



Also update check if state is done and delete current state methods. Notice that I've slightly modified the switch case for the main menu state as well.



Quick preview

If we have done everything correctly so far, when we run the program and press play, we should see the background infinitely scrolling to the left. If we press the ESC key on the keyboard, we should be able to return to the main menu as well. So go ahead, compile, install and run your program. It should look something like this:


Adding the ground

Now that we have the background moving, let's add a moving ground as well. The ground will be moving faster than the background to create a sense of perspective.

Download the ground image, edit it like you have done before and place it inside the res/images folder.
Just like what we did to add the background image, do the same to add the ground image: add a bitmap pointer for the actual image as well as an integer to hold it's horizontal position. After that, initialize them just like you did with the background image:



After that, you just need to update the ground position - I created a separated function for that; draw the ground - make sure you draw it after the background, otherwise it won't show up; add the delete ground bitmap statement in the state delete method. Here's what all of this should look like:



Quick preview

Here is what our game looks like now - the background is moving as well as the ground (but four times faster):


Back to index

Click here to go back to the index post.

[Minix][Tutorial 9] Creating a state machine and a main menu

In this tutorial we are going to create the main menu for our game!

Adding a path builder

First, get menu-background-0x114.bmp, export it as a .bmp with GIMP like I have explained in the previous tutorial and paste it in your res/images folder. You can delete the test.bmp from the previous tutorial.



Think of every time we are going to load an image. Isn't it boring to hard code the entire image path? What if in the future we decide to change the images folder location? Man, what a mess to have to change every single hard coded image path! Wouldn't it be better to have a function that given the image name, would build the entire image path? Yes it would! So, let's do it!

Add the getImagePath function to Utilities, just like shown below.



Now edit line 24 in FlappyNix.c, we should now be able to load images like this. Pretty useful, right?



Compile, install and run the program. This is how it looks like now:



Implementing a state machine

We've got to a point where we will start dealing with states. Every program/game has different states: main menu state, play state, pause state, game over state, etc. In order to manage each state correctly the state machine was invented. It is responsible for state initialization, update, drawing and deletion. So, let's make a few changes and implement our own state machine.

Creating the main menu state

First, let's create the main menu state structure.

Create MainMenuState.c and MainMenuState.h and do not forget to declare it in the Makefile.



To simplify button representation, we will need a Rectangle class. Declare it in the Makefile as well. Here is how it looks, it is pretty simple:



Now let's implement the main menu. Here is MainMenuState.h:



Long story short: there is an integer to tell if the state is complete/done; a background image; two buttons - play and exit - each with an integer that tells if the mouse is hovering them and a Rectangle representing their boundaries; an integer representing the action performed when the state became done - either play or exit could have been pressed.

Below is the implementation of the main menu. Paste it to MainMenuState.c:
#include "MainMenuState.h"

#include "Graphics.h"
#include "Keyboard.h"
#include "Mouse.h"
#include "Utilities.h"

MainMenuState* newMainMenuState() {
    MainMenuState* state = (MainMenuState*) malloc(sizeof(MainMenuState));

    state->done = 0;
    state->background = loadBitmap(getImagePath("menu-background"));

    // these numbers are just meant to create the buttons boundaries
    double w = .075, hi = .44, hf = hi + .12;
    int x1 = getHorResolution() / 2 - getHorResolution() * w;
    int x2 = getHorResolution() / 2 + getHorResolution() * w;
    int y1 = getVerResolution() * hi;
    int y2 = getVerResolution() * hf;
    state->playButton = newRectangle(x1, y1, x2, y2);
    state->mouseOnPlay = 0;

    hi = .64, hf = hi + .12;
    y1 = getVerResolution() * hi;
    y2 = getVerResolution() * hf;
    state->exitButton = newRectangle(x1, y1, x2, y2);
    state->mouseOnExit = 0;

    return state;
}

int updateMainMenuState(MainMenuState* state, unsigned long scancode) {
    int draw = 0;

    // if ESC has been pressed, quit
    if (scancode == KEY_DOWN(KEY_ESC)) {
        state->action = 1;
        state->done = 1;
    }

    // if mouse is inside the play button rectangle (boundaries)
    if (mouseInsideRect(state->playButton))
        state->mouseOnPlay = 1;
    else
        state->mouseOnPlay = 0;

    // if mouse is inside the exit button rectangle (boundaries)
    if (mouseInsideRect(state->exitButton)) {
        state->mouseOnExit = 1;

        // and left mouse button has been released
        if (getMouse()->leftButtonReleased) {
            state->action = 1;
            state->done = 1;
        }
    } else
        state->mouseOnExit = 0;

    return draw;
}

void drawMainMenuState(MainMenuState* state) {
    drawBitmap(state->background, 0, 0, ALIGN_LEFT);

    if (state->mouseOnPlay)
        drawRect(state->playButton, YELLOW);
    else if (state->mouseOnExit)
        drawRect(state->exitButton, YELLOW);
}

void deleteMainMenuState(MainMenuState* state) {
    deleteBitmap(state->background);
    deleteRectangle(state->playButton);
    deleteRectangle(state->exitButton);

    free(state);
}

Contained in the above code snippet are function calls like: mouseInsideRect(state->exitButton) and drawRect(state->playButton, YELLOW)

The first is implemented in Mouse.c:



The second is implemented in Graphics.c:



Ok, now that we have implemented the main menu state, we need to make a lot of changes to FlappyNix and transform it in a state machine.

Modifying FlappyNix to implement a state machine


This is how FlappyNix.h looks like now:



We have deleted the test image, as well as our previous yellow rectangle coordinate. Make sure you get rid of anything directly associated to those things from FlappyNix.c as well.

We now have a void* state; which is a generic pointer to the current state of our program. What is a generic pointer? If you have just read the information on that link you might now know that, since there are different states - game over, main menu, etc - but they all fit under the "state" category, we can represent an abstract state with a void pointer/generic pointer. That state can either become the main menu state or the game over state, get it?

Since there is no way to know the type of that state - because it is just a void pointer - I have added an enumerator that indicates which state is the current one. Every time the state changes, we will update this currentState indicator - this will enable us to call the right methods on the right state and not make any stupid mistakes like calling drawGameOverState() when the active state is MainMenuState. I hope this was not too confusing and you understood everything.

Since I have made a lot of changes to FlappyNix.c I am going to paste the whole source file here and try to explain it after.
#include <minix/drivers.h>
#include "FlappyNix.h"

#include "Graphics.h"
#include "Keyboard.h"
#include "Mouse.h"

#include "MainMenuState.h"

const int FPS = 25;
const int mouseFPSmult = 3;

void checkIfStateIsDone(FlappyNix* game);
void deleteCurrentState(FlappyNix* game);

FlappyNix* startFlappyNix() {
    FlappyNix* flappy = (FlappyNix*) malloc(sizeof(FlappyNix));

    // subscribing devices
    flappy->IRQ_SET_KB = kb_subscribe_int();
    flappy->IRQ_SET_MOUSE = subscribeMouse();
    flappy->IRQ_SET_TIMER = subscribeTimer();

    // resetting timer frequency
    timerSetSquare(0, mouseFPSmult * FPS);

    // initializing other variables
    flappy->scancode = 0;
    flappy->currentState = MAIN_MENU_STATE;
    flappy->state = newMainMenuState();

    // finish initialization
    flappy->done = 0, flappy->draw = 1;
    flappy->timer = newTimer();

    return flappy;
}

void updateFlappyNix(FlappyNix* flappy) {
    int ipc_status, r = 0;
    message msg;

    resetTimerTickedFlag(flappy->timer);

    if (driver_receive(ANY, &msg, &ipc_status) != 0)
        return;

    if (is_ipc_notify(ipc_status)) {
        switch (_ENDPOINT_P(msg.m_source)) {
        case HARDWARE:
            // KEYBOARD interruption
            if (msg.NOTIFY_ARG & flappy->IRQ_SET_KB)
                flappy->scancode = readKBCState();

            // TIMER interruption
            if (msg.NOTIFY_ARG & flappy->IRQ_SET_TIMER)
                timerHandler(flappy->timer);

            // MOUSE interruption
            if (msg.NOTIFY_ARG & flappy->IRQ_SET_MOUSE)
                updateMouse();
            break;
        default:
            break;
        }
    }

    if (flappy->timer->ticked) {
        getMouse()->draw = 1;

        if (flappy->timer->counter % mouseFPSmult == 0) {
            // update at 25 FPS
            switch (flappy->currentState) {
            case MAIN_MENU_STATE:
                updateMainMenuState(flappy->state, flappy->scancode);
                break;
            default:
                break;
            }

            flappy->scancode = 0;
            flappy->draw = 1;
        }
    }

    checkIfStateIsDone(flappy);
}

void drawFlappyNix(FlappyNix* flappy) {
    switch (flappy->currentState) {
    case MAIN_MENU_STATE:
        drawMainMenuState(flappy->state);
        break;
    default:
        break;
    }
}

void stopFlappyNix(FlappyNix* flappy) {
    deleteCurrentState(flappy);
    deleteMouse();
    deleteTimer(flappy->timer);

    // unsubscribe devices
    kb_unsubscribe_int();
    unsubscribeMouse();
    unsubscribeTimer();

    free(flappy);
}

void changeState(FlappyNix* game, State newState) {
    // deleting current state
    deleteCurrentState(game);

    // changing current state
    game->currentState = newState;

    // creating new state
    switch (game->currentState) {
    case MAIN_MENU_STATE:
        game->state = newMainMenuState();
        break;
    }

    game->draw = 1;
}

void checkIfStateIsDone(FlappyNix* game) {
    switch (game->currentState) {
    case MAIN_MENU_STATE:
        if (((MainMenuState*) (game->state))->done) {
            int action = ((MainMenuState*) (game->state))->action;

            switch (action) {
            case PLAY_CHOSEN:
                game->done = 1;
                break;
            case EXIT_CHOSEN:
                game->done = 1;
                break;
            }
        }
        break;
    default:
        break;
    }
}

void deleteCurrentState(FlappyNix* game) {
    switch (game->currentState) {
    case MAIN_MENU_STATE:
        deleteMainMenuState(game->state);
        break;
    }
}

So, I will try to explain what is going on...

The prototypes at lines 13 and 14 are there just for visibility purposes. Their actual implementation is near the bottom of the file, but they are called throughout the file inside other functions, so in order to make them visible for the whole file, I have placed their declarations at lines 13 and 14.

startFlappyNix

The previous yellow rectangle related stuff was removed.
The current state and the actual state pointer initialization has been added.

updateFlappyNix and drawFlappyNix

Not much has changed, we now have a switch dependent on the current state enumerator. Again, this enables us to call the right methods for the currently active state. I have explained this just a couple of lines above.

changeState

This method is called when we want to change from one state to another.

For example:
In the next tutorial we will be adding the play state. When we implement it and then press the play button on the main menu, this method will be called and what will happen is the following:
the active state (in this case, main menu state) will be deleted, the current state identifier will be updated and then, according to this identifier, the new state will be created. Since we pressed the play button, the play state is expected to be generated.

checkIfStateIsDone

This method is called right at the end of the update method. It checks if the active state is done/finished.
If it is, depending on the active state, a certain action is performed: the program can be terminated or there can take place a change of state. And yes, if you are wondering if this is the only place where changeState() - the method previously described - can (or should) be called, you are indeed correct.

deleteCurrentState

This one is easy: according to the current state identifier, the correct delete method for the active state is called.

Main menu preview

So that's it! Phew, this was a long post! Here is a quick preview of what your program should look like, in particular the main menu and it's buttons being hovered:

Back to index

Click here to go back to the index post.