Published: August 30, 2008
by Richard G. Baldwin
File: Allegro00125
This lesson is part of a series (see Resources) designed to teach you how to use Allegro to do graphics programming in C++. My purpose in writing the series is to provide lecture and lab material for a course titled Game Development Using C++ that I teach at Austin Community College in Austin, Texas. However, if you have stumbled upon this series and you find it useful, you are welcome to study it.
I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.
I recommend that you also study the other lessons in my extensive collection of online programming tutorials. You will find a consolidated index at www.DickBaldwin.com.
In this lesson, we will dig a little deeper into animation using a simple example in which a green ball bounces around inside a box as shown in Figure 1. The ball continues to bounce until the user presses the Esc key, at which time the program terminates.
Figure 1. Screen shot of output from BouncingBallInBox.
When the ball hits a wall of the box, it doesn't always bounce in the complementary angle that you might normally expect. Instead it selects one of the four possible directions listed above on a random basis and attempts to bounce in that direction. If it selects a direction that would drive it into the wall, it simply pauses there and tries again during the next iteration of the animation loop.
The ball continues to bounce around until the user presses the Esc key, at which time the program terminates.
Beginning of the source code
I will explain the source code for this program in fragments. A complete listing is provided in Listing 6 near the end of the lesson.
The program begins in Listing 1, which declares and initializes a number of global variables. The purpose of most of the variables is explained by the comments in Listing 1. I will explain the purpose of the others later.
Listing 1. Beginning of the source code.
#include <allegro.h> int width = 220;//width of box int height = 440;//height of box int radius = 5;//radius of ball int x = 110;//initial position of ball int y = 220;//initial position of ball int tempX; int tempY; //Keep track of direction of motion here. //0= northwest 1 = southwest, 2 = northeast, //3 = southeast int dir; |
Beginning of the main function
The main function begins in Listing 2.
Listing 2. Beginning of the main function.
int main(){ allegro_init(); install_keyboard(); set_color_depth(32); set_gfx_mode(GFX_AUTODETECT_WINDOWED,width,height,0,0); //Seed the random number generator and set the initial // direction based on a random number. srand(time(NULL)); dir = rand() % 4; |
The first four statements in Listing 2 do the following:
I have explained all of that before in this series, so I won't repeat that explanation in this lesson.
Set initial direction of motion
The last two statements in Listing 2 are new to this lesson.
The value stored in the variable named dir is used to control the direction of motion for the green ball in Figure 1. Only four directions are allowed, and the correlation between the value of dir and the direction of motion is given below:
A random number generator
At the beginning of the program, and whenever the ball hits a wall, a random number generator function named rand is called to determine the direction in which the ball will travel. Some of what the C++ Reference Library (see Resources) has to say about the rand function is presented in Figure 2.
Figure 2. Description of the rand function.
Returns a pseudo-random integral number in the range 0 to RAND_MAX. This number is generated by an algorithm that returns a sequence of apparently non-related numbers each time it is called. This algorithm uses a seed to generate the series, which should be initialized to some distinctive value using srand. RAND_MAX is a constant defined in <cstdlib>. Its default value may vary between implementations but it is granted to be at least 32767. A typical way to generate pseudo-random numbers in a determined range using rand is to use the modulo of the returned value by the range span and add the initial value of the range: ( value % 100 ) is in the range 0 to 99 Notice though that this modulo operation does not generate a truly uniformly distributed random number in the span (since in most cases lower numbers are slightly more likely), but it is generally a good approximation for short spans. |
Seeding the random number generator
The information about needing to seed the random number generator in Figure 2 is very important. Presumably, if we didn't do that, we would get the same sequence of pseudo-random number every time we run the program.
Listing 2 calls the time function (to get the current time) and passes the return value from that function as the seed in a call to srand. Figure 3 presents some of what the C++ Reference Library (see Resources) has to say about the srand function, including information about using the return value from time as the seed value.
Figure 3. Description of the srand function.
void srand ( unsigned int seed ); Initialize random number generator The pseudo-random number generator is initialized using the argument passed as seed. For every different seed value used in a call to
srand,
the pseudo-random number generator can be expected to generate a
different succession of results in the subsequent calls to
rand. If seed is set to 1, the generator is reinitialized to its initial value and produces the same values as before any call to rand or srand. In order to generate random-like numbers, srand is usually initialized to some distinctive value, like those related with the execution time. For example, the value returned by the function time (declared in header <ctime>) is different each second, which is distinctive enough for most randoming needs. Parameters
|
Set a random initial direction of motion
After seeding the random number generator in Listing 2, the program calls the rand function, using the modulus operator approach described in Figure 2 to generate a random number between 0 and 3 inclusive. This value is assigned to the variable named dir to cause the initial direction of motion of the ball in Figure 1 to be random.
Completion of the main function
Listing 3 presents the completion of the main function.
Listing 3. Completion of the main function.
//Loop until the user presses the Esc key. while( !key[KEY_ESC]){ moveBall(); }//end while loop return 0; }//end main END_OF_MAIN(); |
The code in Listing 3 uses a while loop as an animation loop, causing the program to run until the user presses the Esc key. When the user presses the Esc key, the loop terminates, causing the main function to return, which in turn causes the program to terminate.
During each iteration of the loop in Listing 3, the function named moveBall is called once to attempt to move the green ball shown in Figure 1 by an incremental amount in the direction specified by the value of the variable dir. (I say attempt because the ball won't always move if it has collided with one of the walls.)
Beginning of the moveBall function
The purpose of the moveBall function is to move the ball one increment along one of four directions, and to deal with collisions between the ball and the walls.
Listing 4. Beginning of the moveBall function.
void moveBall(){ //Save current location of ball. tempX = x; tempY = y; switch(dir){ case 0: //Direction is northwest. if((x <= radius) || (y <= radius)){ dir = rand() % 4;; }else{ //No collision, set new location for the ball --x; --y; }//end else break; |
Save the current coordinates of the ball
The moveBall function in Listing 4 begins by saving the current coordinates of the center of the ball for later use in erasing the current image of the ball.
A switch statement
Listing 4 shows the beginning of a switch statement that uses the current value of the direction variable named dir to select from one of four blocks of code for execution. (I gave you the correlation between the value of dir and the direction of motion earlier.) Each case in the switch statement deals with one of the four possible current values of dir.
Motion towards the northwest
The code shown in Listing 4 deals with dir equal to 0, meaning that the current direction of motion for the ball is toward the northwest. Depending on where the ball began its journey towards the northwest, it will eventually collide with either the left wall or the top wall of the box in Figure 1.
Deal with a possible collision with a wall
The current coordinates of the ball are given by the values of x and y, which specify the location of the center of the ball. However, we don't want to know when the center of the ball collides with the wall because by that point in time, half the ball will have been buried in the wall. Rather, we want to know when the outside edge of the ball collides with the wall. Therefore, our collision detection scheme must take the radius of the ball into account.
An artificial boundary
One way to think of this is that there is an artificial boundary inside the wall and separated from the wall by a distance equal to the radius of the ball. When the center of the ball collides with the artificial boundary, the outer edge of the ball will have collided with the actual wall. This is effectively how the code is implemented in the conditional clause of the if statement in Listing 4.
A collision has occurred
If the conditional clause in the if statement in Listing 4 returns true, this means that the ball has collided with either the left wall or the top wall. When that happens, Listing 4 calls the rand function to get a new direction of motion.
May not be a good choice
There is nothing in the code in Listing 4 to prevent the new direction from being the same as the old direction, or being a direction that would try to drive the ball into the wall. (I could have written code to eliminate this possibility, but I elected not to do so in the interest of simplicity.)
Consider for example the case where the current direction of motion is northwest, the ball collides with the top wall, and the new direction of motion is either northwest or northeast. This is a problem because further motion in the northwest or northeast direction would cause the ball to penetrate the wall. In this case, the ball will simply pause at the wall until the same code is executed in a subsequent iteration of the animation loop to send the ball off in a southwest or southeast direction.
The ball may pause at the wall for more than one iteration of the animation loop. However, given the repetition rate of the animation loop, assuming that the random number generator is truly random, this short pause should not be noticeable to the viewer. Therefore, I decided not to write the extra code to prevent the pause from occurring.
When no collision has occurred...
If the conditional clause in the if statement in Listing 4 returns false, a collision with the wall has not occurred. In this situation, the ball should continue moving in a forward direction. The x and y coordinates for the center of the ball are both decremented by one in Listing 4, which will make it appear that the ball has moved one pixel to the left and one pixel towards the top the next time the ball is drawn on the screen.
The other three cases
The code in each of the remaining three cases in the switch statement is very similar to the code in Listing 4. That code simply deals with motion toward the southwest, northeast, and southeast and possible collisions with the walls resulting from motion in those directions. Because of the similarity of the code, I won't discuss it in detail. You can view that code in Listing 6.
Draw the ball in the new location
After the code in the switch statement finishes executing, we need to erase the current image of the green ball and draw a new image of the ball in the new location. That is accomplished by the code in Listing 5.
Listing 5. Draw the ball in the new location.
acquire_screen(); circlefill (screen,tempX,tempY,radius,makecol(0,0,0)); circlefill (screen,x,y,radius,makecol( 128, 255, 0)); release_screen(); rest(5);//Delay for five milliseconds }// end moveBall function. |
Should you call acquire_screen?
The code in Listing 5 calls the acquire_screen function before drawing and calls the release_screen function after drawing. The documentation for acquire_screen states that it is a "Shortcut version of acquire_bitmap(screen)"
The documentation for acquire_bitmap states"
"Acquires the specified video bitmap prior to drawing onto it. You never need to call the function explicitly as it is low level, and will only give you a speed up if you know what you are doing. Using it wrongly may cause slowdown, or even lock up your program."
I have determined experimentally, however, that when I remove the code to acquire and release the screen from Listing 5, I experience some flashing as the ball moves across the screen. This flashing is eliminated by including the code to acquire and release the screen as shown in Listing 5. There may be a better way to eliminate the flashing, but I can't tell you exactly what it is at this time.
Erase the old ball and draw the new ball
In this program, we have a green ball moving across a black background. Therefore, it is easy to erase the old image of the ball before drawing a new image at the new location. (It would be more difficult to erase the old image if the ball were moving across a background consisting of something other than a solid color.)
Listing 5 draws a black ball at the old coordinates that were saved in tempX and tempY. That effectively erases the old image because a black ball cannot be seen in front of a black background. Then the code in Listing 5 draws a new green ball at the new location specified by the x and y coordinates produced by the code in the switch statement discussed above.
In the case of a collision...
For the case where the ball has collided with a wall, the location coordinates for the center of the ball were not modified in the switch statement shown in Listing 4. Therefore, the ball will simply be drawn in the same location as before, still touching the wall, during the current iteration of the animation loop.
If the new direction obtained from the random number generator in Listing 4 sends it away from the wall, it will be drawn one pixel away from the wall during the next iteration of the animation loop. If not, it will simply be drawn in the same location during each iteration of the animation loop until the random number generator returns a value that drives it away from the wall.
Delay and return
Listing 5 inserts a five-millisecond delay and then returns control from the moveBall function, allowing the animation loop shown in Listing 3 to execute another iteration.
As mentioned earlier, the animation loop continues to execute until the user presses the Esc key, at which time the loop terminates, the main function returns, and the program terminates.
In this lesson, I explained the concept of an animation loop that continues to iterate until the user takes some particular action, such as pressing the Esc key.
I introduced you to the use of the C++ pseudo-random number generator function named rand, and showed you how to use that function to obtain a uniformly distributed (approximately) series of values that fall between a specified lower and upper bound.
I showed you how to seed the random number generator with the current time to cause it to return a different sequence of pseudo-random numbers each time the program is run.
Finally, I showed you how to cause a green ball to move at a constant speed in one of four directions in a box, how to detect a collision with a wall, and how to bounce off the wall in a randomly-selected direction.
Listing 6. Source code for BouncingBallInBox project.
/*Project BouncingBallInBox Displays a ball bouncing around inside of a box. The ball is allowed to move in only four directions: northeast, southeast, southwest, or northwest. When the ball hits a wall of the box, it doesn't bounce in the direction that would normally be expected. Instead it selects one of the four possible directions and bounces in that direction. The ball continues to bounce until the user presses the Esc key, at which point the program terminates. */ #include <allegro.h> int width = 220;//width of box int height = 440;//height of box int radius = 5;//radius of ball int x = 110;//initial position of ball int y = 220;//initial position of ball int tempX; int tempY; //Keep track of direction of motion here. //0= northwest 1 = southwest, 2 = northeast, //3 = southeast int dir; void moveBall(){ //Save current location of ball. tempX = x; tempY = y; switch(dir){ case 0: //Direction is northwest. if((x <= radius) || (y <= radius)){ //Ball has collided with either the left wall or // the top wall. //Get a new direction. Note that if the new // direction is the same as the old one, control // will come back to here to get still another // direction the next time the functionis called. dir = rand() % 4; }else{ //No collision, set new location for the ball --x; --y; }//end else break; case 1: //Direction is southwest. if(((x <= radius) || (y >= (height - radius)))){ //get a new direction dir = rand() % 4; }else{ //set new location for the ball --x; ++y; }//end else break; case 2: //Direction is northeast. if(((x >= (width - radius)) || (y <= radius))){ //get a new direction dir = rand() % 4; }else{ //set new location for the ball ++x; --y; }//end else break; case 3: //Direction is southeast if((((x >= (width - radius)) || (y >= (height - radius))))){ //get a new direction dir = rand() % 4; }else{ //set new location for the ball ++x; ++y; }//end else }//end switch //Erase the current image of the ball and draw a new // image at the modified location coordinates. acquire_screen(); circlefill (screen,tempX,tempY,radius,makecol(0,0,0)); circlefill (screen,x,y,radius,makecol( 128, 255, 0)); release_screen(); rest(5);//Delay for five milliseconds }// end moveBall function. int main(){ allegro_init(); install_keyboard(); set_color_depth(32); set_gfx_mode(GFX_AUTODETECT_WINDOWED,width,height,0,0); //Seed the random number generator and set the initial // direction based on a random number. srand (time(NULL)); dir = rand() % 4; //Loop until the user presses the Esc key. while( !key[KEY_ESC]){ moveBall(); }//end while loop return 0; }//end main END_OF_MAIN(); |
Copyright 2008, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-