Chapter 2. A Windows Game Programming Primer

Believe it or not, there was a time not so long ago when the notion of a game other than Solitaire on Windows was considered a joke. I should know because during that same time period I was attempting to get a job as a game programmer who specialized in Windows games. I knew it was only a matter of time before DOS went away and games had to migrate to Windows, but back then no one wanted to think about it. So I found work elsewhere. Now only a few short years later, the concept of developing a computer game for any operating system other than Windows is considered a joke, barring a few Macintosh and Linux games. If you’ve never written a program specifically for Windows, you’re in for somewhat of a surprise because Windows programs are unique in terms of how they are put together. This lesson shows you how to assemble a minimal Windows program, which will get you a step closer toward creating your first Windows game.

In this hour, you’ll learn

  • How Windows programming differs from traditional programming

  • What makes a Windows program tick

  • How to develop a minimal Windows program

Windows Programming Essentials

Before you can even think about developing games for Windows, you need to have some knowledge about what it takes to develop a basic Windows program. Windows programming is unlike other kinds of traditional programming, as you’ll soon find out. Windows programming begins and ends with the Win32 API (Application Programming Interface), which is the set of data types and functions that are used to create Windows programs. The Win32 API is quite massive, and requires a significant learning curve to get comfortable with its many facets. Fortunately, you only need to use a relatively small portion of the Win32 API to create Windows games.

Note

Windows Programming Essentials

The Win32 API is built into all compilers that support Windows programming, such as Microsoft Visual C++. By the way, the “32” in Win32 has to do with the fact that the API is a 32-bit API. This is significant because the original Windows API was developed for 16-bit programming. Remember Windows 3.1?

Certain aspects of Win32 programming are common to all Windows programs, regardless of whether you’re creating a game or a word processor. These common Windows program features are primarily what you learn about in this lesson. This will provide you with a solid foundation on which to build game specific knowledge and code. If you’re getting antsy, just hang in there through this lesson, and I promise you’ll get to create some interesting programs soon enough.

Note

Windows Programming Essentials

In the previous lesson, you learned how object-oriented programming is important for game development. You might be curious as to how OOP impacts Windows programming with Win32. Win32 is a procedural API that relies on object-oriented constructs to represent Window elements such as windows and icons. An example of this is the window object, which represents a rectangular area on the screen. Window objects have data associated with them along with Win32 API functions that are used to manipulate them.

You can think of Win32 programming as a hybrid type of object-oriented programming.

Event-Driven Programming

The most dramatic switch for most people new to Windows programming is the fact that Windows programs are event-driven. This is a fancy way of saying that Windows programs respond to their environment, as opposed to the environment taking cues from the program. In the world of events, the flow of your program follows events external to the program such as mouse clicks and key presses. Instead of writing code to scan for a key press, you write code to take some action whenever a key press is delivered to the program. As you get more comfortable with the concept of events, you’ll see how just about any change in the environment can trigger an event. You can design your programs to ignore or respond to any events you choose.

The event-driven nature of Windows has a lot to do with the fact that it is a graphical operating system. When you think about it, Windows faces a seriously challenging problem in allowing multiple programs to run at the same time and share screen space, memory, input devices, and virtually every other system resource. The event-driven approach to handling user interactions and system changes is extremely important in making Windows as flexible and powerful as it is. In terms of games, events make it possible to divide tasks according to what has taken place. In other words, a left mouse click, a right mouse click, and a key press on the keyboard are handled as separate events.

Communicating with Messages

In traditional non-graphical programs, a program calls a system function to accomplish a given task. Graphical Windows programs introduce a twist on this scenario by allowing Windows to call a program function. When I say “Windows calls a program function,” I mean that Windows sends the program a message, which is a notification containing information about an event. Windows sends messages whenever something takes place that a program might want to know about and respond to, such as the user dragging the mouse or resizing the main program window.

Note

Communicating with Messages

Windows programs are also commonly referred to as Windows applications. So, you’ll often see me using the terms program and application interchangeably—just know that they mean the same thing.

