Chapter 5. Drawing Graphical Images

We’ve all heard that a picture is worth a thousand words, and when it comes to computer games, they might be worth even more than that. Or to translate into nerd talk, a 640×480 image is worth 307,200 pixels. Okay, maybe nerd talk is something I should avoid. The point is that pictures (images) are extremely important in computer games because they provide the only means of incorporating artwork into the games. Sure, it’s nice to be able to draw lines, rectangles, and ellipses using GDI functions, but it would take a great deal of effort to draw a menacing creature out of graphics primitives. For that reason, we use graphical images as a means of visualizing graphical parts of games. This hour is all about showing you how to load and display graphical images.

In this hour, you’ll learn:

  • The fundamentals of bitmap images and why they are important in game programming

  • About the inner workings of bitmap images

  • How to develop an all-purpose bitmap class for use in games

  • How to use the bitmap class to represent slide images in a slideshow program

The Basics of Bitmap Images

Images in windows games are represented by bitmaps, which are rectangular graphics objects containing rows and columns of little squares called pixels. The name bitmap comes from the fact that the rows and columns determine how to map the pixels to the screen; the pixels themselves are composed of bits. Each pixel in a bitmap is a solid color. In fact, the only information associated with a pixel is its color. So, you can think of a bitmap as a rectangular arrangement of little colored squares. If you’re old enough to remember the popular Lite Brite toy, you definitely have an understanding of how a bitmap works. Lite Brite, which is still manufactured today by Hasbro, allows you to plug colored pegs into a black plastic grid to draw pictures.

Note

The Basics of Bitmap Images

Two types of bitmaps are supported in Windows: device-dependent bitmaps and device-independent bitmaps. Device-dependent bitmaps are stored in a manner determined by a specific device, whereas device-independent bitmaps are stored in such a way that they can be displayed on any device. All the bitmaps you work with in this book are device-independent bitmaps, which are sometimes referred to as DIBs. Just keep in mind that when I refer to “bitmaps” from here on, I’m really referring to DIBs.

You are probably familiar with bitmaps from the popular .BMP file type that is used throughout Windows. BMP is the standard image format used in Windows, and is also the format you’ll be using to work with bitmaps throughout this book. Although GIF and JPEG have certainly surpassed BMP as image formats for use on the Web, BMP is still used a great deal by Windows users. Not only that, but the BMP image format is considerably simpler and easier to work with at a programming level than GIF and JPEG. You can use the standard Paint program built into Windows to create and edit BMP files, or use Microsoft Photo Editor, which offers more features for working with photographic images.

Although there is only one bitmap image format, not all bitmap images are created equal. First of all, the bitmap image format allows you to create bitmaps with varying numbers of colors. More specifically, you can create 8-bit bitmaps that use 256 (palletized) colors or 24-bit bitmaps that use more than 16 million colors. You can also create bitmaps that use a technique known as compression to help reduce the size of the bitmap file. To help keep the code simple, the bitmaps you use throughout the book are limited to 8-bit uncompressed bitmaps. This is very important to remember because other types of bitmaps won’t work with the bitmap code you develop in this hour.

Regardless of how you create a bitmap, it ultimately ends up as a file with a .BMP file extension. To use such a bitmap in a Windows program, you have two options:

  • Read the bitmap directly from the file

  • Store the bitmap as a resource and read it from memory

In the first option, the Windows program opens the bitmap file from disk and reads in the image data. It then uses the bitmap image data to create a GDI bitmap graphics object that can be drawn to a device context. The second option involves storing a bitmap as a resource within the executable program, which means that you don’t have to include the bitmap file with the program once it is compiled. The advantage to this option is that you are able to distribute your game as a single program file. Fortunately, the bitmap class you create later in this hour supports both approaches to using bitmaps.

Peeking Inside a Bitmap

In order to use bitmaps in your games, you must have a basic understanding of how they are put together. More specifically, you need to have knowledge of the inner structure of a bitmap because you must write code that reads this structure and extracts information about the bitmap. This is necessary so that you can create a bitmap object that can be drawn. You’ll be glad to know that bitmaps aren’t too terribly complicated, as is evident in Figure 5.1.

