The Allegro Wiki is migrating to github at

Difference between revisions of "Tutorial 4: Introduction to Bitmaps and Keyboard Input"

From Allegro Wiki
Jump to: navigation, search
(One intermediate revision by the same user not shown)
Line 1: Line 1:
{{outdated|Doesn't match current api}}

Latest revision as of 12:58, November 17, 2010

Outdated: Doesn't match current api (edit)


Here we will finally load and display bitmap images. We will also use the keyboard to move the bitmap around the screen, by setting up an interface similar to the one used in previous versions of Allegro

Data Files

   * hello3.c: Source code
   * hello.bmp: A bitmap needed for this tutorial


In continuation of this tutorial series, we will now start getting more into the building blocks of games: bitmaps. Another thing which we briefly touched on was input. Here I will also show you all you need to know for an efficient method of input handling. It may seem like a lot at first, but you can put it in a separate file and not worry too much about it afterwards.

Make sure you have the source code in front of you! First, we are including a new file in this program.

<highlightSyntax language="c"> #include <allegro5/allegro_image.h> </highlightSyntax>

This file will include Allegro 5's iio (image i/o) library, which we will be using to load bitmaps. This library can load bmp, jpg, pcx, png, and tga files without the need for any external libraries. There is another Allegro 5 library which can load more formats, called icodec. This library requires that you have the imagemagick library installed, and will not be covered in this tutorial. It can load all formats supported by imagemagick.

Moving on in the source file, you'll see some global variable definitions.

<highlightSyntax language="c"> float bmp_x=0, bmp_y=0; float bmp_dx = 24, bmp_dy = 24; ALLEGRO_BITMAP *bmp; ALLEGRO_DISPLAY *display; </highlightSyntax>

Since in this tutorial we will finally use additional functions, we define some global variables. You may want to enclose these in a separate struct to keep them organized. For now it is okay to be lazy and leave them floating out in global space, but I must warn you, particularly if you are a novice programmer, that organizing your code is never a bad idea.

We have floating-point values for the (x, y) coordinates of the bitmap we will be drawing, as well as the velocity in the x and y direction, denoted by dx and dy. Finally we have an ALLEGRO_BITMAP pointer to hold the bitmap data and ALLEGRO_DISPLAY to hold the display pointer.

The first thing we want to do is look at the keyboard input section. If you are itching to see how to draw a bitmap, feel free to skip ahead, but I encourage you to read this section as well. I have made a couple definitions to assist in our value assignments.

<highlightSyntax language="c"> #define KEYPRESSED 0x1 #define KEYNEW 0x2 #define KEYREPEAT 0x4 char key[256]; </highlightSyntax>

First KEYPRESSED is defined as the value 1, which has a binary representation of 00000001, and KEYNEW with binary representation 00000010. Note that each uses one bit to represent the number. We will be storing these values in the key array, which holds 8-bit values for each entry. This way we can use each bit to represent a different status. There are five more bits which we will not be using, so we could in fact have performed some bit magic to save memory. However, 256 bytes is hardly anything to worry about. Note that due to the way keyboards are made, there can be at most 256 different keycodes. This way we can be sure our array won't access an out of range value anytime in the near future.

The keyboard event handler will be sending signals for when a key was pressed and when a key was released. We will catch these events and fill our key array appropriately with KEYPRESSED, KEYNEW, and KEYREPEAT flags. We use the KEYPRESSED state to denote if a key is pressed. The KEYNEW state denotes if the keypress is new since the last frame. KEYREPEAT will only be flagged if KEYPRESSED is also flagged, and denotes an operating system repeat event. We will have to manually set these flags, but that is hardly any extra work. The following functions will all be called by the event handler:

<highlightSyntax language="c"> void keydown(ALLEGRO_KEYBOARD_EVENT *kb) { key[kb->keycode] = KEYPRESSED | KEYNEW; } </highlightSyntax>

Here, if a key is depressed, then we set the kb->keycode entry in the key array to be both pressed and new.

<highlightSyntax language="c"> void keyup(ALLEGRO_KEYBOARD_EVENT *kb) { key[kb->keycode] &= ~KEYPRESSED; } </highlightSyntax>

When a key is released, we remove the keypressed state, but keep the keynew status. That way, if the key has not yet been processed, our game can still observe that the key had been pressed.

<highlightSyntax language="c"> void keyrepeat(ALLEGRO_KEYBOARD_EVENT *kb) { key[kb->keycode] |= KEYREPEAT; } </highlightSyntax>

Most operating systems will send repeat signals as well if the key is held down. When processing things such as text input from the user, you will probably want to use these signals and the KEYNEW flag, and completely ignore the KEYPRESSED flag. (Another example would be the menu for a game, where you want to cycle through a discrete set of options.)

<highlightSyntax language="c"> void keyupdate() { int i; static int val = ((KEYNEW | KEYREPEAT) << 24) | ((KEYNEW | KEYREPEAT) << 16) | ((KEYNEW | KEYREPEAT) << 8) | KEYNEW | KEYREPEAT;

for(i=0; i<64; i++) ((int*)key)[i] &= ~val; } </highlightSyntax>

This is the function we must call every frame after processing the input. It looks a little complicated at first, but is actually quite simple when we look at it. The actual work is done in the loop on the last line. That marks all the values in the key array to not be new keypresses. Again, this should only be called after you have done your input processing for the frame.

Above the loop, we take advantage of the knowledge that in almost every situation, an int is 4 bytes. So instead of running through the char array 256 times, we can reduce it to just 64 times. First, the static variable val stores the value we want to push on the key buffer. (By declaring it static, the assignment is performed once, instead of every time the function is called.) We are clearing the KEYNEW and KEYREPEAT flags, so that is the value. We shift it over four times so that it takes up four bytes. (First we shift it three bytes over, then two bytes, then one, then none.) Next we loop through the key array 64 times to clear it.

<highlightSyntax language="c"> void keyclear() { memset(key, 0, sizeof(*key)*256); } </highlightSyntax>

This function should be called before using the key buffer. It will make sure that the memory in the key array is zeroed. All this hard work will now pay off: processing input is a cakewalk.

<highlightSyntax language="c"> void keycheck(float dt) { if(key[ALLEGRO_KEY_UP]) bmp_dy -= 64*dt; if(key[ALLEGRO_KEY_DOWN]) bmp_dy += 64*dt; if(key[ALLEGRO_KEY_LEFT]) bmp_dx -= 64*dt; if(key[ALLEGRO_KEY_RIGHT]) bmp_dx += 64*dt; } </highlightSyntax>

We call this function once every frame. If the key array has a nonzero element at the index of the keycode, we know that the key is either down or was down between the previous frame and the current one. In this case, we are doing something similar to what we did in the previous tutorial: we update the variables with respect to the change in time. This way the acceleration when pressing an arrow key is 64 pixels per second squared. What is going on here is simple: if an arrow key is pressed, then the bitmap will accelerate in that direction.

Next we want to update all non-input-related things:

<highlightSyntax language="c"> void update(float dt) { bmp_x += bmp_dx*dt; bmp_y += bmp_dy*dt;

if(bmp_y < 0) { bmp_y = 0; bmp_dy *= -1; }

if(bmp_x < 0) { bmp_x = 0; bmp_dx *= -1; }

if(bmp_y > al_get_display_height() - al_get_bitmap_height(bmp)) { bmp_y = al_get_display_height() - al_get_bitmap_height(bmp); bmp_dy *= -1; }

if(bmp_x > al_get_display_width() - al_get_bitmap_width(bmp)) { bmp_x = al_get_display_width() - al_get_bitmap_width(bmp); bmp_dx *= -1; } } </highlightSyntax>

In this function we do just that. First we apply the bitmap velocity, again with respect to dt. Then we check to make sure that our bitmap is still on the screen. If it hits an edge, it's direction reverses. Also, if the bitmap happens to leave the boundaries of the screen, we force it back in.

You'll notice four new functions:

<highlightSyntax language="c"> al_get_display_height al_get_display_width al_get_bitmap_height al_get_bitmap_width </highlightSyntax>

The first two return the dimensions of the currently active display. Since we are only using one display for now, we don't need to worry about first setting the active display. The second two return the dimensions of an ALLEGRO_BITMAP object. These are very useful functions to know, you will probably use them a lot in the future.

Now that we have ensured that the bitmap is updated every frame, we need to draw it. This is actually the easiest part.

<highlightSyntax language="c"> void draw() { al_draw_bitmap(bmp, bmp_x, bmp_y, 0); } </highlightSyntax>

In this function, we perform all of our drawing. For us, we just want to draw the bitmap and nothing more. al_draw_bitmap takes as arguments the bitmap to draw, the x and y coordinates, and some flags. The currently supported flags are the following:

<highlightSyntax language="c"> ALLEGRO_FLIP_HORIZONTAL ALLEGRO_FLIP_VERTICAL </highlightSyntax>

These will ... well, I think you can guess what these flags will do. They can be logically ORed together to flip both vertically and horizontally. These will not be used in this tutorial, but instead are saved for use in the next one, Basic Mouse Input and Bitmap Flags. Also, I would like to step out here and gloat that I implemented the use of these flags in the OpenGL driver. Okay, enough of that nonsense, back to the tutorial.

Right! Let's put these functions all together now in the main function. You'll first notice a new initialization function.

<highlightSyntax language="c"> al_init_image_addon()(); </highlightSyntax>

This is for initializing the iio library, which if you recall from the top of this document, we will use for loading bitmaps. Personally, I would recommend saving all your bitmaps as png files. They offer both an alpha channel for transparency and compression to minimize the size of your data.

<highlightSyntax language="c"> bmp = al_load_bitmap("hello.bmp"); if(!bmp) { printf("Error loading hello.bmp\n"); return 1; } </highlightSyntax>

The bitmap is loaded with this function. This is the same function you would use to load any of the file formats metioned at the beginning of this article. Note that bmp is an ALLEGRO_BITMAP* object. If for whatever reason the bitmap could not be loaded, a null pointer will be returned.

Next we start the loop:

<highlightSyntax language="c"> keyclear(); while(!key[ALLEGRO_KEY_ESCAPE]) { </highlightSyntax>

Notice that first we want to clear the key buffer before we ever access it. Otherwise we could get unexpected results. Particularly, keys might appear pressed when they really haven't been. The loop this time is repeating until the escape key is pressed. With the new keyboard handler, this can easily be performed as you see above.

Next we process the event types in a switch, behaving differently for different types.

<highlightSyntax language="c"> case ALLEGRO_EVENT_KEY_DOWN: keydown(&event.keyboard); break; </highlightSyntax>

For a key down event, we call the keydown function and pass it the keyboard object. Since we have already covered what this function does, it will not be repeated.

<highlightSyntax language="c"> case ALLEGRO_EVENT_KEY_UP: keyup(&event.keyboard); break; </highlightSyntax>

Similarly, for key up events, we call our key up function.

<highlightSyntax language="c"> case ALLEGRO_EVENT_KEY_REPEAT: keyrepeat(&event.keyboard); break; </highlightSyntax>

Again, for the operating system repeat events, we call the appropriate function. Finally we have the timer tick event.

<highlightSyntax language="c"> case ALLEGRO_EVENT_TIMER: time = al_current_time(); dt = time-old_time; if(dt > 0.25) dt = 0.25;

keycheck(dt); keyupdate(); update(dt);

if(time - event.timer.timestamp <= 1.0/refresh_rate) { al_clear_to_color(black); draw(); al_flip_display(); }

old_time = time; break; </highlightSyntax>

We have already covered the first few lines here in a previous tutorial. This time, we first process the keyboard state, as we saved it while processing key events. This function gets passed dt because it will be updating variables based on the time which passed between frames. After calling this function we should be done with processing the keyboard for this frame, so we call keyupdate. Recall that this will mark all key events as not being new keypresses. Again, this is done so that if a key is pressed and then released between frames, it will still get marked for processing.

After the keyboard state is checked, the general update function is called. This updates all logic which does not depend on the keyboard. Finally, we are in the drawing section. First the backbuffer is cleared to black, then all drawing code is performed, the buffers are flipped, and we're done.

To compile this example, you will need to link with the Allegro 5 library as well as the allegro_image library (allegro_image-4.9.14). See the Tutorial 2: TrueType Fonts and Display Modes for more detailed compilation instructions. Remember to append the version to the end of the library!