The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

Tutorial 5: Basic Mouse Input and Bitmap Flags

From Allegro Wiki
Jump to: navigation, search
Outdated: Doesn't match current api (edit)


Abstract

In this tutorial you will learn how to handle mouse events and process mouse input in an Allegro 5 program.

Data Files

   * hello4.c: Source code
   * hello.bmp: A bitmap needed for this example

Tutorial

As always, make sure that you have the source code in front of you while reading it. Once you are ready, proceed.

This tutorial only changes the previous one a little bit. We want to learn how to use mouse events, so instead of having the bitmap bounce around the screen and have a velocity which we can change with the keyboard, it is drawn wherever the mouse goes. You will notice that all the keyboard code is left intact. This is all there for the sole purpose of having the escape key exit. It keeps the main loop cleaner. In the future, we will place both the keyboard and the mouse code in separate files and assume they are present.

We make some new definitions in a similar manner as we did with the keyboard.

<highlightSyntax language="c"> #define MOUSE_L 0x01 #define MOUSE_L_NEW 0x02 #define MOUSE_LS (MOUSE_L | MOUSE_L_NEW) #define MOUSE_R 0x04 #define MOUSE_R_NEW 0x08 #define MOUSE_RS (MOUSE_R | MOUSE_R_NEW) #define MOUSE_M 0x10 #define MOUSE_M_NEW 0x20 #define MOUSE_MS (MOUSE_M | MOUSE_M_NEW) int mouse_b=0; int mouse_x, mouse_y; int mouse_dx, mouse_dy; </highlightSyntax>

Here the three standard mouse buttons are represented with two states, just as we did with the keyboard. However, instead of having separate variables for each button, we can store this information in just one. In reality, not all mouses have three buttons. In fact, if you are developing a game for MacOS X, you might only have one button available. Be sure to account for this when setting the default keys in your game. While three buttons are named specifically, our code will actually support up to 16 mouse buttons on compilers where an int is 32 bits. This should probably be more than enough for anyone. If a button higher than number 16 is used, it will just be ignored.

Again, I have used hex values to denote each button state. These make each state use a different bit, so each of the eight bits in mouse_b will be used as flags. I have chosen the variables mouse_x, mouse_y, and mouse_b due to habit: these are the mouse variable names in previous versions of Allegro, representing the (x, y) coordinate of the mouse on the display, as well as the button state, respectively. Also there are the variables mouse_dx and mouse_dy, which represent the change (in pixels) of motion between frames. This would be the same result one could obtain by using the Allegro 4 function get_mouse_mickeys.

Note that if you have multiple displays, this will get a little bit harrier, but sorting that out should be mostly trivial: merely have a separate set of these for each display. In this case, a mouse class might be efficient to place this code, with a separate instance for each display.

These variables will be available to your game's update function to determine the state of the mouse for each frame. The _NEW flags represent when the click is new for that frame. This way you can tell if the button was released and re-clicked between frames. In the case of many games, you will not need such information, but we are being thorough today. The _?S flags, as you can see, represent when both a button is pressed and is new.

You will see functions similar to those whice we had for the keyboard. The following function is called whenever an <highlightSyntax language="c"> ALLEGRO_EVENT_MOUSE_BUTTON_DOWN event is fired.

void mousedown(ALLEGRO_MOUSE_EVENT *mouse) { mouse_b |= (MOUSE_LS) << ((mouse->button-1)*2); } </highlightSyntax> Yikes, what is this doing? The value of the button variable is 1 for the left button, 2 for the right, and 3 for the middle. This is standard. Any other buttons will use numbers higher than these. The way we organized the bits, the first two are used for MOUSE_L and MOUSE_L_NEW, the third and fourth for MOUSE_R and MOUSE_R_NEW, etc. What is happening here is that if a mouse button is pressed, we use the MOUSE_L and MOUSE_L_NEW states and shift them to the left by twice the button number. So if the right button is pressed, we shift it to the right by 2, if the middle button is pressed, we shift by 4. If button 10 were pressed, we would shift it by 18. This is a quick, clean way of handling all the buttons.