The structure of a bitmap consists of three parts: a header, color table, and image data.

Figure 5.1. The structure of a bitmap consists of three parts: a header, color table, and image data.

The figure reveals that every bitmap consists of three basic parts:

  • Header

  • Color table

  • Image data

The header contains information pertaining to the overall makeup of the bitmap, such as its width, height, number of bits per pixel (8 or 24), and so on. The color table contains the palette for the bitmap, which is the list of colors used throughout the image. The color table is extremely important for 8-bit bitmaps because it describes up to 256 colors used by pixels in the image. 24-bit bitmaps don’t need a color table because their colors are sufficiently described by the pixels themselves. The image data is where the actual pixels of the bitmap are stored. For example, if a bitmap is 10×12, it is 10 pixels across and 12 pixels down for a total of 120 pixels. If it is an 8-bit image, each pixel requires 8 bits (1 byte) to describe its color. So, the image data for this bitmap would consist of 120 bytes. To determine the color of each pixel, you would look up the byte value in the color table to see what color the pixel is.

Of course, all this color table and image data stuff takes place automatically once you load an image and start using it with GDI functions. In other words, you only have to worry yourself with the inner workings of bitmaps when you first load the bitmap from a file or resource. Once loaded, you use a handle to a bitmap to draw it to a device context in much the same way as you drew graphics primitives to a device context in Hour 4, “Learning to Draw Basic Graphics.”

Developing a Bitmap Class

Although I could provide you with a handful of bitmap functions and set you on your way, bitmaps provide a perfect opportunity to take advantage of object-oriented programming. More specifically, you can create a class that includes all the code required to load and draw bitmaps, and then use the class to create bitmap objects that are much easier to use than if you had created several functions. Knowing this, the next couple of sections are devoted to the design and development of a bitmap class that you will use throughout the book in just about every example program from here on. Although the bitmap class is admittedly a little tricky in places, once created it is unbelievably easy to use. So, the idea is to create the class once with the knowledge that it will make your life incredibly easier in coming hours.

How It Will Work

The idea behind the Bitmap class is to provide a means of loading bitmaps from a file or resource, as well as drawing bitmaps to a device context. By incorporating these capabilities into a class, you’ll be able to create Bitmap objects in your games that are very easy to use and that hide the messy aspects of working with bitmaps. The Bitmap class has the following requirements:

  • Loads a bitmap from a file

  • Loads a bitmap from a resource

  • Creates a blank bitmap

  • Draws a bitmap to a device context

  • Obtains the width and height of a bitmap

The first two requirements are fairly obvious from the earlier discussion. The third requirement isn’t terribly important just yet, but you might find yourself wanting to create a blank bitmap for some reason, so why not have that capability? Besides, later in Hour 10, “Making Things Move with Sprites,” you’ll see how bitmaps are important for solving a flicker problem associated with animation; the solution requires blank bitmaps. The final requirement isn’t terribly important, but you might encounter a situation in which it’s necessary to determine the width and height of a bitmap. The information is readily available when you load a bitmap, so you might as well make it accessible from the Bitmap class.

Putting the Code Together

The code for the Bitmap class is admittedly complex in places, so I’m not suggesting that you expect to understand every nuance of it immediately. In fact, my goal isn’t really to make you a bitmap expert, which is practically a requirement in order to fully understand the Bitmap class. Even so, I don’t like the idea of accepting code at face value without any explanation, so I’d like for you to have a general understanding of what is going on in the Bitmap class. If I gloss over a section of code, just understand that the goal here is to get you using bitmaps in as little time as possible so that you can quickly move on to placing them in games.

The Class Definition

