A Bouncing Ball


The final example program for this chapter demonstrates how to move the ball. Because sprites are supposed to move, the sprite class is able to keep track of how much it should move and in what direction. It has a member function called Movement() that your game can use to set the direction and speed of the sprite's movement. To do this, your game passes a vector object that contains x and y values to the Movement() function in its parameter list. The movement you specify is in pixels. The x and y values are values in a Cartesian coordinate system. Every computer screen, and every window on a computer screen, has an x and a y axis. Windows sets the positive x axis so that it points from left to right and the positive y axis so that it points down. This is probably different than what you did when you learned Cartesian coordinate systems, but it is valid. However, it means that location 0,0 is at the upper-left corner of the screen. This is illustrated in Figure 4.3.

Figure 4.3. The origin of the Windows screen coordinate system is at the upper-left corner of the screen.


Note

If you ever decide to do games with DirectX, you will use the Windows-style coordinate system with the origin at the upper-left corner of the screen. DirectX provides a function that helps you load a bitmap properly by inverting it when it's loaded.


The Windows-style coordinate system means that you have to invert all of your bitmaps when you load them or they will be displayed upside down. However, the sprite class takes care of that for you by inverting bitmaps when it loads them.

As you might expect, the bouncing ball program is much larger than the stationary ball program. During each frame, the program has to move the ball. It also has to check whether the ball hit the edge of the screen or went off the edge. If so, it may have to adjust the ball's position before it bounces the ball. Listing 4.3 shows how it's done.

Listing 4.3. The bouncing ball program

 1    #include "LlamaWorks2d.h" 2 3    using namespace llamaworks2d; 4 5 6    class my_game : public game 7    { 8    public: 9      bool OnAppLoad(); 10     bool InitGame(); 11     bool UpdateFrame(); 12     bool RenderFrame(); 13 14     ATTACH_MESSAGE_MAP; 15   private: 16     sprite theBall; 17   }; 18 19 20   CREATE_GAME_OBJECT(my_game); 21 22   START_MESSAGE_MAP(my_game) 23   END_MESSAGE_MAP(my_game) 24 25   bool my_game::OnAppLoad() 26   { 27     bool initOK = true; 28 29     // 30     // Initialize the window parameters. 31     // 32     init_params lw2dInitiParams; 33     lw2dInitiParams.openParams.fullScreenWindow = true; 34     lw2dInitiParams.openParams.millisecondsBetweenFrames = 0; 35 36     // This call MUST appear in this function. 37     theApp.InitApp(lw2dInitiParams); 38 39     return (initOK); 40   } 41 42   bool my_game::InitGame() 43   { 44     bool initOK; 45 46     theBall.BitmapTransparentColor(color_rgb (0.0f,1.0f,0.0f)); 47     initOK = 48       theBall.LoadImage( 49         "ball.bmp", 50         image_file_base::LWIFF_WINDOWS_BMP); 51 52     if (initOK==true) 53     { 54       theBall.X(1); 55       theBall.Y(1); 56       theBall.Movement(vector(5,3)); 57 58       bitmap_region boundingRect; 59       boundingRect.top = 0; 60       boundingRect.bottom = theBall.BitmapHeight(); 61       boundingRect.left = 0; 62       boundingRect.right = theBall.BitmapWidth(); 63       theBall.BoundingRectangle(boundingRect); 64     } 65     else 66     { 67       ::MessageBox( 68         NULL, 69         theApp.AppError().ErrorMessage().c_str(), 70         NULL, 71         MB_OK | MB_ICONSTOP | MB_SYSTEMMODAL); 72 73     } 74     return (initOK); 75   } 76 77 78   bool my_game::UpdateFrame() 79   { 80     bool updateOK = true; 81 82   vector ballVelocity = theBall.Movement(); 83   theBall.Move(); 84   if (theBall.X()<=0) 85   { 86     theBall.X(1); 87     ballVelocity.X(ballVelocity.X() * -1); 88   } 89   else if (theBall.X() + theBall.BoundingRectangle(). right >= 90             theApp.ScreenWidth()) 91   { 92       theBall.X(theApp.ScreenWidth() - 93           theBall.BoundingRectangle().right - 1); 94       ballVelocity.X(ballVelocity.X() * -1); 95   } 96   else if (theBall.Y()<=0) 97   { 98       theBall.Y(1); 99       ballVelocity.Y(ballVelocity.Y() * -1); 100  } 101  else if (theBall.Y() + theBall.BoundingRectangle(). bottom >= 102           theApp.ScreenHeight()) 103  { 104      theBall.Y(theApp.ScreenHeight() -105 theBall.BoundingRectangle().bottom - 1); 106      ballVelocity.Y(ballVelocity.Y() * -1); 107  } 108  theBall.Movement(ballVelocity); 109 110  return (updateOK); 111 } 112 113 114 bool my_game::RenderFrame() 115 { 116 117    theBall.Render(); 118    return true; 119  } 

Like the previous examples, this program creates a game class. If you look on lines 9 and 11 of Listing 4.3, you'll see that there are two new member functions. The first is called OnAppLoad(). The OnAppLoad() function is required by the game engine. However, if your game class doesn't have it, the engine provides one.