Messages are always sent to a specific window, usually the main program window; every Windows program has a main window. Many windows are floating around in a typical Windows session, and all of them are receiving messages at one time or another that inform them of changes going on around them. Each of these windows has a window procedure, which is a special function that processes messages for the window. Windows handles the details of routing messages to the appropriate window procedures; your job is to write code in the window procedure for your program that responds to certain messages.

With the probability of many different messages being sent to a window, you might wonder how many of them you need to worry about in a game. And what happens to the messages you ignore? In the average Windows program, including most games, you usually only concern yourself with a handful of messages. You write code to respond to these messages, and you allow a default Windows message handler to take care of messages you ignore; Windows provides default handlers for all messages. By the way, a handler is simply a chunk of code that is called in response to a message; its job is to “handle” the event.

Understanding Device Independence

In the golden era of DOS games, it was common for game developers to program games so that they worked directly with the memory on your graphics card. The benefit to this was that it made games extremely fast because the graphics for a game was being processed directly by the graphics card. Although this worked great for DOS games, you don’t have quite the same luxury in Windows. Generally speaking, Windows is specifically designed to keep you from interacting directly with hardware such as graphics cards. This design is known as device independence, which simply means that Windows programs draw graphics in a manner that is independent of the specific hardware devices that get drawn on. The upside to this approach is that Windows programs work on a wide range of hardware without any special modification. This was impossible in the DOS world.

Device independence in Windows is made possible by the Graphical Device Interface, or GDI, which is the part of the Win32 API that deals with graphics. The GDI sits between a program and the physical graphics hardware. Under the GDI, graphics are drawn to a virtual output device; it is up to Windows to resolve the virtual device down to a specific hardware device via the system configuration, hardware drivers, and so on. Even though the GDI is designed to keep you at an arm’s length from graphics hardware, it is still quite powerful. In fact, all the games developed throughout this book are based solely on the GDI.

Storing Program Information as Resources

Just about every Windows program relies to some extent on resources, which are pieces of information related to a program outside of the program code itself. For example, icons are a good example of resources, as are images, sounds, and even menus. You specify all the resources for a Windows program in a special resource script, which is also known as an RC file. The resource script is a text file containing a list of resources that is compiled and linked into the final executable program. Most resources are created and edited with visual resource tools, such as the icon editor integrated into the Visual C++ development environment.

Dealing with Strange Data Types

Perhaps the toughest part of Windows programming is getting comfortable with the strange data types that are a part of every Windows program. One of the most common data types you’ll see is called a handle, and it’s nothing like a door handle in the real world. A handle in Windows is a number that refers to a graphical Windows object such as a window or icon. Handles are important because an object is often moved around in memory by Windows, which means that its memory address is a moving target. A handle gives you a fixed reference to an object independent of the object’s physical location in memory. Handles are used throughout the Win32 API when dealing with Windows objects. Don’t worry if handles sound a little complicated right now because they’ll start making more sense as you see them used in a real Windows program.

In addition to handles, the Win32 API introduces a variety of new and different data types that you might find strange at first. All Win32 data types appear in uppercase, so they are easily identifiable. For example, a fairly simple and commonly used Win32 data type is RECT, which represents a rectangle structure. There is also an HWND data type, which represents a handle to a window. Win32 is chock full of data types, so it’s futile to try and cover them all in one place. Instead, you’ll learn about new data types as you encounter them throughout the book.

In order for your Windows programs to be able to recognize and use Win32 data types, you must import them into your program’s source files. This is accomplished with a single line of code:

#include <windows.h>

The header file windows.h defines the entire Win32 API, and is essential in every Win32 application. It’s generally a good idea to import it first in every relevant source file.

Unconventional Coding Conventions

If you’ve ever seen the code for a Windows program before, you might have wondered what was going on with the weird variable names. Programmers have long struggled with the problem of writing code that is easy to understand. Windows makes this problem worse by introducing a lot of different data types, which makes it difficult to keep up with which variables are of which type. Legendary Microsoft programmer Charles Simonyi came up with a pretty crafty solution to this problem that is now known as Hungarian notation. (Mr. Simonyi is Hungarian.) With Hungarian notation, variable names begin with a lowercase letter or letters that indicate the data type of the variable. For example, integer variable names begin with the letter i. Following are some Hungarian notation prefixes commonly used in Windows programming:

  • i—. integer

  • b—. Boolean (BOOL)

  • sz—. string terminated by zero

  • p—. pointer

  • h—. handle

  • w—. unsigned short integer (WORD)

  • dw—. unsigned long integer (DWORD)

  • l—. long integer