Win32 defines several data structures that pertain to bitmaps, so you’ll see several different ones throughout the Bitmap class code. One of these structures is BITMAPINFOHEADER, which is a structure that stores the header information associated with a bitmap. When you read a bitmap from a file or resource, you will store its header in a BITMAPINFOHEADER structure. Another important bitmap-related structure is RGBQUAD, which is used to store the four 8-bit components of a 32-bit color. Even though you’re working with 8-bit images, it’s still necessary to use the RGBQUAD structure for loading bitmaps.

Speaking of 8-bit images, you know that an 8-bit bitmap requires up to 256 colors. The following code is for a custom structure named BITMAPINFO_256 that stores the header and a color table for a bitmap with 256 colors:

struct BITMAPINFO_256
{
  BITMAPINFOHEADER  bmiHeader;
  RGBQUAD           bmiColors[256];
};

This structure will come in quite handy in a moment when you begin loading bitmaps. Before we get into that, however, let’s take a look at the Bitmap class definition, which is shown in Listing 5.1.

Example 5.1. The Bitmap Class Definition Includes the Member Variables and Methods Used to Manage Bitmaps

 1: class Bitmap
 2: {
 3: protected:
 4:   // Member Variables
 5:   HBITMAP m_hBitmap;
 6:   int     m_iWidth, m_iHeight;
 7:
 8:   // Helper Methods
 9:   void Free();
10:
11: public:
12:   // Constructor(s)/Destructor
13:   Bitmap();
14:   Bitmap(HDC hDC, LPTSTR szFileName);
15:   Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance);
16:   Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor =
17:     RGB(0, 0, 0));
18:   virtual ~Bitmap();
19:
20:   // General Methods
21:   BOOL Create(HDC hDC, LPTSTR szFileName);
22:   BOOL Create(HDC hDC, UINT uiResID, HINSTANCE hInstance);
23:   BOOL Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor);
24:   void Draw(HDC hDC, int x, int y);
25:   int  GetWidth() { return m_iWidth; };
26:   int  GetHeight() { return m_iHeight; };
27: };

This listing reveals the overall makeup of the Bitmap class, including its member variables, constructors, destructor, and methods. The m_hBitmap member variable is used to store a handle to the bitmap, which is extremely important for drawing the bitmap to a device context using the GDI (line 5). The m_iWidth and m_iHeight member variables store the width and height of the bitmap, respectively (line 6).

The Constructors and Destructor

Notice in Listing 5.1 that there are three constructors in the Bitmap class in addition to the default constructor, each of which corresponds to one of the different approaches to creating bitmaps (lines 14–16)). Each Bitmap() constructor has a corresponding Create() method that is used to handle the work of loading the bitmap data and creating it as a GDI object (lines 20–22). The Free() method is a helper method used within the Bitmap class to free up the memory associated with the bitmap (line 9). The Draw() method shouldn’t be too surprising because it provides a means of drawing the bitmap to a device context (line 23). And finally, the GetWidth() and GetHeight() methods are simply used to obtain the width and height of the bitmap, respectively (lines 25 and 26).

The CD-Rom for this book contains the code for all the Bitmap() constructors, as well as the destructor and the Free() method.

The Bitmap() constructors are all very simple in that they call a corresponding Create() function to carry out the specifics of creating a bitmap based on a file, a resource, or a solid color. The default constructor doesn’t do much of anything other than initialize member variables; the idea behind this constructor is that you will eventually call the Create() method directly to load the Bitmap object with data. The destructor calls the Free() method to release the memory associated with the bitmap and clear the bitmap handle.

The Free() method first checks to see if the bitmap handle, m_hBitmap, is valid—in which case, it deletes the GDI bitmap object and clears out the handle. This is all that’s required to free the memory associated with the bitmap.

Three Ways to Create a Bitmap

There are three Create() methods in the Bitmap class that are fairly long, but you can access them from the CD-ROM. The first Create() method is responsible for loading a bitmap from a file.

The method starts out by calling Free() to make sure that any previous bitmap data is cleaned up; this would apply if the same Bitmap object was being reused for a different bitmap. The file is then opened, and the resulting file handle is checked to make sure that the open proceeded without a hitch. The file header for the bitmap is then read from the file, and some checks are made to ensure that it was read without any problems. The file header contains information about the bitmap file itself, whereas the info header contains information about the bitmap. The info header is read next, and another error check is performed.

