Adding Animated Sprite Support to the Game Engine


In order to add animated sprite support to the game engine, a few changes have to be made to existing code. These changes impact the Bitmap and Sprite classes, but strangely enough they don't directly impact the GameEngine class at all. The next couple of sections show you exactly what changes need to be made to the Bitmap and Sprite classes to integrate frame-animated sprites into the game engine.

Drawing Only Part of a Bitmap

The first change is related to the Bitmap class, which represents a bitmap image. As you know, the Sprite class already relies on the Bitmap class to handle the details of storing and drawing a sprite's visual appearance. However, the Bitmap class is currently only designed to draw a complete image. This presents a problem for animated sprites because the frame images are all stored in a single bitmap image. It is therefore necessary to modify the Bitmap class so that it supports drawing only a part of the bitmap.

If you recall, the Bitmap class includes a Draw() method that accepts a device context and an XY coordinate to indicate where the bitmap is to be drawn. The new method you now need is called DrawPart() , and it accepts the familiar device context and XY coordinate, as well as the XY position and width and height of the frame image within the overall sprite bitmap. So, the new DrawPart() method basically allows you to select and draw a rectangular subsection of a bitmap image. Listing 17.1 contains the code for the Bitmap::DrawPart() method.

Listing 17.1 The Bitmap::DrawPart() Method Supports Frame Animation by Allowing You to Draw Only a Part of a Sprite's Bitmap Image
 1: void Bitmap::DrawPart(HDC hDC, int x, int y, int xPart, int yPart,  2:   int wPart, int hPart, BOOL bTrans, COLORREF crTransColor)  3: {  4:   if (m_hBitmap != NULL)  5:   {  6:     // Create a memory device context for the bitmap  7:     HDC hMemDC = CreateCompatibleDC(hDC);  8:  9:     // Select the bitmap into the device context 10:     HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap); 11: 12:     // Draw the bitmap to the destination device context 13:     if (bTrans) 14:       TransparentBlt(hDC, x, y, wPart, hPart, hMemDC, xPart, yPart, 15:         wPart, hPart, crTransColor); 16:     else 17:       BitBlt(hDC, x, y, wPart, hPart, hMemDC, xPart, yPart, SRCCOPY); 18: 19:     // Restore and delete the memory device context 20:     SelectObject(hMemDC, hOldBitmap); 21:     DeleteDC(hMemDC); 22:   } 23: } 

This code is very similar to the original Draw() method in the Bitmap class, except that it doesn't take for granted the parameters of the source bitmap image. More specifically , the DrawPart() method uses the new arguments xPart , yPart , wPart , and hPart to single out a frame image within the bitmap image to draw. These arguments are passed in to the TransparentBlt() function to draw a frame image with transparency (lines 14 and 15) and the BitBlt() function to draw a frame image without transparency (line 17).

It's important to note that the Draw() method still works fine for drawing sprites that don't involve frame animation. In fact, the Draw() method has now been modified to use the DrawPart() method, which helps to conserve code since the methods are very similar. Listing 17.2 shows the code for the new Bitmap::Draw() method, which is surprisingly simple.

Listing 17.2 The New Bitmap::Draw() Method Simply Calls the DrawPart() Method to Draw a Bitmap Image In Its Entirety
 1: void Bitmap::Draw(HDC hDC, int x, int y, BOOL bTrans,  2:   COLORREF crTransColor)  3: {  4:   DrawPart(hDC, x, y, 0, 0, GetWidth(), GetHeight(), bTrans, crTransColor);  5: } 

Notice that the new Draw() method just calls the DrawPart() method and passes in arguments that result in the entire bitmap image being drawn (line 4). You could've kept the original Draw() method without modifying it, but it's wasteful to duplicate code without a good reason. More importantly, if you ever need to change the manner in which a bitmap is drawn, you only have to modify one section of code, the DrawPart() method.

Animating the Sprite Class

Now that you've altered the Bitmap class to support the drawing of only part of a bitmap image, you're ready to make changes to the Sprite class to support frame animation. Earlier in the hour , you worked through the design of a frame-animated sprite that involved adding several important pieces of information to a traditional unanimated sprite. The following new member variables of the Sprite class represent these pieces of information:

 int m_iNumFrames, m_iCurFrame; int m_iFrameDelay, m_iFrameTrigger; 

These member variables correspond one-to-one with the pieces of animated sprite information mentioned earlier in the hour. They are all initialized in the Sprite() constructors, as the following code reveals:

 m_iNumFrames = 1; m_iCurFrame = m_iFrameDelay = m_iFrameTrigger = 0; 

The default value of the m_iNumFrames variable is 1 because a normal sprite that doesn't use frame animation includes only one bitmap image. The other three variables are initialized with values of , which is suitable for a sprite without frame animation.

In order to create a sprite that takes advantage of frame animation, you must call the SetNumFrames() method and possibly even the SetFrameDelay() method. Listing 17.3 contains the code for the SetNumFrames() method.

Listing 17.3 The Sprite::SetNumFrames() Method Turns a Normal Sprite Into an Animated Sprite by Setting Its Number of Frames
 1: void Sprite::SetNumFrames(int iNumFrames)  2: {  3:   // Set the number of frames  4:   m_iNumFrames = iNumFrames;  5:  6:   // Recalculate the position  7:   RECT rect = GetPosition();  8:   rect.right = rect.left + ((rect.right - rect.left) / iNumFrames);  9:   SetPosition(rect); 10: } 

The listing shows how the number of frames is set by assigning the specified argument to the m_iNumFrames member variable (line 4). It's also important to recalculate the sprite's position because it is no longer based upon the entire image size (lines 7 “9).

The SetFrameDelay() method goes hand in hand with the SetNumFrames() method, and it's even easier to follow:

 void SetFrameDelay(int iFrameDelay) { m_iFrameDelay = iFrameDelay; }; 

As you can see, the SetFrameDelay() method is a simple accessor method that sets the m_iFrameDelay member variable. Setting the number of frames and the frame delay is all that is required to turn an ordinary sprite into a frame-animated sprite. Of course, you also need to make sure that you've laid out the frame images for the sprite in the sprite's single bitmap image.

Although the SetNumFrames() and SetFrameDelay() methods are all you need to worry about from the perspective of game code, work still needs to be done to get the Sprite class ready to use its new member variables. For example, the GetWidth() method needs to take into consideration the number of frames when calculating the width of the sprite image. This width is either the entire sprite image for an unanimated sprite, or the width of a single frame image for an animated sprite. Following is the code for the new GetWidth() method:

 int GetWidth() { return (m_pBitmap->GetWidth() / m_iNumFrames); }; 

Notice that this code simply divides the width of the sprite image by the number of frames. For an unanimated sprite, the number of frames is one, which means that the normal bitmap width is returned. For animated sprites, the width returned reflects the width of an individual frame image.

A new helper method is included in the Sprite class to handle the details of updating the current animation frame. This method is called UpdateFrame() and is shown in Listing 17.4.

Listing 17.4 The Sprite::UpdateFrame() Method Updates the Sprite's Current Animation Frame
 1: inline void Sprite::UpdateFrame()  2: {  3:   if ((m_iFrameDelay >= 0) && (--m_iFrameTrigger <= 0))  4:   {  5:     // Reset the frame trigger;  6:     m_iFrameTrigger = m_iFrameDelay;  7:  8:     // Increment the frame  9:     if (++m_iCurFrame >= m_iNumFrames) 10:       m_iCurFrame = 0; 11:   } 12: } 

The UpdateFrame() method is called by the Update() method to update the frame image of the sprite, if necessary. The method starts off by making sure that the frame indeed needs to be updated. This check involves seeing if the frame delay is greater than or equal to , as well as if the frame trigger is less than or equal to (line 3). If both of these conditions are met, the frame trigger is reset to the frame delay (line 6) and the current frame is moved to the next frame (lines 9 and 10).

The Update() method is responsible for calling the UpdateFrame() method to make sure that the frame is updated. Listing 17.5 shows how the UpdateFrame() method is now called in the Update() method.

Listing 17.5 The Sprite::Update() Method Now Calls the UpdateFrame() Method to Update the Sprite's Animation Frame
 1: SPRITEACTION Sprite::Update()  2: {  3:   // Update the frame  4:   UpdateFrame();  5:  6:   ...  7: } 

The update of the frame is now taken care of first thing in the Update() method (line 4). I deliberately left out the remainder of the Update() code because none of it changed, and it's quite lengthy; the only code of importance to this discussion is the new call to UpdateFrame() .

The last Sprite method impacted by the addition of animation support is the Draw() method, which must now take into account the current frame when drawing a sprite. Listing 17.6 shows the new and improved Draw() method ”complete with frame animation support.

Listing 17.6 The Sprite::Draw() Method Draws the Sprite Differently Depending on Whether It Is an Animated Sprite
 1: void Sprite::Draw(HDC hDC)  2: {  3:   // Draw the sprite if it isn't hidden  4:   if (m_pBitmap != NULL && !m_bHidden)  5:   {  6:     // Draw the appropriate frame, if necessary  7:     if (m_iNumFrames == 1)  8:       m_pBitmap->Draw(hDC, m_rcPosition.left, m_rcPosition.top, TRUE);  9:     else 10:       m_pBitmap->DrawPart(hDC, m_rcPosition.left, m_rcPosition.top, 11:         m_iCurFrame * GetWidth(), 0, GetWidth(), GetHeight(), TRUE); 12:   } 13: } 

This version of the Draw() method now takes a look at the number of frames before deciding how to draw the sprite (line 7). If there is only one frame, the familiar Bitmap::Draw() method is called to draw the single bitmap image as you've been accustomed to doing (line 8). However, if there is more than one frame, the new Bitmap::DrawPart() method is used to draw only the current frame image within the sprite bitmap (lines 10 and 11). This is a critical piece of code that justifies why you needed to make the changes to the Bitmap class earlier in the hour.



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