Note

l—

BOOL, WORD, and DWORD are commonly used Win32 data types.

Applying Hungarian notation is very simple. For example, an integer count might be named iCount, whereas a handle to an icon might be named hIcon. Similarly, a null-terminated string storing the name of a sports team might be named szTeamName. Keep in mind that Hungarian notation is completely optional, but it really is a good idea. You’ll encounter Hungarian notation in all the code in this book, and after a while you’ll hopefully begin to appreciate the convenience it offers.

Peeking Inside a Windows Program

Although I could certainly go on and on about the Win32 API, the best way to get you closer to creating a Windows game is to take a look at what actually goes into a Windows program. The next few sections break apart the major aspects of a Windows program, along with the code that makes them tick. It’s not terribly important for you to understand every line of code at this point because much of what goes into a generic Windows program is overhead code that you won’t bother with again once you dive into game creation. Nevertheless, it’s important to at least have a feel for what is required of every Windows program.

Where It All Begins

If you come from the world of traditional C/C++ programming in non-graphical environments, you are no doubt familiar with the main() function. The operating system calls main() when a program is first run, and your program code starts executing inside main(). There is no main() function in a Windows program. However, Windows offers a similar function called WinMain() that serves as the starting point for a Windows program. Unlike main(), WinMain() simply creates and initializes some things and then eases out of the picture. After WinMain() creates a main window for the program, the rest of the program executes by responding to events in the main window procedure. The window procedure is where most of the really interesting things take place in a Windows program.

The Window Class

All windows in a Windows program are created based on a window class, which is a template that defines the attributes of a window. Multiple windows can be created from a single window class. For example, there is a standard Win32 window class that defines the attributes of a button, and all buttons in Windows are created from it. If you want to create a new window of your own, you have to register a window class and then create a window from it. In order to create new windows from a window class, the window class must be registered with Windows using the RegisterClassEx() Win32 API function. After a window class has been registered, you can use it to create as many windows as you want.

Window classes are represented by a data structure in the Win32 API called WNDCLASSEX, which defines the attributes of a window. Following is how the WNDCLASSEX structure is defined in the Win32 API:

typedef struct _WNDCLASSEX {
  UINT        cbSize;
  UINT        style;
  WNDPROC     lpfnWndProc;
  int         cbClsExtra;
  int         cbWndExtra;
  HINSTANCE   hInstance;
  HICON       hIcon;
  HICON       hIconSm;
  HCURSOR     hCursor;
  HBRUSH      hbrBackground;
  LPCSTR      lpszMenuName;
  LPCSTR      lpszClassName;
} WNDCLASSEX;

This code reveals some of those strange Win32 data types I talked about earlier. It isn’t important right now to go through each and every member of this structure. Instead, let’s focus on a few of the more interesting members:

  • lpfnWndProc—. A pointer to the window procedure for the window class

  • hIcon—. The icon for the window class

  • hIconSm—. An optional small icon for the window class

  • hCursor—. The mouse cursor for the window class

  • hbrBackground—. The background brush for the window class

These members hopefully make some sense because they are related to fairly obvious parts of a window. The first member, lpfnWndProc, is probably the trickiest because it is a pointer to the window procedure for the window class; you find out what this procedure looks like in a moment. The hIcon and hIconSm members are used to set the icons for the window class, and they correspond to the program icons you see when a program is running in Windows. hCursor is used to set a special mouse cursor (pointer) for the window class if you decide you want something other than the standard arrow cursor. And finally, hbrBackground is used to set the background for the window class, which is the color that fills the background of the inside of the window. Most windows use white as a background color, but you’re free to set it to any color you want.

Again, it’s not imperative that you feel totally comfortable with the window class structure at this point. The goal right now is just to get acclimated with Win32 programming to a degree in which we can assemble a complete program. Later in the lesson, you put the window class structure to use in creating a minimal Windows program.