With the headers properly read from the file, the Create() method is ready to get down to the real business of reading the bitmap data. The color table for the bitmap is read and followed by the image data. It is worth pointing out the usage of the CreateDIBSection() Win32 function, which is used to obtain a handle to a GDI bitmap object from raw bitmap data. This function ultimately makes it possible to read a bitmap and prepare it for use with the GDI. The last step in the Create() method is to free the bitmap memory if an error occurred while reading its data.

The second Create() method supported by the Bitmap class is used to load a bitmap from a resource.

This method follows roughly the same pattern as the first Create() method—except in this case, the bitmap information is retrieved from a resource in memory, as opposed to being read from a file. The first step is to find the bitmap resource, load the resource into memory, and then lock the resource so that you can access its raw data. You don’t have to worry about the color table in this case because it is already included in the bitmap information. The width and height of the bitmap are stored next, and the image data is copied and used as the basis for obtaining a bitmap handle using CreateDIBSection(). Finally, the method concludes by performing some clean up in case an error occurred.

The last of the Create() methods is the one that creates a blank bitmap in a solid color.

This method is considerably different from the other two Create() methods because it has the luxury of not having to involve itself with any existing bitmap data. Instead, it uses a Win32 function called CreateCompatibleBitmap() to create an entirely new bitmap based on the supplied device context. Because the width and height are provided as arguments to the method, they are easy to store. Most of the work in the method has to do with filling the bitmap with a solid color. A compatible device context is first created to hold the bitmap for drawing, and then a solid brush in the specified color is created. The bitmap is then selected into the device context and filled with the solid brush. The graphics objects are then cleaned up to finish the job of this Create() method.

Drawing the Bitmap

The three Create() methods are by far the toughest parts of the Bitmap class, so the Draw() method will be a welcome relief.

The Draw() method is actually similar to the third Create() method because it involves drawing a bitmap. However, in this case the bitmap is being drawn to an outside device context, as opposed to you drawing on the bitmap itself. The Draw() method accepts a device context and an XY coordinate as arguments. This reveals how simple it is to use the method to draw a bitmap. The first step in drawing the bitmap is ensuring that the bitmap handle is valid. If the handle checks out okay, a compatible device context is created to temporarily store the bitmap, and the bitmap is selected into the device context. This is an important step because drawing images always takes place from one device context to another.

The bitmap is actually drawn using the Win32 BitBlt() function, which draws an image from a source device context to a specified location on a destination device context. Drawing a bitmap image is sometimes referred to as blitting, which is where the name BitBlt() comes from; you are blitting the image bits when you draw the image. The Draw() method finishes up by cleaning up the temporary device context.

Note

Drawing the Bitmap

The x and y arguments to the Bitmap::Draw() method are specified relative to the device context the bitmap is being drawn on, and they indicate the upper left corner of the bitmap image.

You now have a complete, fully working Bitmap class that is ready to be used in games to load and draw bitmap images. Let’s move on and take a look at a program example that puts the Bitmap class through its paces.

Building the Slideshow Example Program

Because you haven’t learned about how to process user input such as key strokes and mouse clicks, it still isn’t quite possible to create a game yet. However, the remainder of the hour guides you through the creation of a surprisingly practical example program that happens to serve as a great demonstration of the Bitmap class. I’m referring to the Slideshow program, which uses a series of bitmap images to present a slideshow. The Slideshow program takes advantage of the timing mechanism built into the game engine to provide a delay between moving to each successive image in a slideshow. The Bitmap class proves to be quite useful as a means of loading and drawing bitmaps from both files and resources.

Before looking at the code for the Slideshow program, let’s quickly go over what it is supposed to accomplish:

  • Create several bitmaps from files

  • Create several bitmaps from resources

  • Create a blank bitmap in a solid color

  • Draw the bitmaps one at a time as part of a slideshow

  • Time the presentation of the slides in the slideshow appropriately

