Adding Background Support to the Game Engine


Because you're not going to worry about creating scrolling backgrounds, that leaves three types of backgrounds you're going to incorporate into the game engine: solid backgrounds, image backgrounds, and animated backgrounds. The first two background types can easily coexist in a single class, whereas the third requires a custom class of its own. So, you're going to be creating two new classes that will become part of the game engine. The first class is a basic background class that encompasses solid backgrounds and image backgrounds, whereas the second is a specific type of animated background that displays a starry night sky with twinkling stars. The starry background will serve as a good enough demonstration of how to create an animated background that you should be able to create your own custom animated backgrounds without any trouble.

Creating a Basic Background Class

The basic background that supports both solid and image backgrounds is contained within the Background class. The Background class is flexible in that the same class can be used to create both kinds of backgrounds ”the decision regarding which kind of background you're creating is decided by which constructor you use. Listing 18.1 shows the code for the Background class, which reveals its overall design.

Listing 18.1 The Background Class Is Used to Create Both Solid and Image Backgrounds
 1: class Background  2: {  3: protected:  4:   // Member Variables  5:   int       m_iWidth, m_iHeight;  6:   COLORREF  m_crColor;  7:   Bitmap*   m_pBitmap;  8:  9: public: 10:   // Constructor(s)/Destructor 11:           Background(int iWidth, int iHeight, COLORREF crColor); 12:           Background(Bitmap* pBitmap); 13:   virtual ~Background(); 14: 15:   // General Methods 16:   virtual void  Update(); 17:   virtual void  Draw(HDC hDC); 18: 19:   // Accessor Methods 20:   int GetWidth()  { return m_iWidth; }; 21:   int GetHeight() { return m_iHeight; }; 22: }; 

The Background class contains several member variables that support both solid and image backgrounds. The m_iWidth and m_iHeight member variables keep track of the width and height of the background (line 5), which applies to any kind of background. The m_crColor variable stores the color of the background (line 6), and only applies to solid backgrounds. Similarly, the m_pBitmap variable stores a pointer to a Bitmap object (line 7), which is used to draw the bitmap image for an image background.

The Background class includes two constructors ”each of which corresponds to one of the two basic background types. The first constructor accepts a width and height, along with a color for the solid background (line 11). The second constructor accepts a bitmap image (line 12), which it uses as the basis for calculating the width and height of the image background.

Two familiar methods are included in the Background class for updating and drawing the background. The Update() method is called to update the appearance of the background (line 16), and in the case of the solid and image backgrounds, this method does nothing. The purpose for having the Update() method is to allow derived animated background classes to use it to update themselves . The Draw() method applies to all kinds of backgrounds, and simply accepts a device context on which to draw the background (line 17). The last two methods in the Background class are the GetWidth() and GetHeight() accessor methods, which simply return the width and height of the background (lines 20 and 21).

Now that you have an understanding of how the Background class is assembled , you can take a look at the specific code that makes it work. Listing 18.2 shows the code for the Background() constructors.

Listing 18.2 The Background::Background() Constructors Are Used to Create and Cleanup After a Background
 1: Background::Background(int iWidth, int iHeight, COLORREF crColor)  2: {  3:   // Initialize the member variables  4:   m_iWidth = iWidth;  5:   m_iHeight = iHeight;  6:   m_crColor = crColor;  7:   m_pBitmap = NULL;  8: }  9: 10: Background::Background(Bitmap* pBitmap) 11: { 12:   // Initialize the member variables 13:   m_crColor = 0; 14:   m_pBitmap = pBitmap; 15:   m_iWidth = pBitmap->GetWidth(); 16:   m_iHeight = pBitmap->GetHeight(); 17: } 

The constructors for the Background class are pretty straightforward in that they simply initialize the member variables for the class based on the arguments provided. It is important to note that the bitmap pointer is set to NULL in the first constructor (line 7), which indicates that the background is a solid background, as opposed to an image background. Similarly, the background color is set to in the second constructor (line 13) because an image background doesn't have a background color.

The Update() and Draw() methods in the Background class are responsible for updating and drawing the background. However, because neither a solid or image background is animated, the Update() method serves as more of a placeholder for future derived background classes. Listing 18.3 shows the code for these two methods.

Listing 18.3 The Background::Update() and Background::Draw() Methods Handle the Updating and Drawing of a Background
 1: void Background::Update()  2: {  3:   // Do nothing since the basic background is not animated  4: }  5:  6: void Background::Draw(HDC hDC)  7: {  8:   // Draw the background  9:   if (m_pBitmap != NULL) 10:     m_pBitmap->Draw(hDC, 0, 0); 11:   else 12:   { 13:     RECT    rect = { 0, 0, m_iWidth, m_iHeight }; 14:     HBRUSH  hBrush = CreateSolidBrush(m_crColor); 15:     FillRect(hDC, &rect, hBrush); 16:     DeleteObject(hBrush); 17:   } 18: } 

As I mentioned earlier, the Update() method is designed to be overridden with animation code in derived background classes, so it makes sense to leave it blank in this class (line 3). However, the Draw() method makes up for this lack of action by drawing a solid background or image background, depending on the state of the bitmap pointer (line 9). If the bitmap pointer is not NULL , you know that this is an image bitmap, so the Draw() method is called on the Bitmap object (line 10). Otherwise, you know this is a solid bitmap so a solid rectangle is drawn in the color of the background (lines 13 “16). The code in the Draw() method reveals how two types of backgrounds are supported in a single class.

Creating an Animated Background Class

If the Background class didn't get you too excited, hopefully you'll be intrigued by the StarryBackground class, which represents an animated background of a starry sky. The starry sky could also be interpreted as a starry view of outer space, which makes the background more flexible in terms of how you use it in games .

The StarryBackground class is derived from the Background class, which means that it inherits member variables and methods from the Background class. However, the StarryBackground class requires its own constructor, as well as its own version of the Update() and Draw() methods. The StarryBackground class also adds some new member variables to the equation, which are used to manage the twinkling stars in the background. Listing 18.4 contains the code for the StarryBackground class.

Listing 18.4 The StarryBackground Class Is Used to Create an Animated Background of a Starry Sky
 1: class StarryBackground : Background  2: {  3: protected:  4:   // Member Variables  5:   int       m_iNumStars;  6:   int       m_iTwinkleDelay;  7:   POINT     m_ptStars[100];  8:   COLORREF  m_crStarColors[100];  9: 10: public: 11:   // Constructor(s)/Destructor 12:           StarryBackground(int iWidth, int iHeight, int iNumStars = 100, 13:             int iTwinkleDelay = 50); 14:   virtual ~StarryBackground(); 15: 16:   // General Methods 17:   virtual void  Update(); 18:   virtual void  Draw(HDC hDC); 19: }; 

The most important member variable in the StarryBackground class is m_iNumStars , which keeps track of how many stars appear on the background (line 5). This number is stored as a variable as opposed to a constant because you might want to vary the number of stars in different games. The m_iTwinkleDelay member variable is used to control how fast the stars twinkle (line 6) ”the longer the delay, the slower they twinkle. So, setting a small value for the twinkle delay causes the stars to twinkle rapidly , which isn't very realistic. You'll find out in a moment that there is a default twinkle delay that I've found to be reasonably realistic in most situations.

The stars themselves are stored in two different member variables, m_ptStars and m_crStarColors . The m_ptStars variable is an array of points that stores the location of each individual star (line 7). You still need to keep track of the color of each star, which is a shade of gray that can vary between black and bright white. The m_crStarColors array stores the color of each star and corresponds directly to the stars stored in the m_ptStars array (line 8). Notice that both arrays are created with 100 elements, which means that you can't have more than 100 stars in the starry background.

Moving right along, the constructor for the StarryBackground class accepts several arguments to describe the starry background. More specifically , it allows you to provide the width, height, number of stars, and twinkle delay for the background (lines 12 and 13). It's important to note that the number of stars and twinkle delay arguments have default values, which you might find suitable for your games. You can also use your own trial and error approach to deciding exactly how many stars you like, as well as what kind of twinkle delay results in a good look for the twinkling stars.

The constructor for the StarryBackground class is shown in Listing 18.5 and reveals how the stars are created.

Listing 18.5 The StarryBackground::StarryBackground() Constructor Is Used to Create a Starry Background
 1: StarryBackground::StarryBackground(int iWidth, int iHeight, int iNumStars,  2:   int iTwinkleDelay) : Background(iWidth, iHeight, 0)  3: {  4:   // Initialize the member variables  5:   m_iNumStars = min(iNumStars, 100);  6:   m_iTwinkleDelay = iTwinkleDelay;  7:  8:   // Create the stars  9:   for (int i = 0; i < iNumStars; i++) 10:   { 11:     m_ptStars[i].x = rand() % iWidth; 12:     m_ptStars[i].y = rand() % iHeight; 13:     m_crStarColors[i] = RGB(128, 128, 128); 14:   } 15: } 

The StarryBackground() constructor first initializes the m_iNumStars member variable, making sure not to allow it to be set any higher than 100 (line 5). The twinkle delay is then set (line 6), and the star creation process begins. The creation of the stars involves looping through the number of stars (line 9), and then setting the position of each star to a random value (lines 11 and 12), as well as setting the color of each star to a neutral color (line 13). By neutral color, I mean a shade of gray that isn't too bright or too dark.

Once the stars are created in the constructor, you can turn your attention to the Update() and Draw() methods, which are really the most important part of the StarryBackground class. Listing 18.6 contains the code for these methods.

Listing 18.6 The StarryBackground::Update() and StarryBackground::Draw() Methods Handle the Updating and Drawing of a Starry Background
 1: void StarryBackground::Update()  2: {  3:   // Randomly change the shade of the stars so that they twinkle  4:   int iRGB;  5:   for (int i = 0; i < m_iNumStars; i++)  6:     if ((rand() % m_iTwinkleDelay) == 0)  7:     {  8:       iRGB = rand() % 256;  9:       m_crStarColors[i] = RGB(iRGB, iRGB, iRGB); 10:     } 11: } 12: 13: void StarryBackground::Draw(HDC hDC) 14: { 15:   // Draw the solid black background 16:   RECT    rect = { 0, 0, m_iWidth, m_iHeight }; 17:   HBRUSH  hBrush = CreateSolidBrush(RGB(0, 0, 0)); 18:   FillRect(hDC, &rect, hBrush); 19:   DeleteObject(hBrush); 20: 21:   // Draw the stars 22:   for (int i = 0; i < m_iNumStars; i++) 23:     SetPixel(hDC, m_ptStars[i].x, m_ptStars[i].y, m_crStarColors[i]); 24: } 

The Update() method in this code is responsible for causing the stars to twinkle. This is accomplished by randomly changing the color of each star to a different shade of gray. The stars are looped through (line 5), and the twinkle delay is used as the basis for determining if the color of a star should be changed (line 6). If so, a random number is selected between 0 and 255 (line 8), and this color is used for each of the three color components to establish a new color for the star (line 9).

graphics/book.gif

When the three different components of a color (red, green, and blue) are set to the same number, the resulting color is a shade of gray. For example, the color (128, 128, 128) is medium gray. On the other hand, the color (0, 0, 0) is black, whereas (255, 255, 255) is white.


The Draw() method for the StarryBackground class is used to draw the background, and involves first drawing a solid black rectangle, followed by drawing the individual stars. The black rectangle is drawn with a call to FillRect() (line 18), which you saw earlier in the book. The more interesting code occurs near the end of the method, and consists of a loop that draws each individual star with a call to SetPixel() (lines 22 and 23). The SetPixel() function is a Win32 function that sets an individual pixel to a specified color.



Sams Teach Yourself Game Programming in 24 Hours
Sams Teach Yourself Game Programming in 24 Hours
ISBN: 067232461X
EAN: 2147483647
Year: 2002
Pages: 271

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