Creating a Window

A critical part of any Windows program, including games, is the creation of the main program window. Creating a window involves using a window class, which you learned about in the previous section. Although window classes define general characteristics for a window, other attributes of a window must be defined when a window is created. These attributes are provided as arguments to the CreateWindow() function, which is the Win32 API function used to create windows. Following is an example of creating a window using the CreateWindow() function:

hwnd = CreateWindow(szAppName,
  "My Game",
  WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT,
  CW_USEDEFAULT,
  CW_USEDEFAULT,
  CW_USEDEFAULT,
  NULL,
  NULL,
  hInstance,
  NULL);

It’s not terribly important to understand every line of this code, but it is possible to focus on a few interesting aspects of the window creation. First of all, the name of the window class is specified as the first argument, szAppName. The second argument is the window title, "My Game", which is displayed in the title bar of the window when the program is run. The WS_OVERLAPPEDWINDOW style is a standard Win32 style that identifies a traditional window that can be resized. The four CW_USEDEFAULT styles indicate the initial XY position of the window on the screen, as well as the window’s width and height; you can use specific numbers for these settings, but CW_USEDEFAULT tells Windows to use a reasonable default value. The remaining parameters aren’t terribly important right now, so we won’t bother with them at the moment.

Keep in mind that I’m not expecting you to immediately absorb all this information; the main goal here is to start getting familiar with the general structure of the CreateWindow() function and its arguments. Notice that CreateWindow() returns a window handle to the newly created window. It’s also worth pointing out that I could’ve used numeric values when specifying the window’s X position, Y position, width, and height. For example, the previous code could have used hard-coded values such as 0, 0, 640, and 480. In fact, it is often helpful for games to use a fixed window size, which is why you’ll eventually be plugging in real numbers for the width and height of your game windows.

Handling Messages

Earlier in the lesson, you learned that Windows communicates with your program by sending it messages. Let’s take a closer look at messages to see how they work. A message has three pieces of information associated with it:

  • A window

  • A message identifier

  • Message parameters

The window associated with a message is the window to which the message is being sent. The message identifier is a number that specifies the message being sent. The Win32 API defines numeric constants that represent each message. For example, WM_CREATE, WM_PAINT, and WM_MOUSEMOVE are all numeric constants defined in the Win32 API that identify messages associated with window creation, window painting, and mouse movement, respectively.

The message parameters consist of two pieces of information that are entirely specific to the message being sent. These 32-bit values are called wParam and lParam, and their meaning is completely determined by the message being handled. For example, the wParam parameter for the WM_SIZE message contains information about the type of sizing performed on the window, whereas the lParam parameter contains the new width and height of the window’s inside area, which is also known as the window’s client area. The width and height are packed into the low and high words of the 32-bit lParam value, which is a common approach that Win32 uses to shove two pieces of information into a single location. If low words and high words sound intimidating, don’t worry because I show you exactly how to extract useful information from lParam later in the book when you need it.

When a message is delivered to a program by Windows, it is processed in the WndProc() function. Although WndProc() is responsible for handling messages for a given window class, your program must still take on the task of routing messages to the appropriate window procedures. This is taken care of by a message loop, which must be placed in the heart of the WinMain() function:

while (GetMessage(&msg, NULL, 0, 0)) {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

This code essentially processes all messages at the application level and routes them to the appropriate window procedures. Code in each different window procedure is then responsible for taking action based on the messages they receive. If window procedures sound mysterious at this stage, read on to learn how they work.

The Window Procedure

Every window in a Windows program has an associated window procedure, which is a special function that is capable of being called by Windows. Windows calls a window procedure to deliver messages to a given window. You can think of a window procedure as a message processing function. In object-oriented terms, a window procedure is the behavioral part of a window object, whereas Windows maintains the data part of the object. Figure 2.1 shows how a window procedure fits into the object-oriented concept of a window.

In object-oriented terms, a window is an object whose behavior is determined by a window procedure and whose data is managed by Windows.

Figure 2.1. In object-oriented terms, a window is an object whose behavior is determined by a window procedure and whose data is managed by Windows.

Window procedures are actually associated with window classes, which means that multiple windows created from a single class share the same window procedure. This is logical because the behavior of a given class of windows should be the same. A window procedure is defined in the Win32 API like this:

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);