These requirements for the Slideshow program make it pretty clear what is supposed to happen. The only thing worth mentioning is that you wouldn’t normally mix the two approaches of loading bitmaps from files and resources. Generally speaking, it makes sense to commit to one approach or the other. In the case of a slideshow, it probably makes more sense to load bitmaps from files because you might want to change the images without recompiling the program. However, in this case it’s important to demonstrate the different uses of the Bitmap class.

Writing the Program Code

The header for the Slideshow program example is the best place to start in terms of getting acquainted with how it works. Check out Listing 5.2 to get a feel for what the header is all about. Don’t forget that you can access all of the code for the Slideshow program on the accompanying CD-ROM.

Example 5.2. The Slideshow.h Header File Imports a Few Header Files and Declares Several Global Variables

 1: #pragma once
 2:
 3: //-----------------------------------------------------------------
 4: // Include Files
 5: //-----------------------------------------------------------------
 6: #include <windows.h>
 7: #include "Resource.h"
 8: #include "GameEngine.h"
 9: #include "Bitmap.h"
10:
11: //-----------------------------------------------------------------
12: // Global Variables
13: //-----------------------------------------------------------------
14: HINSTANCE   _hInstance;
15: GameEngine* _pGame;
16: const int   _iNUMSLIDES = 7;
17: Bitmap*     _pSlides[_iNUMSLIDES];
18: int         _iCurSlide;

Other than declaring a few global variables, there aren’t really any surprises here. The _hInstance variable is necessary to store because a program instance handle is necessary in order to load bitmaps as resources (line 14). An instance is simply a program loaded into memory; all the programs you have open and running in Windows are considered instances. An instance handle is a reference to an instance, which allows you to interact with the instance and do things such as load resources that are stored in the executable program file. Because you’re going to be loading bitmaps as resources, it’s important to store away an instance handle for the Slideshow program.

The _pGame global variable is used to store a pointer to the game engine, which should be familiar to you by now (line 15). The remaining member variables relate specifically to the slideshow functionality. The global constant _iNUMSLIDES is used to set the number of slides in the slideshow, and should be changed if you modify the number of slides in the slideshow (line 16). The _pSlides variable is perhaps the most important because it stores away pointers to the Bitmap objects (line 17); these objects correspond to the slides in the slideshow. Finally, the _iCurSlide variable represents the array index of the current slide, which is the slide currently being displayed (line 18). This variable is important because it keeps track of where the slideshow is in the slide sequence.

The global variables in the Slideshow header file are somewhat revealing, but you obviously have to look at the code for the game functions to really get a feel for what’s going on. Listing 5.3 contains the code for the GameInitialize() function, which is the first of the Slideshow game functions.

Example 5.3. The GameInitialize() Function Creates the Game Engine, Sets the Frame Rate to One Cycle Per Second, and Stores Away the Instance Handle for the Program

 1: BOOL GameInitialize(HINSTANCE hInstance)
 2: {
 3:   // Create the game engine
 4:   _pGame = new GameEngine(hInstance, TEXT("Slideshow"),
 5:     TEXT("Slideshow"), IDI_SLIDESHOW, IDI_SLIDESHOW_SM);
 6:   if (_pGame == NULL)
 7:     return FALSE;
 8:
 9:   // Set the frame rate
10:   _pGame->SetFrameRate(1);
11:
12:   // Store the instance handle
13:   _hInstance = hInstance;
14:
15:   return TRUE;
16: }

The GameInitialize() function is pretty basic in terms of doing things you’ve gotten accustomed to seeing in a program that makes use of the game engine. Notice, however, that the frame rate for the game engine is set to 1, which means that there is only one game cycle per second (line 10). This is important because it helps to establish the timing of the slideshow. The problem is that one second is still too short a period to flip between slides, so we’ll have to trick the game engine into displaying each slide a little longer; you tackle this problem in a moment. The only other task carried out in GameInitialize() is storing away the instance handle, which is very important for loading bitmaps from resources (line 13).