Warning

I cannot overemphasize the importance of calling theApp.InitApp() in your game class's OnAppLoad() function. Your OnAppLoad() function must pass a variable of type init_params to the theApp.InitApp() function. When it does, the application object's InitApp() function copies all of the initialization information your OnAppLoad() function into the application object so that the application object can use it to set up Windows, OpenGL, and OpenAL properly. Without a call to theApp.InitApp(), LlamaWorks2D cannot initialize your game properly.


The code for OnAppLoad() begins on line 25. The purpose of this function is to set the initialization parameters for Windows, OpenGL, and OpenAL. The game engine contains reasonable defaults for any initialization values you don't set. LlamaWorks2D calls the OnAppLoad() function when it starts the program but before it initializes the program's window, OpenGL, or OpenAL. This enables you to set initialization information before the program gets very far along. On line 33 of Listing 4.3, the OnAppLoad() function tells the program to run in full-screen mode. Most games run in full-screen mode; I recommend you consider using it for your games.

Just before it returns, the OnAppLoad() function calls another function. Remember that LlamaWorks2D hides a lot of the hard work of Windows programming in an object that represents the program itself. That object is available to all functions in the program. The name of the object is theApp. Anytime your functions need access to the application object, they access it through the name theApp. On line 37, you can see that the OnAppLoad() function calls one of the application object's member functions, InitApp(). Your OnAppLoad() function must call InitApp(). If it doesn't, your program will compile, but it won't run. It just prints an error message saying that it can't initialize Windows and then quits.

After the program initializes itself, it must initialize your game. To do so, it calls your game class's InitGame() function. We saw this function in the previous example. However, this version, which starts on line 42, is more complex. The first thing it does is load the ball's bitmap image, which is stored in the file Ball.bmp. If Ball.bmp is not in the same directory as the game itself, the InitGame() function won't be able to load it. As a result, the game won't initialize properly.

The InitGame() function sets the transparent color for the ball's bitmap on line 46. It then calls the sprite class's LoadImage() function to load the bitmap.

If the InitGame() function successfully loads the ball's bitmap image, it sets the ball's starting location on the screen. The location is specified in terms of x and y coordinates. The sprite class has functions called X() and Y() that set the values of the sprite's x,y location. InitGame() calls them on lines 5455.

Next, the program specifies the direction and speed of the ball's movement. It does this in a nifty shorthand way. The sprite::Movement() function requires a vector as its only parameter. One way to pass it a vector is to create a vector variable, as in the following code fragment:

 vector ballMovement; ballMovement.X(5); ballMovement.Y(3); theBall.Movement(ballMovement); 


Warning

Loading bitmaps is probably the slowest task in your game. It should not be done while the player is in a level. It will slow or stop gameplay momentarilyand players don't like that a bit. To solve this problem, load all your bitmaps in the InitGame() function.


This style of programming is easy to read, but it's not necessarily the most efficient. Notice that the statements on lines 5463 of Listing 4.3 only use the vector to specify the ball's movement. It's not used for anything else. If the InitGame() function created a variable like the code fragment shown here, it would not reuse the variable. It would be more efficient if InitGame() could specify the vector without having to create a variable for it. That would save some memory as the program executes.

Rather than create a variable that will be used in only one statement, we can save memory and CPU time by taking advantage of a special feature of C++ called nameless temporary variables. You create a nameless temporary variable by calling a class's constructor. That's what's happening on line 56. The InitGame() function creates a nameless vector variable by calling the vector constructor and passing it the x and y values for the vector.

Tip

It's a good idea to use nameless temporary variables when you're passing information in a parameter list and you won't use the variable anywhere else. This approach makes your code shorter. You may also save memory and CPU time because many compilers recognize nameless temporary variables and perform special optimizations on them. Some of them can actually optimize it right out of existence so that the program never needs to allocate memory for it.


The ball's movement is specified in pixels. Line 56 says that the ball moves 5 pixels to the right and 3 pixels down on every frame of animation.

Next, InitGame() sets the ball's bounding rectangle on lines 5863. The bounding rectangle is the smallest rectangle that will enclose the ball. As you'll see shortly, the game uses the bounding rectangle to test for when the ball hits the edges of the screen. This technique is called collision detection. Collision detection is exactly what the name implies; it's the act of detecting when sprites hit something. Every time the ball moves, the game tests to see if it hit anything. Collision detection is an extremely common and necessary task in games. It can become very complex, especially in 3D games. However, 2D games can usually detect collisions between sprites just by testing whether their bounding rectangles overlap. It's a fast, easy test that doesn't require a lot of CPU time. I'll demonstrate it throughout the remaining chapters in this book.