Don’t sweat it if this prototype looks a little intimidating. You are only concerned right now with the meaning of the arguments, which should be somewhat familiar from the previous explanation of messages:

  • hwnd—. Handle of the window to which the message is being sent

  • iMsg—. Message identifier

  • wParam—. Primary message parameter

  • lParam—. Secondary message parameter

Practically every WndProc() function contains a large switch statement that is responsible for separating the messages being handled. Following is an example of a WndProc() function with a very common piece of code that handles the WM_DESTROY message, which is passed to a window whenever it is being destroyed:

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
  switch (iMsg) {
    case WM_DESTROY :
      PostQuitMessage(0);
      return 0;
  }
  return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

Notice that the switch statement operates on the iMsg argument, and in this case only looks for the WM_DESTROY message. The PostQuitMessage() function call causes the application to quit because the main window is being destroyed. This code gets called when you click the X in the upper right corner of a window. Notice in this code how any messages not handled in the switch statement are passed through to the DefWindowProc() function. This is a strict requirement of all window procedures because DefWindowProc() is responsible for the default handling of messages. If you left this code out, some strange things would happen in your program.

Working with Resources

You might not realize it, but as a Windows user you are already very familiar with an important part of Windows programming: resources. Resources are special data items associated with a Windows program that typically define portions of a program’s user interface. Unlike data such as variables used in program code, resources are stored in a program’s executable file and loaded only as needed. Following are some of the standard resources supported by Windows:

  • Bitmap

  • Cursor

  • Dialog box

  • Icon

  • Menu

In addition to these standard resources, Windows also supports user-defined resources, which are custom resources that you write special code to handle. User-defined resources are very important in game programming because they allow you to include sound effects and music with a program as resources.

Resources are defined in a program using a resource script, which is a text file with a .RC extension. You use a resource compiler to compile a resource script into a binary resource file with a .RES extension. This binary resource file is then linked with an application’s object code to create a complete executable application. The process of compiling and linking resources to an application’s .EXE file is usually a standard part of a Windows compiler’s overall build process; in other words, you shouldn’t have to worry about manually compiling resource scripts. Following is an example of a simple resource script that defines an icon and a cursor:

IDI_MINE ICON   "Mine.ico"
IDC_MINE CURSOR "Mine.cur"

You might initially be a little confused by the fact that resources are compiled into a binary form, especially considering that resources don’t have any C/C++ code directly associated with them. The compilation process for resources is actually quite different from the process for C/C++ code. A special resource compiler is required to compile resources, but its main job is to assemble all the resources defined in a resource script into a single binary file. A compiled binary resource file is also very different from an object file of compiled C/C++ code. You can think of a compiled resource file as a group of resources combined into binary form. Even though compiled resource files aren’t directly related to object files, they are merged together at the link stage of application development to create an executable application file (.EXE).

Perhaps the most commonly used resource is the icon, which forms a vital part of the user interface for Windows applications. If you don’t specifically set a custom icon for an application, the default Windows icon is used. Fortunately, it’s very simple to set a custom icon for an application. In fact, it only involves two steps:

  1. Define the icon in the application’s resource script.

  2. Load the icon when you define and register the application’s window class in the WinMain() function.

The first step is accomplished by adding a single line to the resource script for your application:

IDI_MINE ICON "Mine.ico"

This code uses the ICON resource statement to define an icon named IDI_MINE that is stored in the file Mine.ico. When the resource script is compiled, the icon will be referenced from the Mine.ico file and compiled into the binary resource file. The size of the icon can be either 32×32 or 16×16, depending on how you are going to use it; the default icon size for an application is 32×32. In the next section of the lesson, you learn how to define both a small (16×16) and a large (32×32) icon for the Skeleton program example.

The second step to setting a custom icon involves loading the icon and assigning it to the hIcon field of the WNDCLASSEX structure used to define a window class. You do this by calling the LoadIcon() Win32 API function and passing in the name of the icon:

wndclass.hIcon = LoadIcon(hInstance, "Mine.ico");

The hInstance parameter to LoadIcon() is passed into the WinMain() function by Windows, and is a handle to the application instance. This handle references the executable file from which the icon resource is to be loaded.

Building the Skeleton Example Program

At this point, you’ve seen the major portions of a Windows program isolated into individual parts. Understandably, it’s difficult to get a feel for a complete Windows program when viewing it in small chunks of code. For this reason, it’s very important to see how a complete Windows program comes together. This section shows you a complete, yet minimal, Windows program called Skeleton that will form the basis for games to come throughout the book. Following are the files that go into the Skeleton program example:

  • Skeleton.h—Header file for the application

  • Skeleton.cpp—Source code file for the application

  • Resource.h—Header file for the resource IDs

  • Skeleton.rc—Resource file for the application

The next couple of sections explore the program code and the related resources that go into the Skeleton application. All of this code is available on the accompanying CD-ROM, along with Microsoft Visual C++ project files.

Writing the Program Code

The Skeleton.h header file is surprisingly simple, and does nothing but import a couple of headers for use by Skeleton.cpp. Listing 2.1 contains the code for this file.

Example 2.1. The Skeleton.h Header File Simply Imports a Couple of Header Files

 1: #pragma once
 2:
 3: //-----------------------------------------------------------------
 4: // Include Files
 5: //-----------------------------------------------------------------
 6: #include <windows.h>
 7: #include "Resource.h"

In case you aren’t familiar with it, line 1 shows how to use the #pragma once compiler directive to keep the Skeleton.h header from being accidentally referenced more than once. This gets to be more of an issue in larger programs in which there are a lot of dependencies between different source files, but it’s a good idea to get in the habit of using the directive. Lines 6 and 7 contain the important code, which is the inclusion of the standard windows.h header file and the Resource.h resource identifier header file, which you learn about in a moment.

The bulk of the code for the Skeleton program is in the Skeleton.cpp source code file, which is shown in Listing 2.2.

Example 2.2. The Skeleton.cpp Source Code File Builds on the Code You Saw Throughout This Lesson to Create a Complete Windows Program

 1: //-----------------------------------------------------------------
 2: // Include Files
 3: //-----------------------------------------------------------------
 4: #include "Skeleton.h"
 5:
 6: //-----------------------------------------------------------------
 7: // Global Function Declarations
 8: //-----------------------------------------------------------------
 9: LRESULT CALLBACK  WndProc(HWND hWindow, UINT msg, WPARAM wParam,
10:   LPARAM lParam);
11:
12: //-----------------------------------------------------------------
13: // Global Functions
14: //-----------------------------------------------------------------
15: int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
16:   PSTR szCmdLine, int iCmdShow)
17: {
18:   static TCHAR  szAppName[] = TEXT("Skeleton");
19:   WNDCLASSEX    wndclass;
20:   HWND          hWindow;
21:   MSG           msg;
22:
23:   // Create the window class for the main window
24:   wndclass.cbSize         = sizeof(wndclass);
25:   wndclass.style          = CS_HREDRAW | CS_VREDRAW;
26:   wndclass.lpfnWndProc    = WndProc;
27:   wndclass.cbClsExtra     = 0;
28:   wndclass.cbWndExtra     = 0;
29:   wndclass.hInstance      = hInstance;
30:   wndclass.hIcon          = LoadIcon(hInstance,
31:     MAKEINTRESOURCE(IDI_SKELETON));
32:   wndclass.hIconSm        = LoadIcon(hInstance,
33:     MAKEINTRESOURCE(IDI_SKELETON_SM));
34:   wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
35:   wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
36:   wndclass.lpszMenuName   = NULL;
37:   wndclass.lpszClassName  = szAppName;
38:
39:   // Register the window class
40:   if (!RegisterClassEx(&wndclass))
41:     return 0;
42:
43:   // Create the window
44:   hWindow = CreateWindow(szAppName, szAppName, WS_OVERLAPPEDWINDOW,
45:     CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,
46:     NULL, hInstance, NULL);
47:
48:   // Show and update the window
49:   ShowWindow(hWindow, iCmdShow);
50:   UpdateWindow(hWindow);
51:
52:   // Enter the main message loop
53:   while (GetMessage(&msg, NULL, 0, 0))
54:   {
55:     // Process the message
56:     TranslateMessage(&msg);
57:     DispatchMessage(&msg);
58:   }
59:   return (int)msg.wParam;
60: }
61:
62: LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam,
63:   LPARAM lParam)
64: {
65:   HDC         hDC;
66:   PAINTSTRUCT ps;
67:   RECT        rect;
68:
69:   switch (msg)
70:   {
71:     case WM_PAINT:
72:       // Draw some text centered in the client area of the main window
73:       hDC = BeginPaint(hWindow, &ps);
74:       GetClientRect(hWindow, &rect);
75:       DrawText(hDC, TEXT("This is a skeleton application!"), -1, &rect,
76:         DT_SINGLELINE | DT_CENTER | DT_VCENTER);
77:       EndPaint(hWindow, &ps);
78:       return 0;
79:
80:     case WM_DESTROY:
81:       // Exit the application
82:       PostQuitMessage(0);
83:       return 0;
84:   }
85:   return DefWindowProc(hWindow, msg, wParam, lParam);
86: }