Although GameInitialize() creates and initializes the game engine, it’s the GameStart() function that really gets the Slideshow program on track. Its counterpart is GameEnd(), which cleans up the bitmaps created in GameStart(). The code for both functions is shown in Listing 5.4.

Example 5.4. The GameStart() and GameEnd() Functions Take Care of Initializing and Cleaning Up the Slide Bitmaps

 1: void GameStart(HWND hWindow)
 2: {
 3:   // Create and load the slide bitmaps
 4:   HDC hDC = GetDC(hWindow);
 5:   _pSlides[0] = new Bitmap(hDC, TEXT("Image1.bmp"));
 6:   _pSlides[1] = new Bitmap(hDC, TEXT("Image2.bmp"));
 7:   _pSlides[2] = new Bitmap(hDC, TEXT("Image3.bmp"));
 8:   _pSlides[3] = new Bitmap(hDC, IDB_IMAGE4, _hInstance);
 9:   _pSlides[4] = new Bitmap(hDC, IDB_IMAGE5, _hInstance);
10:   _pSlides[5] = new Bitmap(hDC, IDB_IMAGE6, _hInstance);
11:   _pSlides[6] = new Bitmap(hDC, 640, 480, RGB(128, 128, 64));
12:
13:   // Set the first slide
14:   _iCurSlide = 0;
15: }
16:
17: void GameEnd()
18: {
19:   // Cleanup the slide bitmaps
20:   for (int i = 0; i < _iNUMSLIDES; i++)
21:     delete _pSlides[i];
22:
23:   // Cleanup the game engine
24:   delete _pGame;
25: }

The GameStart() function takes on the responsibility of creating the slide bitmaps. It first obtains a device context, which is necessary to create any bitmaps (line 4). It then loads several bitmaps from files (lines 5–7), as well as loading a few bitmaps as resources (lines 8–10). The final bitmap is a solid color blank bitmap that is created using the third Bitmap() constructor (line 11). After creating the bitmaps, the GameStart() function sets the current slide to the first bitmap (line 14).

The GameEnd() function simply cleans up the slide bitmaps by stepping through the array and deleting them one at a time (lines 20 and 21). This is an important part of the Slideshow program because you definitely want to clean up after yourself.

The current slide is drawn to the game screen in the Slideshow program thanks to the GamePaint() function, which is shown in Listing 5.5.

Example 5.5. The GamePaint() Function Simply Draws the Current Slide Bitmap

 1: void GamePaint(HDC hDC)
 2: {
 3:   // Draw the current slide bitmap
 4:   _pSlides[_iCurSlide]->Draw(hDC, 0, 0);
 5: }

The GamePaint() function is probably simpler than you might have expected. Here, you’re really getting to see how helpful the Bitmap class is because it allows you to draw a bitmap image with a very simple call to the Draw() method (line 4) of the Bitmap object. The _iCurSlide global variable is used to make sure that the appropriate slide is drawn.

The GameCycle() function is responsible for moving to the next slide after imposing a small delay, as shown in Listing 5.6.

Example 5.6. The GameCycle() Function Steps Through the Slides After Waiting a Few Seconds

 1: void GameCycle()
 2: {
 3:   static int iDelay = 0;
 4:
 5:   // Establish a 3-second delay before moving to the next slide
 6:   if (++iDelay > 3)
 7:   {
 8:     // Restore the delay counter
 9:     iDelay = 0;
10:
11:     // Move to the next slide
12:     if (++_iCurSlide == _iNUMSLIDES)
13:       _iCurSlide = 0;
14:
15:     // Force a repaint to draw the next slide
16:     InvalidateRect(_pGame->GetWindow(), NULL, FALSE);
17:   }
18: }