A similar operation is done when the button is released. <highlightSyntax language="c"> void mouseup(ALLEGRO_MOUSE_EVENT *mouse) { mouse_b &= (~MOUSE_L) << ((mouse->button-1)*2); } </highlightSyntax> This works in the same manner, but does not unset the MOUSE_?_NEW state, because if it has not yet been read, we do not want to clear it. This value instead is cleared by the mouseupdate function, just as we did for the keyboard in the previous tutorial. Before getting to that though, let's first look at how we update the axes: <highlightSyntax language="c"> void mouseaxes(ALLEGRO_MOUSE_EVENT *mouse) { mouse_dx = mouse->x - mouse_x; mouse_dy = mouse->y - mouse_y; mouse_x = mouse->x; mouse_y = mouse->y; } </highlightSyntax> We compute the change in mouse position and store it, then grab the position straight from the event and store that as well. If this function is called each time the mouse axes change, then we will know exactly where the mouse is at any given moment. This is exactly what we will do later on in the event loop. <highlightSyntax language="c"> void mouseupdate() { mouse_b &= 0x55555555; } </highlightSyntax> 0x55555555 = 01010101010101010101010101010101 in binary. This clears all the MOUSE_?_NEW flags from every button in one step, while retaining any currently pressed status.

Getting into the heart of the example program, you will observe that the keycheck function has been cleared since the previous tutorial. This is intentional: we are no longer using the keyboard, and it would distract us from our purpose if we kept it in. We also have a smaller update function: <highlightSyntax language="c"> void update(float dt) { if(mouse_b & MOUSE_L_NEW) orientation ^= ALLEGRO_FLIP_HORIZONTAL;

else if(mouse_b & MOUSE_R_NEW) orientation ^= ALLEGRO_FLIP_VERTICAL;

else if(mouse_b & MOUSE_M_NEW) orientation ^= ALLEGRO_FLIP_HORIZONTAL | ALLEGRO_FLIP_VERTICAL; } </highlightSyntax> Remember in the previous tutorial when we mentioned the ALLEGRO_FLIP_* flags, but didn't get to use them? Now we will use them. The XOR operations we are performing here act to toggle the bits. If they were set before, they are now no longer set; if they were not set before, now they are set. So when you click the left button, the image will flip horizontally, when you click the right button it will flip vertically, and if you have a middle button, clicking it will flip both ways. Note also that it will only flip when the mouse button first goes down, and to make it flip again you must release and re-press the mouse.

Drawing is performed at the mouse position, so the draw function has been updated to reflect this. It's a little ugly, but this way the bitmap is centered around the mouse location: <highlightSyntax language="c"> void draw() { al_draw_bitmap(bmp, mouse_x-al_get_bitmap_width(bmp)/2, mouse_y-al_get_bitmap_height(bmp)/2, orientation); } </highlightSyntax> Installing the mouse and registering it as an event source follows the same style as all other components. <highlightSyntax language="c"> if(!al_install_mouse()) { printf("Error installing mouse.\n"); return 1; }

/* ... */

al_register_event_source(queue, (ALLEGRO_EVENT_SOURCE*)al_get_mouse_event_source()); </highlightSyntax> If the mouse cannot be installed, the function al_install_mouse returns zero. In general, though, this should never happen. We have five new events to handle in the event queue: <highlightSyntax language="c"> case ALLEGRO_EVENT_MOUSE_AXES: mouseaxes(&event.mouse); break;

case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: mousedown(&event.mouse); break;

case ALLEGRO_EVENT_MOUSE_BUTTON_UP: mouseup(&event.mouse); break;

case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY: printf("Mouse entered display.\n"); break;

case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY: printf("Mouse left display.\n"); break; </highlightSyntax> The first three events have already been covered with their respective functions. The other two events will be fired when the mouse enters or exits the display. (If running in a fullscreen mode, they should never be fired.) These events do not currently appear to be implemented yet for all systems, so use them with caution. When running this program, you can see if they have been implemented on your platform or not.

Finally, to clear the MOUSE_?_NEW flags, we must call the mouseupdate function every frame after processing the mouse input. I usually perform all my mouse input in the keycheck method, though you can make a mousecheck method if you would prefer to separate things.

The mouse can now be used in a similar manner as in previous versions of Allegro. Note that in some situations, the solution may be better solved by using Allegro 5's low-level functionality instead of this higher-level framework we have developed. To check if a mouse button is clicked, you can do something like the following in your keycheck routine: <highlightSyntax language="c"> if(mouse_b & MOUSE_LS) // Left button clicked if(mouse_b & MOUSE_R_NEW) // Right button down, was up previously if(mouse_b & MOUSE_M) // Middle button down, was up or down // in the previous frame if(mouse_b & MOUSE_M && mouse_b & !MOUSE_M_NEW) // Middle button down, was // down in previous frame </highlightSyntax> To compile this example, you will need to link with the Allegro 5 library as well as the allegro image (allegro_image-4.9.14) library. See the Tutorial 2: TrueType Fonts and Display Modes for more detailed compilation instructions.