This is admittedly a lot of code to throw at you at once, but I wanted you to see the Skeleton.cpp source code file in its entirety so that there would be no mysteries regarding what goes into a Windows program. Fortunately, the game engine that you build in the next lesson helps to hide a great deal of this code so that you never have to worry with it again. But for now we must push on and learn how it works!

Line 4 of the code imports the Skeleton.h header file, which is important because it in turn imports windows.h and Resource.h. Line 9 is a forward declaration of the WndProc() function, which is necessary because the function must be referenced in WinMain() before its code appears. Speaking of WinMain(), its job is to create the main window class (lines 24–37), register the window class (lines 40 and 41), create the main window (lines 44–46), show the main window (lines 49 and 50), and then get the application message loop going (lines 53–58). This certainly looks like a lot of interesting code, but it’s actually quite boring once you see a few Windows programs; this code is largely duplicated verbatim in every Windows program. Fortunately, you’ll be hiding this code in the game engine in the next lesson.

Getting back to WinMain(), you might be curious why the string for the program name is placed inside the apparent function call, TEXT() (line 18). TEXT() is actually a macro, not a function, and its job is to convert text into a form that can be used on a wide range of Windows systems. I won’t get into all the details, but let’s just say that it’s a good idea to work with text using the TEXT() macro so that your program won’t act strange on computers that are set up with a version of Windows designed for another language, such as Japanese.

Another important thing to notice in WinMain() is how both large and small icons are set for the program (lines 30–33). This is more important than you might realize because Windows XP typically displays the small 16×16 icon for programs even though the large 32×32 size was more prevalent in older versions of Windows.

The remainder of the code in the Skeleton.cpp source code file is the WndProc() function, which handles only two messages: WM_PAINT and WM_DESTROY. The WM_PAINT message is sent whenever a program needs to paint the client area (inside) of its window. In this case, a sentence of text is being drawn in the center of the client area to indicate that this is a skeleton application (lines 73–77). You learn all about painting both text and graphics in Hour 4, “Drawing Basic Graphics,” so I’ll skip explaining this code in more detail at this point.

Assembling the Resources

The Skeleton program only uses two resources: the large and small application icons. Before listing these icons in a resource script, it’s important to assign them unique numeric identifiers, which are also known as resource IDs. This is accomplished in the Resource.h header file, which is shown in Listing 2.3.

Example 2.3. The Resource.h Header File Contains Resource IDs for the Skeleton Program Example

 1: //-----------------------------------------------------------------
 2: // Icons                    Range : 1000 - 1999
 3: //-----------------------------------------------------------------
 4: #define IDI_SKELETON        1000
 5: #define IDI_SKELETON_SM     1001

As you can see, it only takes two lines (4 and 5) to define identifiers for the icon resources. Pulling the resources into the Skeleton application simply involves listing them in the application’s resource script, as shown in Listing 2.4.