The InitGame() function sets the ball's bounding rectangle by creating and initializing a variable of type bitmap_region. This is a type that's defined in LlamaWorks2D. If you look at the code on lines 5962, it appears that InitGame() is accessing a class's public member data. However, the bitmap_region type is not a classit's a structure. Therefore, its members are public by default. Your functions can access them directly. Lines 5962 set the top, bottom, left, and right edges of the sprite's bounding rectangle. These values are distances away from the upper-left corner of the sprite's bitmap image. Setting the top and left both at 0 means that the upper-left corner of the bounding rectangle is at the upper-left corner of the bitmap. Setting the bottom and right edges to the height and width means that the bottom-right corner of the bounding rectangle is at the bottom-right corner of the bitmap. This does not have to be the case, but often, this is exactly how you will set up your sprites.

On line 63, the InitGame() function calls the sprite class's BoundingRectangle() function and passes it the variable boundingRect. This sets the sprite object's bounding rectangle.

If the bitmap was not loaded correctly, the InitGame() function executes the code on lines 6771. These statements use the standard Windows MessageBox() function to display an error message. If you want more information on MessageBox(), you can find it on the Web at www.msdn.microsoft.com.

Tip

As the statements on lines 6771 show, your game can call Windows functions whenever it needs to. This does not interfere in any way with OpenGL or LlamaWorks2D.


Beginning on line 78, Listing 4.3 presents the UpdateFrame() function. This is another new function in the my_game class. Every game class must have this function. As you've probably guessed by now, LlamaWorks2D provides one for you if you do not write it yourself. The purpose of the UpdateFrame() function is to calculate the new positions for each sprite in the frame. To do that, it first gets the ball's movement vector and stores it in a variable.

The UpdateFrame() function next calls the sprite::Move() function on line 83. This tells the ball to move based on its movement vector. After the Move() function finishes, it is possible that the ball will have moved off the screen or collided with the edge of the screen. The series of chained if-else statements beginning on line 84 tests for that. This style of if-else statement puts a new if onto the end of an else. It's kind of tricky, but almost every C++ programmer does it. You'll have to get used to it if you want to work with other C++ programmers. The statement on line 89 says that if the ball didn't hit the right edge of the screen, UpdateFrame() checks to see if it hit the left edge. The chained if-else statement continues and tests for collisions with the top and bottom of the screen as well.

The statement on line 84 tests the position of the ball's upper-left corner against the left edge of the screen, which is located at an x position of 0. If you look at the statement on line 84, you'll see that it gets the ball's x position by calling the sprite::X() function. We used that same function on line 54 to set the x position of the ball.

What's up here? The sprite class's X() and Y() functions set the x and y coordinates in the InitGame() function. Now they're getting those values?

The answer is yes. We can have functions do this in C++. Programming this way cuts down on the number of function names you have to remember when you're using LlamaWorks2D. I'll show you how to make functions like this for your own classes in chapter 5, "Function and Operator Overloading".

Tip

It's very common to add very small fudge factors to the position of objects in games to simplify code. This is a technique that you should not hesitate to use when appropriate.


If the ball has gone off the left edge of the screen, it's time to bounce the ball. The statement on line 86 sets the ball's x position to 1. That shifts the ball over 1 pixel away from the edge of the screen so that the UpdateFrame() function won't bounce the ball again in the next frame of animation. No one will notice that the position of the ball has shifted by 1 pixel. However, shifting the ball over 1 pixel makes the program simpler because it doesn't have to keep track of whether there was a bounce on the previous frame. This technique is a way of adding a fudge factor that keeps your life simple.

Note

The simple method of bouncing a ball shown here assumes that there is no friction. As a result, the speed of the ball does not change; only its direction changes. If we were doing a more realistic game, we would probably need to make the ball slow down with each bounce.


The statement on line 87 bounces the ball by multiplying its x direction by -1. Because multiplying by -1 makes positive numbers negative and negative numbers positive, this has the effect of switching the x direction of the ball. This easy technique for bouncing sprites in games is illustrated in Figure 4.4.

Figure 4.4. The ball's x direction reverses when you multiply it by -1.


Figure 4.4 shows the ball hitting the left edge of the screen after it has been moving down and to the left. Multiplying the ball's x direction by -1 points it in the opposite direction. The y direction remains unchanged. So in the next frame of animation, the ball continues to move down. However, it is now moving to the right.

If the ball has not hit the left edge of the screen, the UpdateFrame() function tests to see whether it hit the right edge of the screen on lines 8990. It does so by adding the width of the bounding rectangle to the ball's x position. That calculates the position of the right edge of the bitmap. If the right edge has gone off the right edge of the screen, the statements on lines 9294 set the ball so that it is 1 pixel away from the right edge of the screen and then bounce the ball.

Note

If you compile and run this program, you'll need to press Alt+F4 when you want to quit. If you don't, the program keeps running indefinitely.


Lines 96100 test to see whether the ball has hit the top of the screen. If so, it sets the ball's y position to 1 and then bounces the ball. On lines 101107, UpdateFrame() performs the test for the bottom of the screen. If the ball has hit the bottom, it moves the ball so that it is 1 pixel above the bottom of the screen and then bounces the ball.



Creating Games in C++(c) A Step-by-Step Guide
Creating Games in C++: A Step-by-Step Guide
ISBN: 0735714347
EAN: 2147483647
Year: N/A
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net