Because the game engine is limited to a minimum frame rate of one second, it’s necessary to slow down the slideshow further by imposing a delay in the GameCycle() function. Three seconds is a reasonable delay for viewing each slide, so the GameCycle() function uses a static variable, iDelay, to delay each slide for three seconds before moving to the next (line 3). In order to move to the next slide, the iCurSlide variable is incremented (line 12); if it is incremented past the last slide, the slideshow cycles back to the first slide (line 13).

Just because you increment the current slide variable doesn’t mean that the slideshow updates to show the new slide bitmap. Keep in mind that the current slide bitmap is drawn in the GamePaint() function, which is called whenever the client area of the Slideshow window needs to be repainted. The trick is to force a repaint of the window, which is possible using the Win32 InvalidateRect() function. The GameCycle() function calls InvalidateRect() to force a repaint and display the new slide bitmap (line 16). The second argument to InvalidateRect() indicates that the entire client area is to be repainted, whereas the last argument indicates that it isn’t necessary to erase the client area before repainting.

Assembling the Resources

You’ve now seen the vast majority of the code for the Slideshow program example, which hopefully wasn’t too overwhelming seeing as how the Bitmap class removed a great deal of the work required to display images. The only remaining code to address involves the bitmap resources used in the program. To demonstrate how to use bitmaps as resources, I decided to go ahead and include all the slide bitmaps as resources, even though the program only loads three of them as resources. Listing 5.7 contains the code for the Resource.h header file, which defines unique resource IDs for the bitmaps.

Example 5.7. The Resource.h Header File Contains Resource IDs for the Icons and Bitmap Images in the Slideshow Program Example

 1: //-----------------------------------------------------------------
 2: // Icons                    Range : 1000 - 1999
 3: //-----------------------------------------------------------------
 4: #define IDI_SLIDESHOW       1000
 5: #define IDI_SLIDESHOW_SM    1001
 6:
 7: //-----------------------------------------------------------------
 8: // Bitmaps                  Range : 2000 - 2999
 9: //-----------------------------------------------------------------
10: #define IDB_IMAGE1          2000
11: #define IDB_IMAGE2          2001
12: #define IDB_IMAGE3          2002
13: #define IDB_IMAGE4          2003
14: #define IDB_IMAGE5          2004
15: #define IDB_IMAGE6          2005

This code reveals how resources are typically organized according to type. It also shows how the numbers used to distinguish between resources are specified as ranges for each resource type. This is primarily an organizational issue, but it does help to keep things straight. The bitmap resource IDs are created in lines 10–15, and are pretty straightforward. Notice that there is no ID for the seventh bitmap, the solid color bitmap, because it is a blank bitmap that doesn’t derive from a resource.

The bitmap resource IDs come into play in the Slideshow.rc resource script, which is shown in Listing 5.8.

Example 5.8. The Slideshow.rc Resource Script Contains the Resources for the Slideshow Program Example, Including the Bitmap Resources

 1: //-----------------------------------------------------------------
 2: // Include Files
 3: //-----------------------------------------------------------------
 4: #include "Resource.h"
 5:
 6: //-----------------------------------------------------------------
 7: // Icons
 8: //-----------------------------------------------------------------
 9: IDI_SLIDESHOW      ICON         "Slideshow.ico"
10: IDI_SLIDESHOW_SM   ICON         "Slideshow_sm.ico"
11:
12: //-----------------------------------------------------------------
13: // Bitmaps
14: //-----------------------------------------------------------------
15: IDB_IMAGE1         BITMAP       "Image1.bmp"
16: IDB_IMAGE2         BITMAP       "Image2.bmp"
17: IDB_IMAGE3         BITMAP       "Image3.bmp"
18: IDB_IMAGE4         BITMAP       "Image4.bmp"
19: IDB_IMAGE5         BITMAP       "Image5.bmp"
20: IDB_IMAGE6         BITMAP       "Image6.bmp"

This resource script reveals how bitmap resources are included using the BITMAP resource type. Including bitmaps in a resource script is similar to including icons, and involves specifying the resource ID, resource type, and image file for each bitmap (lines 15–20). By including bitmaps in the resource script, you are alleviating the need to include separate bitmap image files with the completed program.