Example 2.4. The Skeleton.rc Resource Script Contains the Resources for the Skeleton Program Example

 1: //-----------------------------------------------------------------
 2: // Include Files
 3: //-----------------------------------------------------------------
 4: #include "Resource.h"
 5:
 6: //-----------------------------------------------------------------
 7: // Icons
 8: //-----------------------------------------------------------------
 9: IDI_SKELETON       ICON         "Skeleton.ico"
10: IDI_SKELETON_SM    ICON         "Skeleton_sm.ico"

Notice that the Skeleton.rc resource script first includes the Resource.h header file (line 4), and it then specifies the two icon resources by referencing their IDs (identifiers) and their physical locations (filenames). As long as you associate the Skeleton.rc file with the project file for your program in the development environment (compiler) that you’re using, it will be automatically compiled and linked in with the application. As an example, I used Visual C++ to create the examples in this book, and after I added Skeleton.rc to the Skeleton project, I didn’t have to worry about compiling or linking it.

Testing the Finished Product

I wish I could tell you that the Skeleton program example is full of excitement and intrigue when you run it for the first time, but unfortunately this just isn’t the case. Seeing as how it is a minimal Windows application, there isn’t a whole lot you can do with it. However, when you consider that the program “inherits” a lot of functionality from Windows because you can minimize it, maximize it, resize it, drag it around with the mouse, and so on, you start to realize that the Win32 API is quite powerful in its own way. Figure 2.2 shows the finished Skeleton application in action.

The Skeleton program is a good example of a minimal Windows program, and not much else.

Figure 2.2. The Skeleton program is a good example of a minimal Windows program, and not much else.

I warned you: There isn’t much to look at here. Fortunately, you now have enough basic Windows programming skills to start doing some fun things.

Summary

If there was one lesson that I dreaded writing in this book, it was this one. This is because I didn’t relish the idea of departing from the emphasis on creating games for a whole lesson to teach Windows programming. Not only that, but I worried incessantly over how to explain enough of a complex topic in a single lesson without scaring you away from the remainder of the book. Now that it’s behind both of us, let me say that from here on you’ll find the book smooth sailing. Even the most complex game code in the book pales in comparison to being slapped with Windows code when you’ve never faced it before. So, please take this lesson for what it is: a necessary speed bump on the way to becoming a Windows game developer.

Hour 3, “Creating an Engine for Games,” picks up where this lesson leaves off by freeing you from the messy Windows code that lies beneath every Windows program. More specifically, you learn how to develop a game engine that allows you to focus only on the game specific parts of your game programs, as opposed to the overhead code that is only there to satisfy Windows. The game engine also lays the groundwork for integrating advanced game features throughout the remainder of the book.

Q&A

Q1:

What really happens when “Windows sends a program a message”?

A1:

When Windows sends a program a message, what really happens is that Windows calls the WndProc() function in the program, which is then responsible for performing an action based upon the message. Within the WndProc() function, there is a switch statement that looks at one of the function arguments to see what kind of message was sent from Windows. There are also a couple of arguments that contain data specific to the message.

Q2:

Why is it necessary for a main program window to have a window class?

A2:

Window classes are very important in Windows programming because they describe the general characteristics of a window. It is necessary to create a window class for a main program window because the window must be of a type that is uniquely identifiable to Windows. In other words, you can think of all the windows that are floating around in Windows as belonging to different window classes, whereas the main window of your Windows program belongs to its own unique class. In some ways, you can think of a window class as a license to create a certain kind of window.

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:

What’s a handle?

2:

What problem does Hungarian notation attempt to solve?

3:

What standard Win32 function is akin to the main() function in traditional C/C++ programs?

4:

What header file is required as an import into all Windows programs?

Exercises

  1. Use Hungarian notation to come up with variable names for the following variables: an integer that stores the score of a game, a string that stores the name of a color, and a Boolean that keeps track of whether a player in a game is alive.

  2. Change the text displayed in the WM_PAINT message handler in the Skeleton application, and then rebuild and run the program to see the different results.

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

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