Testing the Finished Product

The Slideshow program example is definitely the most practical program you’ve seen thus far. You can use the program to display any kind of slideshow of images that you want, provided that you store the images as 8-bit (256 color) bitmaps with no compression. To demonstrate the practicality of the program, I decided to put together a slideshow of photos from a recent trip I took to Arizona. Figure 5.2 shows a photo of a jackrabbit, which happens to be the opening slide in the slideshow.

The Slideshow program example demonstrates how to display bitmap images, and also serves as a good way to relive a vacation.

Figure 5.2. The Slideshow program example demonstrates how to display bitmap images, and also serves as a good way to relive a vacation.

You’ll notice that every three seconds the slide will change, eventually leading to the end of the slideshow, after which it wraps around and begins with the first image again. Figure 5.3 shows another slide in the slideshow to demonstrate how the slides are changed.

Each successive slide in the Slideshow program is separated by a three second delay.

Figure 5.3. Each successive slide in the Slideshow program is separated by a three second delay.

Whether or not you decide to place your family album of digital photographs into a Slideshow program of your own, you’ve hopefully gained an appreciation of bitmaps and how to work with them at the programming level. You also now have the Bitmap class, which you will be using heavily throughout the remainder of the book.

Summary

This hour tackled one of the most important topics of game development by introducing you to bitmap images, as well as how they are loaded and displayed in a Windows program. You first analyzed the inner structure of a bitmap, which was necessary in order to gain an understanding of what it takes to write code that can load a bitmap from a file or resource. You then used this knowledge to assemble a bitmap class that handles the messy details of creating bitmaps based on files, resources, or solid colors. The hour concluded by demonstrating how to use the bitmap class in a practical example program.

This hour concludes the first part of the book, which laid the groundwork for game programming in Windows. The next part of the book addresses the all-important topic of interacting with game players. You find out how to receive and process user input from a keyboard, mouse, and joystick, as well as creating your first complete game.

Q&A

Q1:

Why isn’t the Bitmap class in this hour more flexible in supporting other image types?

A1:

There are two reasons for the Bitmap class being limited in its support for image types. The first reason has to do with coding simplicity—it takes considerably more complex code to handle multiple image types. Because the goal of this book is to get you creating games with minimal distractions, keeping the Bitmap class as simple as possible is extremely important. The second reason has to do with performance—using an image format with fewer colors results in faster games. This is because less colors results in less information being transferred when an image is drawn to a device context, which means speedier draws. Performance is a critical factor in virtually all games, so sacrificing zillions of colors in favor of a smoother and quicker game is usually worth the trade-off.

Q2:

What’s the purpose of the third Create() method in the Bitmap class?

A2:

The third Create() method in the Bitmap class is used to create a blank bitmap in a solid color, which might not seem very useful at first. However, later on in the book, you’ll find out how bitmaps don’t always originate with bitmap files or resources. Besides, you might want to create a bitmap as a background for a game that gets customized specifically for the game. For example, consider a space background with several planets. You might create a solid black bitmap and then draw the planets on top of it.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you’ve learned, and begin learning how to put your knowledge into practice. The answers to the quiz can be found in Appendix A, “Quiz Answers.”

Quiz

1:

Why would you want to store bitmaps as resources in an executable program, as opposed to using them straight from files?

2:

What is the purpose of the color table in a bitmap?

3:

What kind of bitmap is supported by the Bitmap class created in this hour?

Exercises

  1. Modify the Slideshow program example so that it uses your own bitmap images as slides. Make sure to change the number of slides in the program to accommodate how many you’re using. Also, if you choose to use a different size for your images, simply change the size of the game screen to match when you create the game engine.

  2. Alter the frame rate of the Slideshow program example to see how it impacts the speed of the slide images being drawn. You should notice the program reach a maximum speed, after which it doesn’t really make any difference how fast you set the rate. In other words, there are limits as to how fast of a frame rate you can get.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.232.189