Game Programming Genesis

  • Upload
    john

  • View
    35

  • Download
    0

Embed Size (px)

DESCRIPTION

game design

Citation preview

Game Programming Genesis

Game Programming GenesisPart I : Beginning Windows Programming by Joseph "Ironblayde" Farrell

Introduction

The purpose of this article is to introduce the very basics of Windows programming. By its end, you should be able to put together a working, albeit simple Windows program. All that is required is a basic knowledge of C. I rarely use C++ extensions in my code. However, since Windows itself is object-oriented, a little knowledge about classes never hurt anyone. If you're not familiar with it, don't worry, you won't need anything complicated, so you should be able to pick up what you need as you go. All of the code examples included have been tested using the Microsoft Visual C++ 6.0 compiler. If you don't have a Win32 C/C++ compiler, this is the one to get. That said, let's get started!

Setting Up

The two header files that contain most of the Windows functions you'll need are windows.h and windowsx.h. Make sure you include both in your programs. Aside from that, you'll just be using the standard C headers, like stdio.h, conio.h, and so on. Aside from that, there's one line of code you'll see at the beginning of many Windows programs:

#define WIN32_LEAN_AND_MEAN

Besides having a cool sound to it, this line excludes some MFC stuff from the Windows header files, to speed up your build time a little bit. Since you're not likely to be using MFC for games programming, it's probably a good idea to use this most of the time. If you've never seen this type of statement before -- a #define directive followed by only a name -- it has to do with something called conditional compilation. Take a look at this example:

#ifdef DEBUG_MODE

printf("Debug mode is active!");

#endif

If the program containing this code has a line in the beginning that reads #define DEBUG_MODE, then the printf() statement will be compiled. Otherwise, it will be left out. This is a useful way to enable or disable code within your program that helps you track down any logic errors you might have. In the case of WIN32_LEAN_AND_MEAN, its definition is used to remove rarely-used components of the Windows header files. Got it? Good. On to the code...

The WinMain() function

Just as all DOS-based C programs begin execution with the main() function, Windows programs begin with the WinMain() function. A basic, empty WinMain() function looks something like this:

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

return(0);

}

For a function that does nothing but return a value, it's sure got a lot of unfamiliar stuff in it! First things first; what's the deal with the WINAPI declarator? This is an example of what's known as a calling convention. It affects such things as the way parameters are passed to the function, which function performs stack cleanup, and a few other things that you never really see. A function with the WINAPI calling convention passes parameters left-to-right, as opposed to the default right-to-left order. Unless you're going to be using assembly language with your programs, you don't need to know all the details of how calling conventions work, only that WinMain() must have the WINAPI convention specified.

Next, let's take a look at the four parameters that the function receives:

HINSTANCE hinstance: This is a handle to the instance of your application. Basically, these are like pointers that are used to keep track of all the applications that are running at any given time. Many Windows functions take the instance of your application as a parameter, so it knows which application to apply the action to.

HINSTANCE hPrevInstance: You don't need to worry about this parameter, as it's now obsolete. In older versions of Windows, this would be a handle to the instance of the application that called your application. The only reason it's included anymore is for backwards compatibility. You'll see a few more things like that as you go on with Windows programming.

LPSTR lpCmdLine: This is a pointer to a string containing the command-line parameters used when the program was invoked. Note that there is no parameter specifying the number of command-line parameters, so you'll need to determine that yourself.

int nCmdShow: This integer indicates how the main window should be opened. You don't need to do anything with this if you don't want to. It takes values given by constants beginning with SW_. Some examples are SW_SHOWNORMAL for the default method, SW_MAXIMIZE or SW_MINIMIZE for maximizing or minimizing windows, etc.

That's about it for WinMain()'s parameters. Often, the only one that will be of any consequence is hinstance. Before we go on to actually displaying a window, something needs to be said about the way Microsoft names variables.

Hungarian Notation

Microsoft uses a standardized way of naming variables, functions, constants, and classes that is known as Hungarian notation. You've already seen an example of this in the WinMain() function. The Hungarian notation for variable names consists of several prefixes which reveal the variable's data type. These are the prefixes used:

bBOOL (int)

byBYTE or UCHAR (unsigned char)

cchar

cx, cyshort (usually lengths; c stands for "count")

dwDWORD (unsigned long)

fnFunction pointer

hHandle (like a pointer, used for Windows objects)

iint

lLONG (long int)

lpLong pointer (32-bit)

msgMessage (we'll cover this later)

nnumber (short or int)

sString

sz, strNull-terminated ("ASCIIZ") string

wWORD or UINT (unsigned int)

x, yshort (usually coordinates)

In addition, variable names consisting of more than one word have each word capitalized, with no underscores. For instance, a pointer to an area of memory used for player data might be called lpPlayerData. This standard notation is often helpful in understanding code. For instance, in the WinMain() function discussed above, without seeing the function header, Hungarian notation tells you that hinstance and hPrevInstance are handles, lpCmdLine is a 32-bit pointer, and nCmdShow is an integer.

Function naming under Hungarian notation follows the same rules as variables, minus the prefixes. In other words, the first letter is capitalized, and the first letter of subsequent words in the function name are capitalized as well. An example might be ShowCharacterStats().

The rule for naming constants is that all capital letters are used, and underscores often separate words within a constant's name, or separate a prefix from a constant's name. The constant WIN32_LEAN_AND_MEAN is an example. One thing you'll see often in Windows is that constants are often prefixed by an abbreviation of the function with which they are meant to be used. For example, the constants SW_SHOWNORMAL, SW_MAXIMIZE, and SW_MINIMIZE, which I mentioned briefly earlier, have the SW_ prefix because they are meant to be used as arguments to a function called ShowWindow().

Finally, classes are named in the same manner as functions, except with a capital C preceding the name, so an example class for a vehicle in a racing game might be CVehicle.

You don't have to use this naming convention in your programs, but you should be familiar with it, because all Microsoft products follow these guidelines. It is rather convenient if you can convince yourself to start using it. I'm still working on that. Anyway, moving on...

Messages

When you were programming in DOS, you didn't need to worry about other programs that might be running, because DOS is not a multitasking OS. When programming in Windows, however, you must take this into consideration. For this and other reasons, Windows uses what are called messages to communicate with applications and tell them what's going on. Messages serve a variety of purposes. They tell an application when its window is being resized, moved, or closed. They signal to a program that it is about to be closed. They inform a program when part of its window must be refreshed. They can be used to track mouse movement and button presses. The list goes on. In any case, your Windows program must be able to handle these messages.

The way this is done is through the use of a special type of function called a callback function. Callback functions are functions which you don't actually call from your code; rather, certain events cause them to be called. You create such a function by using the CALLBACK calling convention, much like the WINAPI convention is used for WinMain(). I'm going to leave this topic for a minute, though, because before you can process messages for your window, you have to be able to create the window in the first place.

Window Classes

Here's where it helps to know a little about C++, because the first thing you must do to create a window is to create a window class. The class contains all the information about the window such as what icon it uses, the menu attached to it (if any), etc. In just about every Windows program you create, you'll need to create a window class to meet your needs. In order to do this, you need to fill out a WNDCLASSEX structure. The "EX" part of the name stands for "extended," as there is an older version of this structure called WNDCLASS. We'll be using the extended version. Here's what it looks like:

typedef struct _WNDCLASSEX {

UINT cbSize;

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HANDLE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCTSTR lpszMenuName;

LPCTSTR lpszClassName;

HICON hIconSm;

} WNDCLASSEX;

This structure has quite a few members to it, and you must set them all in order to create your window class. It's not so bad, though. Let's run through a brief description of all the fields.

UINT cbSize: This is the size of the structure, in bytes. You'll see this a lot, especially if you get into DirectX. It's included so that if a structure of this type (or rather, a pointer to that structure) is passed as a parameter to a function, the structure size can simply be looked up rather than having to be computed. Always set it to sizeof(WNDCLASSEX).

UINT style: This is the window style, which takes constants prefixed by CS_. Furthermore, you can combine several of these constants by using the | (bitwise OR) operator. Most times there are only four you'll use. For the sake of keeping the length of this article down, I'll show you those four. You can always look up the rest on your MSDN Help. You did remember to get Visual C++, didn't you?

CS_HREDRAWSpecifies that the window should be redrawn if it is horizontally resized.

CS_VREDRAWSpecifies that the window should be redrawn if it is vertically resized.

CS_OWNDCAllows each window to have a unique device context or DC (not covered in this article).

CS_DBLCLKSDiscerns between single- and double-clicks while the mouse is in this window.

WNDPROC lpfnWndProc: A pointer to the callback function that handles messages sent to this window. If you've never used function pointers, the address of a function is simply the function's name, without the parenthesis afterwards.

int cbClsExtra: This is reserved for extra class info, which most programs don't need. Certainly you won't find many uses for this when writing games, so simply set it to 0.

int cbWndExtra: Basically the same as cbClsExtra, except for extra window information. You'll almost always be setting this one to 0 as well.

HANDLE hInstance: This is the instance of the application using the window class, which is one of the parameters passed to WinMain(). This should be set to hinstance.

HICON hIcon: This is a handle to the icon that represents the program, and will usually be set using the LoadIcon() function. Until you learn how to use resources in your programs, you can set this to a generic system icon by using LoadIcon(NULL, IDI_WINLOGO). There are other IDI_ constants representing Windows icons; you can find the list in the Help files for your compiler.

HCURSOR hCursor: This is a handle to the cursor used for the mouse while it is in your window. This is usually set using the LoadCursor() function. Again, you can use resources to load your own custom cursors, but until you learn that, or if you just want the standard Windows cursor, use LoadCursor(NULL, IDC_ARROW).

HBRUSH hbrBackground: When your window receives a message that it needs to be refreshed (or "repainted"), the least that will happen is that Windows will repaint the area with a solid color or "brush." That brush is defined by this parameter. There are several kinds of stock brushes you can load using the GetStockObject() function. Some of these are BLACK_BRUSH, WHITE_BRUSH, GRAY_BRUSH, etc. For now, you're safe using GetStockObject(BLACK_BRUSH). Sorry I'm touching on all of these functions so briefly, but I'm trying to keep the length down. I'll revisit them in future articles, I promise!

LPCTSTR lpszMenuName: If you want to create a window with pull-down menus, this parameter gives the name of the menu to load and attach to the window. Since you don't know how to create menus yet, you can specify no menu by setting this to NULL.

LPCSTR lpszClassName: This is simply a name by which you refer to the class. You can call it anything you want, so use a descriptive name. You might call it "Game_Class" or something like that.

HICON hIconSm: This is a handle to the small icon used on the window's title bar and on the Start Menu bar. You set this the same way you set hIcon -- by using the LoadIcon() function. For now, we'll use LoadIcon(NULL, IDI_WINLOGO) for the standard Windows logo icon.

That's it! Now that you're familiar with all the fields of the WNDCLASSEX structure, you can fill it out and you're ready to create a window. A sample class might look like this:

WNDCLASSEX sampleClass; // declare structure variable

sampleClass.cbSize = sizeof(WNDCLASSEX); // always use this!

sampleClass.style = CS_DBLCLKS | CS_OWNDC |

CS_HREDRAW | CS_VREDRAW; // standard settings

sampleClass.lpfnWndProc = MsgHandler; // we need to write this!

sampleClass.cbClsExtra = 0; // extra class info, not used

sampleClass.cbWndExtra = 0; // extra window info, not used

sampleClass.hInstance = hinstance; // parameter passed to WinMain()

sampleClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Windows logo

sampleClass.hCursor = LoadCursor(NULL, IDC_ARROW); // standard cursor

sampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // a simple black brush

sampleClass.lpszMenuName = NULL; // no menu

sampleClass.lpszClassName = "Sample Class" // class name

sampleClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // Windows logo again

And you're all set. There is one thing I should mention, though. Notice the typecast to HBRUSH on the result of the GetStockObject() function. This is because GetStockObject() can be used to load many objects, not just brushes, and so it returns a variable of type HGDIOBJ, which is a bit more general. In older versions of Visual C++, you wouldn't need the typecast, but VC++ 6.0 is more picky about typecasting, so you'll get an error if you try to compile without it.

The last thing you need to do is register the new class with Windows so you can use it to create new windows. This is accomplished with a simple function call to RegisterClassEx(). It only takes one parameter: the address of your structure. So in the example listed above, you would register the class like this:

RegisterClassEx(&sampleClass);

Now that Windows is familiar with the new class you've created, you can use it to create a window. It's about time, hey?

Creating Windows

The good news is that all you need to create a window is a call to CreateWindowEx(). The bad news is that this function takes a lot of parameters. You're probably getting sick of these long lists by now, but this one isn't too bad. Here's the function prototype:

HWND CreateWindowEx(

DWORD dwExStyle, // extended window style

LPCTSTR lpClassName, // pointer to registered class name

LPCTSTR lpWindowName, // pointer to window name

DWORD dwStyle, // window style

int x, // horizontal position of window

int y, // vertical position of window

int nWidth, // window width

int nHeight, // window height

HWND hWndParent, // handle to parent or owner window

HMENU hMenu, // handle to menu, or child-window identifier

HINSTANCE hInstance, // handle to application instance

LPVOID lpParam // pointer to window-creation data

);

First things first: the return value. By now, all these crazy data types that Windows uses are probably starting to look familiar. If not, don't worry, you'll get used to it sooner than you think. The return type is HWND, which is a handle to a window. You'll want to store the value returned by CreateWindowEx(), as you'll need it as a parameter for several Windows functions. Now, let's tackle that parameter list. Many are self-explanatory.

DWORD dwExStyle: Extended window style is something you'll rarely use, so you can set this to NULL most of the time. If you're interested, the Help files for your compiler list a ton of constants beginning with WS_EX_ that can be used here.

LPCTSTR lpClassName: Remember when you named the class you created? Just pass that name here.

LPCTSTR lpWindowName: This is simply the text that will appear on the window's title bar.

DWORD dwStyle: The window style parameter allows you to specify what type of window you want to create. There are a lot of constants that can be used here, beginning with WS_, and they can be combined with the | operator. I'll list just a few of the common ones:

WS_POPUPA window that has no controls built into it.

WS_OVERLAPPEDA window with simply a title bar and a border.

WS_OVERLAPPEDWINDOWA window with a title bar including all standard controls.

WS_VISIBLESpecifies that the window is initially visible.

The WS_OVERLAPPEDWINDOW constant is actually a combination of several other constants in order to create a standard window. Basically, you can follow these guidelines. If you want a window that can be maximized, minimized, resized, etc., use WS_OVERLAPPEDWINDOW. If you want a window with a title bar but which has a fixed size, use WS_OVERLAPPED. If you want a window that has no controls on it whatsoever, use WS_POPUP. Such a window will just appear as a black rectangle originally. This is what you'll probably use for writing fullscreen games. Also, always specify the WS_VISIBLE flag, unless for some reason you don't want anyone to see your window, or if you want to take care of some other things first, and display the window later.

int x, y: These are the coordinates on the screen at which the upper-left corner of the newly created window will appear.

int nWidth, nHeight: These are, you guessed it, the width and height of the window, in pixels.

HWND hWndParent: This is a handle to the parent window of the window you're creating. This is mostly used with controls such as checkboxes and pushbuttons. For creating a main window, set this to NULL, which represents the Windows desktop.

HMENU hMenu: This is a handle to the menu which should be attached to the window. If you're loading a resource menu -- after you learn how to do that -- you would use the LoadMenu() function. For a window with no menu attached, simply set this to NULL.

HINSTANCE hInstance: This is the instance of the application; again, pass the parameter that was passed to WinMain().

LPVOID lpParam: This is something you're not likely to use, especially for games, where only simple windows are needed. It's used for creating things like multiple document interfaces. Just set it to NULL.

At last, we have everything we need to create a window. Here's a sample call that would get the job done:

HWND hwnd;

if (!(hwnd = CreateWindowEx(NULL, // extended style, not needed

"Sample Class", // class identifier

"Sample Window", // window title

WS_POPUP | WS_VISIBLE, // parameters

0, 0, 320, 240, // initial position, size

NULL, // handle to parent (the desktop)

NULL, // handle to menu (none)

hinstance, // application instance handle

NULL))) // who needs it?

return(0);

This might be something you'd use for a game, because it's a popup window. Notice that I've enclosed the CreateWindowEx() call inside an if statement. This is because if CreateWindowEx() fails, it returns NULL. The way this statement is set up, if the window can't be created for some reason, WinMain() simply returns and the program ends.

Now you've almost got enough to make a Windows program that creates a functional window. Almost. Remember when we created "Sample Class," and had to provide a pointer to a message handler function? We need to write that function before Windows will let us create anything.

Handling Messages

I've already explained some of the things messages are used for in Windows. Now I'll go over how to make use of them. The prototype for a message handling function looks like this:

LRESULT CALLBACK MsgHandler(

HWND hwnd, // window handle

UINT msg, // the message identifier

WPARAM wparam, // message parameters

LPARAM lparam // more message parameters

};

The LRESULT type of the return value is used specfically for message processing functions like the one we're going to write. I talked about the CALLBACK convention a little bit earlier. The parameters are very simple:

HWND hwnd: This is the handle of the window that sent the message currently being processed.

UINT msg: This is a message identifier. The values for this parameter are constants beginning with WM_ (for "Windows message"). The number of different messages that can be sent is ridiculously high, but here are some important ones:

WM_ACTIVATEA new window is receiving the focus.

WM_CLOSEA window is being closed.

WM_COMMANDA menu option has been selected.

WM_CREATEA window has been created.

WM_LBUTTONDBLCLKLeft mouse button has been double-clicked.

WM_LBUTTONDOWNLeft mouse button has been pressed.

WM_MOUSEMOVEThe mouse has been moved.

WM_MOVEA window has been moved.

WM_PAINTPart of a window needs to be repainted.

WM_RBUTTONDBLCLKRight mouse button has been double-clicked.

WM_RBUTTONDOWNRight mouse button has been pressed.

WM_SIZEA window has been resized.

WM_USERUse this for whatever you want.

WPARAM wparam, LPARAM lparam: The exact use of these parameters depends on which message is being sent, but they are used to further specify the meaning of the message.

If you had to write code to handle every message that your window might receive, you'd probably go insane. I know I would! Thankfully, Windows provides a default message handler. If you don't have any special instructions for handling certain messages, you can always call DefWindowProc(). With that in mind, here is the simplest, fully functional message handler that you could possibly write:

LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)

{

return(DefWindowProc(hwnd, msg, wparam, lparam));

}

Simple, hey? Usually you'll want to handle some of these messages yourself. In that case, you can write your own code, and return 0 to tell the program that you've dealt with the message. Here's an example of a message handler that calls an initialization function when the window is created, and calls the default handler for anything else.

LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)

{

if (msg == WM_CREATE)

{

Initialize_Game();

return(0);

}

return(DefWindowProc(hwnd, msg, wparam, lparam));

}

Your message handler will probably end up being a big switch statement to accommodate the messages you want to handle manually, followed by a call to the default handler for everything else. Now there's just one more thing I need to show you before everything is working smoothly, and that's how to make sure your message handler is getting called when it has work to do.

Reading the Message Queue

Near the beginning of your program's main loop, you need to see if the message queue -- where all pending messages are stored -- has anything waiting for you. If so, there are a few things you need to do in order for your handler to do its job correctly. The function you need here is PeekMessage(). Here is its prototype:

BOOL PeekMessage(

LPMSG lpMsg, // pointer to structure for message

HWND hWnd, // handle to window

UINT wMsgFilterMin, // first message

UINT wMsgFilterMax, // last message

UINT wRemoveMsg // removal flags

);

The return type, BOOL, is really just an int, but it takes only two values: TRUE or FALSE. If a message is waiting on the queue, the function returns TRUE. Otherwise, it returns FALSE. The parameters are pretty straightforward:

LPMSG lpMsg: This is a pointer to a variable of type MSG. If a message is waiting, this variable will be filled with the message information.

HWND hWnd: The handle of the window whose queue you want to check.

UINT wMsgFilterMin, wMsgFilterMax: The indices of the first and last messages in the queue to check. Most of the time, you'll only be interested in the first message on the queue, so you would set both of these parameters to 0.

UINT wRemoveMsg: Generally this takes only two values, PM_REMOVE or PM_NOREMOVE. Use the former if you want to remove the message from the queue after reading it, and the latter if you want to leave the message on the queue. Usually, if a message is waiting, you'll prepare it to be handled right away, in which case you should use PM_REMOVE.

If a message is waiting, you need to do a few things to get your handler to kick in. Don't worry, it's only two simple calls: one to TranslateMessage() and one to DispatchMessage(). Their prototypes are very similar:

BOOL TranslateMessage(CONST MSG *lpmsg);

LONG DispatchMessage(CONST MSG *lpmsg);

The first call performs a bit of translation on the message, as you may have guessed, and the second call invokes your message handler and sends it the appropriate information from the MSG structure. That's all you need to know! With every iteration of your main loop, if a message is waiting, you call these two functions and your MsgHandler() function takes care of the rest. Here's a code example:

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

No problem! Now you can write a Windows program that creates and registers a window class, and creates a window with a valid message handler. That wasn't so bad, was it? There are just a few more things I'd like to mention before I wrap this article up. While we're on the topic of messages, there will come a time when you want to send messages manually. Here's how.

Sending Messages

There are actually two ways to go about doing this. You can either call PostMessage() or SendMessage(). Their prototypes are very similar:

BOOL PostMessage(

HWND hWnd, // handle of destination window

UINT Msg, // message to post

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

);

LRESULT SendMessage(

HWND hWnd, // handle of destination window

UINT Msg, // message to post

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

);

The parameters are the same as those taken by the MsgHandler() function we wrote, so I won't go over them again. The only thing you need to know is the difference between the two functions, so I'll go over each one briefly.

PostMessage() is used when you simply want to add a message to the queue and let your program logic take care of it. The function returns a nonzero value (TRUE) if it succeeds, or zero (FALSE) if it fails. It simply adds the message you specify to the queue, and returns immediately. In most cases, a call to PostMessage() will get the job done.

SendMessage() is a bit different. Notice that it returns an LRESULT, which is used for message processing functions. That's because SendMessage() doesn't post a message to the queue -- it translates the message and invokes the message handler immediately, and doesn't return until the message handler has finished processing the message. SendMessage() is used rather than PostMessage() for events of higher priority that need to occur quickly. Use it when you want something done immediately.

Now that you know that, the topic of messages leads into the last topic I need to mention for now, and that is a major difference between Windows programming and DOS programming.

Program Flow

In DOS, you don't need to worry about any of this message stuff. You don't need to concern yourself with multiple programs running simultaneously. But when you're programming in Windows, these are very important matters. As a result, your programs need to work a bit differently than they do in DOS. Consider this bit of pseudo-code:

// main game loop

do

{

// handle messages here

// ...

// update screen if necessary

if (new_screen)

{

FadeOut();

LoadNewMap();

FadeIn();

}

// perform game logic

WaitForInput();

UpdateCharacters();

RenderMap();

} while (game_active);

Suppose FadeOut() works like this: when the function is called, it dims the image on the screen over a period of about a second. When the screen is totally black, the function returns. FadeIn() works in a similar fashion. WaitForInput() simply waits until a key is pressed. Perhaps it stores the input in a global variable somewhere. Now, in a DOS-based game, this is a perfectly acceptable way to do things. In a Windows game, it is certainly not!

Why not? Well, what happens when new_screen becomes true? It fades the screen out, loads a map, and fades back in. Altogether this takes about two seconds. That's two seconds during which no messages are being processed, so the user could do something such as minimize the window, but the program will keep running like it hasn't happened yet. This sort of thing can cause erroneous output, general protection faults, etc. Needless to say, this is unacceptable. The WaitForInput() function is even worse, because it suspends the flow of the program until a key is pressed during every frame. Whereas the previous example has the potential to cause trouble, this one is a near-certainty.

The bottom line is that if your game runs at 30 FPS, you need to make sure the entire main loop executes 30 times a second. Each iteration of the main loop should show only one frame, not many frames as in the theoretical FadeOut() function in the example. When first learning Windows programming, this can be a bit of an obstacle, because it's a different way of thinking. However, once you figure out how to set up your program to run this way, I think you'll find it makes for a much more organized and flexible program.

Closing

That's about it for basic Windows programming. While the example we developed over the course of the article doesn't do much except display a window, it contains the entire framework for a functional Windows application. Next time I'll get into handling resources, which allows you to incorporate custom icons, cursors, sounds, menus, and more -- right into your .EXE!

If you have any questions or comments about this article or anything else, feel free to contact me via E-mail at [email protected], or via ICQ. My UIN is 53210499. Until next time, farewell!

Game Programming GenesisPart II : Using Resources in Win32 Programs by Joseph "Ironblayde" Farrell

Introduction

Welcome back! As you may have guessed by the title, in this article I'm going to show you how to use resources in your Windows programs. Simply put, resources are binary data that's appended to your .EXE file after the actual program code. Using resources is easy to learn and has a lot of advantages. It allows the developer to consolidate a lot of data into one file, include custom icons and such things with their programs, and prevent users from altering that data. Windows supports a large number of resource types, so I'm just going to cover the ones I think are most convenient and easiest to learn: bitmaps, cursors, icons, menus, and string tables. After that, I'll show you how to create a custom resource type, so you can include anything you want.

Again, all you need to understand this article is a basic understanding of the C language. C++ always helps since Windows itself is object-oriented, but most of my code is straight C. Also, I will assume that you have read my previous article, "Beginning Windows Programming," or have the equivalent knowledge. I use and recommend the Microsoft Visual C++ compiler, but if you're using a different one, it's not a big deal. Ready? Here we go!

Resource Scripts

Before we get into any of the specific resource types, we need to go over the method used to tell the compiler what resources to include, and how. This method is to use a special file called a resource script, which is simply a text file either written by the developer or automatically generated by Visual C++, or whatever IDE you happen to be using. Your script file should have the file extension .rc. Most of a script file is taken up by lines which define or specify the resources to include. The simplest of these lines is used by several resource types, and looks like this:

[identifier] [resource type] [filename]

The identifier can be one of two things: a string representing the resource, or a numeric constant that's #defined in a header file meant to accompany the resource script file. If you use numeric constants, which is usually a good idea, you can use the #include directive in your script file to include the header that corresponds to it. You can also use C-style comments to make things a little easier to understand. That said, here's what a very simple resource script file might look like:

#include "resource.h"

// icons

ICON_MAIN ICON myicon.ico

// bitmaps

IMG_TILESET1 BITMAP tileset.bmp

IMG_TILESET2 BITMAP tileset2.bmp

That's not too bad, right? There's one thing that can be confusing, though. Just by looking at my brief example, you can't tell if ICON_MAIN and IMG_TILESET are meant to be strings or numeric constants. The file would appear the same no matter which case were true. At compile time, your compiler will look at the identifiers you're using and search through your header files looking for their definitions. If no matching #define statements are found, it's assumed that you're using string identifiers.

Don't worry about the actual lines themselves just yet; I'll explain each type of entry when I get to that particular resource. If you don't want to bother with resource scripting at all, you can just insert the resources from your IDE (in Visual C++, go to "Resource..." under the Insert menu) and a resource script will be generated automatically. I prefer to do it myself with good old Notepad, but don't ask me why because I can't think of a good reason. :) Now that you know the basics of creating a resource script, let's get started on the specific resource types.

Icons and Cursors

Most of the Windows programs you use every day have their own icons built in, and now you know how it works: they're simply resources included in the EXE file. Custom cursors that are used by those programs are also included as resources. You've already seen an example of the script line that includes an icon resource, and the line for cursors is very similar. Here they are:

[identifier] CURSOR [filename]

[identifier] ICON [filename]

After adding a line such as this to your script file -- make sure to include the script file in your project -- the icon or cursor specified by [filename] will be included as a resource in your EXE file. That's all there is to it! You can use any icon/cursor editor to generate the files you want to include. I use the one that's included in Visual C++.

Including the resources doesn't do a whole lot for your program, though, because you don't know how to use them yet! To get an idea for how icon and cursor resources are utilized in a program, let's revisit the window class we developed in the last article:

WNDCLASSEX sampleClass; // declare structure variable

sampleClass.cbSize = sizeof(WNDCLASSEX); // always use this!

sampleClass.style = CS_DBLCLKS | CS_OWNDC |

CS_HREDRAW | CS_VREDRAW; // standard settings

sampleClass.lpfnWndProc = MsgHandler; // message handler function

sampleClass.cbClsExtra = 0; // extra class info, not used

sampleClass.cbWndExtra = 0; // extra window info, not used

sampleClass.hInstance = hinstance; // parameter passed to WinMain()

sampleClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Windows logo

sampleClass.hCursor = LoadCursor(NULL, IDC_ARROW); // standard cursor

sampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // a simple black brush

sampleClass.lpszMenuName = NULL; // no menu

sampleClass.lpszClassName = "Sample Class" // class name

sampleClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // Windows logo again

You remember this, don't you? The hIcon field specifies the icon to be used to represent the program, and the hIconSm field is the icon used on the Start Menu and the window's title bar. The hCursor field sets the cursor to be used when the mouse is within the boundaries of the window you create. I promised you we'd take a look at the functions used to fill these fields a little more closely, so here are their prototypes:

HICON LoadIcon(

HINSTANCE hInstance, // handle to application instance

LPCTSTR lpIconName // icon-name string or icon resource identifier

);

HCURSOR LoadCursor(

HINSTANCE hInstance, // handle to application instance

LPCTSTR lpCursorName // name string or cursor resource identifier

);

The return type is a handle to the cursor you're loading. The parameters are very straightforward:

HINSTANCE hInstance: This is a handle to the instance of your application. To load resources from your program, just pass the HINSTANCE that is passed to your WinMain() function when the program is executed. To use standard Windows resources like we did in the window class above, set this to NULL.

LPCTSTR lpIconName, lpCursorName: This is a string identifier that identifies the resource you want to load. If your script file refers to resources by string, simply pass the string. But if you're using numeric constants, the Windows header files include a macro that changes an integer to a form compatible with this parameter called MAKEINTRESOURCE().

As an example, let's look at the line that sets the icon to represent the program. Suppose your resource script file looks like this:

#include "resource.h"

ICON_MAIN ICON myicon.ico

CURSOR_ARROW CURSOR arrow.cur

If the identifiers ICON_MAIN and CURSOR_ARROW do not have matching #define statements somewhere in resource.h, then you would pass the corresponding string to the appropriate resource-loading function, like this:

sampleClass.hIcon = LoadIcon(hinstance, "ICON_MAIN");

Now let's say that resource.h contains a few #define directives:

#define ICON_MAIN 1000

#define CURSOR_ARROW 2000

Now you have to use the MAKEINTRESOURCE() macro to turn the numerical identifier into something of type LPCTSTR. This gives you a little more ease of flexibility in loading resources. Any of the following calls would be correct:

sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(ICON_MAIN)); or...sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(1000)); or...int ident = 1000;sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(ident)); That's about all you need to know about including icons and cursors in your programs, but I'll mention one more thing while we're on the topic. If you want to set a cursor sometime other than at the beginning of the program, there's a simple Windows function you can use to accomplish this:

HCURSOR SetCursor(HCURSOR hCursor);

The one parameter is the handle you get by calling LoadCursor(), and the handle that is returned is a handle to the previous cursor. If no previous cursor was set, the return value is NULL. Relatively painless, wouldn't you say? Let's move on to something a bit more interesting.

Bitmaps

Including bitmap resources is probably the easiest way to add images to your program. Bitmaps are native to Windows and so there are functions included to deal with loading and manipulating them, but remember, if you include too many, you'll end up with an enormous .EXE file. In any case, you include bitmaps in your resource script file in basically the same way you handle icons and cursors:

[identifier] BITMAP [filename]

There is a function called LoadBitmap() that is analagous to LoadCursor() and LoadIcon(); it is used to retrieve a handle to a bitmap, but since I haven't talked about graphics yet, I won't describe this function here. You can probably guess exactly how it works, but once you have a handle to a bitmap, what would you do with it? More to come on that in the future, don't worry! For now, I just wanted to show you how to include a bitmap resource. Now let's look at something you can use right away.

String Tables

The string table is one of my favorite resource types. It's exactly what you're thinking: a giant table full of strings. There are any number of purposes for using a string table. You can use it to store data filenames, character dialogue for a game, message-box text, text for menus that are generated by the program, anything you want. Creating a string table in your script file is easy. Here's what it looks like:

STRINGTABLE

{

// entries go here

}

An entry in a string table consists of a number to identify the string, followed by a comma, then the string itself, enclosed in double quotation marks. The strings in a string table can include escape sequences like \n or \t. Note that the string table itself does not have an identifier, so each program you write can include only one string table. A simple string table might look something like this:

// program information

STRINGTABLE

{

1, "3D Space Game v1.0"

2, "Written by The Masked Coder"

3, "(C) 2000 WienerDog Software"

}

To load a string from your program's string table, you use the -- you guessed it -- LoadString() function. Here is its prototype:

int LoadString(

HINSTANCE hInstance, // handle to module containing string resource

UINT uID, // resource identifier

LPTSTR lpBuffer, // pointer to buffer for resource

int nBufferMax // size of buffer

);

The integer returned by the function is the number of characters, excluding the terminating null character, that were successfully copied into the buffer. This corresponds to the length of the string. If you load a blank string, or if the function fails, the return value is 0. Take a look at the parameters:

HINSTANCE hInstance: Once again, this is the instance of your application.

UINT uID: This is the number that identifies the particular string you want to load.

LPTSTR lpBuffer: This is a pointer to the location you want the string copied to.

int nBufferMax: This is the size of the buffer in bytes. If the string to be loaded is longer than the buffer can hold, the string is truncated and null-terminated.

For example, to load WienerDog Software's copyright message, the following code would be used:

char buffer[80];

LoadString(hinstance, 3, buffer, sizeof(buffer));

Even though the declaration of a string table in your script file has to use numbers and not identifiers, I usually #define a number of string table constants in one of my header files when using a string table. For instance, to accompany the string table above, I might have a line like:

#define ST_WIENERDOGCOPYRIGHT 3

Your code will be much easier to read if you have LoadString() calls that use readable constants for the uID parameter, rather than just having the index numbers. This doesn't mean you should have a constant for every string table entry; that would take ages if you have a large string table. Usually I like to use one #define per "section" of the string table. For instance, ST_FILENAMES for the first index where filenames are stored, ST_DIALOGUE for the first index of the character dialog strings, etc.

Menus

This is the last type of Windows resource I'll go over, and it's also one of the most useful. Menu resources are used to define the menu bar that would appear underneath the title bar of your application, and are loaded during the definition of the window class. Looking back, in the window class we developed during the last article, there was a line that looked like this:

sampleClass.lpszMenuName = NULL;

If you're creating a windowed application, chances are that you'll want to have a menu bar of some sort. This is done using the menu resource. The script file entry for this one can get a little complicated, but here is its most basic form:

[identifier] MENU

{

POPUP [menu name]

{

MENUITEM [item name], [identifier]

}

}

The identifier is what you're used to: either a string or a numeric constant that is used to refer to the menu. Within the MENU brackets, there can be one or more POPUP menus, each of which represent a pull-down menu, whose name is given by [menu name]. Within the POPUP brackets, there can be one or more MENUITEMs, each of which represents a final menu selection, with a name given by [item name] and an identifier that must be a numeric constant. Within the menu and item names, if you want that option to be accessible by a keyboard shortcut, you precede the letter of the shortcut with the ampersand (&). For instance, if you want to create a File menu accessible by pressing Alt+F, the menu name should be &File. Menu and item names should be enclosed in double quotation marks. With that, here is an example of a simple menu resource:

MAIN_MENU MENU

{

POPUP "&File"

{

MENUITEM "&New", MENUID_NEW

MENUITEM "&Open...", MENUID_OPEN

MENUITEM "&Save", MENUID_SAVE

MENUITEM "Save &As...", MENUID_SAVEAS

MENUITEM "E&xit", MENUID_EXIT

}

POPUP "&Help"

{

MENUITEM "&Contents", MENUID_CONTENTS

MENUITEM "&Index...", MENUID_INDEX

MENUITEM "&About", MENUID_ABOUT

}

}

You can also create submenus by including one POPUP inside of another, specify menu items as being initially grayed or checked, or do several other more advanced things, but I'm not going to go into that here. To obtain a handle to a menu resource, use the LoadMenu() function whose prototype is shown here:

HMENU LoadMenu(

HINSTANCE hInstance, // handle to application instance

LPCTSTR lpMenuName // menu name string or menu-resource identifier

);

You should be used to these parameters by now. The first one is the instance of your application, and the second is the identifier you assigned to the menu. Remember to use MAKEINTRESOURCE() if you used a numerical constant. Now, to attach a menu to a window, you have two options. The first is to set the menu as the default for your window class, like this:

sampleClass.lpszMenuName = LoadMenu(hinstance, MAKEINTRESOURCE(MAIN_MENU));

The second option is to leave lpszMenuName equal to NULL, and attach a menu yourself later. This can be useful if you want to create two windows with different menus, but don't want to define two separate window classes. To attach a menu, use the SetMenu() function:

BOOL SetMenu(

HWND hWnd, // handle to window

HMENU hMenu, // handle to menu

);

The return value is TRUE if the function succeeds, or FALSE if it fails. The parameters are pretty easy to figure out:

HWND hWnd: This is the handle to the window to which you want to attach the menu. Pass the handle that was returned when you called CreateWindowEx().

HMENU hMenu: To identify the menu, pass the handle returned by LoadMenu(). If you pass NULL, the specified window's menu is removed.

This resource is particularly nice because all the functionality of the menu is defined by that simple scripting. But what happens when the user selects a menu option? The answer is that Windows sends a WM_COMMAND message informing the program that it must take action. Let's pay a visit to our message-handling function and see if we can't figure out how to handle this.

Handling Menu Events

As you probably remember, Windows messages are handled by a special callback function usually called WindowProc() or something similar. The simple one we wrote last time was called MsgHandler(), and its prototype looked like this:

LRESULT CALLBACK MsgHandler(

HWND hwnd, // window handle

UINT msg, // the message identifier

WPARAM wparam, // message parameters

LPARAM lparam, // more message parameters

};

When a menu message is sent, msg will be WM_COMMAND, and the menu item that was selected will be contained in wparam. This is why menu item identifiers can't be strings; they need to fit into the wparam parameter. More specifically, the menu item identifier is the low word of wparam. To extract the low or high word of a 32-bit variable type like WPARAM, LPARAM, int, etc. Windows provides macros called LOWORD() and HIWORD() that do the job. They are shown here:

#define LOWORD(l) ((WORD) (l)) #define HIWORD(l) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))

In the case of LOWORD(), the typecast to WORD simply truncates the value to the lower 16 bits. HIWORD() shifts the upper 16 bits to the right, then performs a logical AND with 0xFFFF just to be sure any bits above the lower 16 are all set to zero. If you're not familiar with the >> and operator shifts to the right. For example, suppose you had a 16-bit variable x whose value was 244. In binary this is 0000 0000 1111 0100. The following example shows a bit shift, and the effect on x:

short int x = 244, y;

y = x New goes here

break;

case MENUID_OPEN:

// code to handle File->Open goes here

break;

// the rest of the option handlers go here

}

// tell Windows you took care of it

return(0);

}

Make sense? Good. That about wraps it up for the specific resource types I'm going to cover. There are others, such as accelerator tables (tables full of keyboard shortcuts), HTML pages, WAV files etc. but I think these are the most useful. Before I wrap this up, though, there's one more very powerful feature of Windows programs I'm going to show you, and that's defining a custom resource type.

Custom Resources

The standard Windows resources are those which have special functions for loading and handling them, but they are not the only types you can use. Resources can be any data you want them to be! Working with custom resources requires a little more work since you must locate and read the resource data manually, but it's not too bad. The script file entry for a custom type follows the basic format you're already used to:

[identifier] [resource type name] [filename]

The resource type name is a string that defines your custom resource, and can be whatever you want. For the purposes of this example, let's say you want to include a data file called p1config.dat that contains information necessary to initialize a character in a game program. We'll call the custom resource type CHARCONFIG. With that in mind, here's an example of what the script file entry might look like for your data file:

DATA_PLAYERINIT CHARCONFIG p1config.dat

Pretty simple, hey? Now that you've included your file, there are three steps you must take in order to retrieve a pointer to the resource data. Each involves calling a function we haven't talked about yet, so let's go through them one at a time. The first thing you must do is to find the resource with a call to FindResource(). Here's the prototype:

HRSRC FindResource(

HMODULE hModule, // module handle

LPCTSTR lpName, // pointer to resource name

LPCTSTR lpType // pointer to resource type

);

The return value is a handle to the resource's information block, or NULL if the function fails. The parameters are as follows:

HMODULE hModule: The HMODULE data type is simply an HINSTANCE. Don't ask me why they felt they needed another name for it, but you should simply pass the instance of your application. You don't even need a typecast because the data types are exactly the same.

LPCTSTR lpName: This is the resource identifier. Remember to use MAKEINTRESOURCE() on this one if you're using numeric constants to define your resources.

LPCTSTR lpType: This is the resource type, so pass the string you used to define your resource type. In our case, this is CHARCONFIG.

A sample function call looks like this:

HRSRC hRsrc = FindResource(hinstance, MAKEINTRESOURCE(DATA_PLAYERINIT), "CHARCONFIG");

This is a handle to the info block the resource resides in. The next step to getting a pointer to the data is to take this handle and pass it to LoadResource() to actually load the data. This yields a handle to the resource itself. Here is the function prototype:

HGLOBAL LoadResource(

HMODULE hModule, // resource-module handle

HRSRC hResInfo // resource handle

);

The return type, HGLOBAL, is a pretty general handle type, as opposed to the other load functions we've covered, which returned specific handle types like HBITMAP or HICON. If the function fails, this value will be NULL. The parameters are straightforward:

HMODULE hModule: Again, simply the application instance.

HRSRC hResInfo: Pass the handle that was returned by FindResource().

Now that you have a handle to the resource, you can finally get a pointer to the data that was in the resource file you included. This is achieved with a call to LockResource(), shown here:

LPVOID LockResource(HGLOBAL hResData);

Simply pass the handle that was returned by LoadResource(). If the return value is NULL, the function call failed. If not, you've got your pointer! Now you're free to do whatever you like with the data. Note that the return type is LPVOID (Windows-speak for void*), so if you want to use array notation on the pointer, you need to cast it to something like a BYTE*. Now that we've gone through all the steps, I'll show you an example of a function you might write to return a pointer to a specified resource:

UCHAR* LoadCustomResource(int resID)

{

HRSRC hResInfo;

HGLOBAL hResource;

// first find the resource info block

if ((hResInfo = FindResource(hinstance, MAKEINTRESOURCE(resID), "CUSTOMRESOURCETYPE")) == NULL)

return(NULL);

// now get a handle to the resource

if ((hResource = LoadResource(hinstance, hResInfo)) == NULL)

return(NULL);

// finally get and return a pointer to the resource

return ((UCHAR*)LockResource(hResource));

}

Closing

Well, that about does it for resources! See, programming for Windows is fun. :) Even with all this knowledge of resources, you're still pretty limited in what you can actually get your programs to do, so next time I'll be going over some basic Windows GDI (Graphics Device Interface) functions, so you can start using all this stuff to put some demo programs together. As always, send me your comments, your ideas, your death threats:

E-mail: [email protected]: UIN #53210499

Farewell everyone, until we meet again...

Game Programming GenesisPart III : Tracking Your Window and Using GDI by Joseph "Ironblayde" Farrell

Introduction

If you've been with me for the last two articles, you've probably been asking yourself when I'm going to show you something useful. Well, the wait is over! Today I'll be showing you the basics of Windows GDI (Graphical Device Interface), and a few other things along the way, like responding to user input and dealing with some more of the messages that Windows generates. As far as actually displaying graphics, I'm going to go over three basic topics: showing text, plotting pixels, and displaying bitmaps. Before getting into too much of that though, I'm going to cover several more Windows messages in detail so you will be sure to know what's going on when the user starts messing with things. They always do. :)

As always, you need only a basic knowledge of the C language, and the information that was covered in previous articles of this series. Since this article will enable you to make some working graphical demos, there is a sample program available along with the article. The code used for this program was written and compiled in Visual C++, but it is simple enough that you shouldn't have to change it to get it working with other compilers. All right, enough with the disclaimers, and on to the fun stuff!

Device Contexts

In the first article in this series, we defined and registered a window class. One of the lines in that definition, giving the window's capabilities, was this:

sampleClass.style = CS_DBLCLKS | CS_OWNDC |

CS_HREDRAW | CS_VREDRAW; // standard settings

Three of those attributes are fairly self-explanatory, but the other -- CS_OWNDC -- requires some explanation. If you recall, I told you that this attribute allowed for the window to have its own unique device context, and that device contexts would not be covered just yet. Well, grasshopper, the time has come.

A device context is a structure that represents a group of graphic objects and their attributes, as well as some output device and its attributes and settings. Using device contexts allows you to manipulate graphics in a very straightforward manner, without having to worry about a lot of low-level details. Windows GDI is a graphics-rendering system which takes Windows graphics calls and passes the information to the appropriate device driver. To make use of GDI graphics, you must use device contexts. Thankfully, it's very easy to do. You can get a device context for a window using a simple function call:

HDC GetDC( HWND hWnd // handle to a window );

That looks pretty harmless, doesn't it? All you do is pass a handle to the window for which you want a device context (or DC), and the return value is a handle to that device context. If you pass NULL, the handle returned is for a DC to the entire screen. If the function call fails, the return value is NULL.

Now is a good place to mention that device contexts are a little more general than dealing with graphics calls only. The type of DC we'll be talking about is called a display device context, because it deals with displaying graphics. In addition, there are printer device contexts, which use a printer as the output device; memory device contexts, which allow for manipulation of bitmap data; and information device contexts, for retrieving data for a specified device. Don't worry if this all sounds complicated. It's Windows -- its primary function is to confuse people. :) Once we get into some code, I think you'll find that it's actually not that difficult.

When you're finished with a device context, you have to release it. This frees up any memory that was being used by the object -- you'll come across the concept of releasing objects a lot more in the future. Once again, this is done by using a simple function call:

int ReleaseDC(

HWND hWnd, // handle to window

HDC hDC // handle to device context );

The return value is 1 if the DC was successfully released, or 0 if something went wrong. The parameters are self-explanatory, but I'll list them here anyway.

HWND hWnd: This is the handle to the window which is referred to by the DC you're trying to release. If you have a DC for the whole desktop, pass NULL.

HDC hDC: The handle to the device context you want to release.

Before we get into doing some graphics displays with device contexts and GDI, I want to talk about some of the important messages you'll encounter when creating a windowed application. The four messages I want to cover briefly are WM_MOVE, WM_SIZE, WM_ACTIVATE, and WM_PAINT.

Tracking the Status of Your Window

The first two are relatively simple. WM_MOVE is called whenever the window is moved by the user. The new window coordinates are stored in lparam. (Remember, messages are further specified by the contents of lparam and wparam, which are parameters received by your message-handling function.) The low word of lparam is the x-coordinate of the upper-left corner of the window's client area. The high word of lparam is the y-coordinate.

The WM_SIZE message is sent when the window is resized. Like the WM_MOVE message, its parameterization is held in lparam. The low word is the client area's width, and the high word is its height. But unlike WM_MOVE, the wparam parameter also holds some significant. It can take any of the following values:

SIZE_MAXHIDESome other window has been maximized.

SIZE_MAXIMIZEDWindow has been maximized.

SIZE_MAXSHOWSome other window has been restored.

SIZE_MINIMIZEDWindow has been minimized.

SIZE_RESTOREDWindow has been resized, but neither maximized nor minimized.

When I'm writing windowed applications, I usually like to keep a few global variables that give the window's current position and size. If these variables were called xPos, yPos, xSize, and ySize, you'd handle the WM_SIZE and WM_MOVE messages something like this:

if (msg == WM_SIZE)

{

xSize = LOWORD(lparam);

ySize = HIWORD(lparam);

}

if (msg == WM_MOVE)

{

xPos = LOWORD(lparam);

yPos = HIWORD(lparam);

}

Next up is the WM_ACTIVATE message, which tells you when a new window becomes the active window. This can be useful because you may not want to be processing all of your program's logic if some other application has the focus. Sometimes, such as in writing fullscreen DirectX programs, ignoring the WM_ACTIVATE message can cause your program to experience a fatal error by doing something it's not supposed to be doing. In any case, it's good to watch the WM_ACTIVATE messages and take action accordingly.

The WM_ACTIVATE message is sent to both the window being activated, and the window being deactivated. You can determine which is the case by looking at the low word of wparam. It will be set to one of three possible values:

WA_CLICKACTIVEWindow was activated by a mouse click.

WA_ACTIVEWindow was activated by some other means (keyboard, function call, etc.)

WA_INACTIVEWindow was deactivated.

For dealing with this message, I'll keep another global variable called bFocus, and change its value when a WM_ACTIVATE message is received. The code would look something like this:

if (msg == WM_ACTIVATE)

{

if (LOWORD(wparam) == WA_INACTIVE)

focus = FALSE;

else

focus = TRUE;

// tell Windows we handled it

return(0);

}

There are two related messages called WM_KILLFOCUS and WM_SETFOCUS, which a window receives immediately before it loses or gains the keyboard focus, respectively. Since it's possible for no window to have the keyboard focus, I suggest using the WM_ACTIVATE message to track your window's status. Now, on to the biggie.

The WM_PAINT Message

A window receives this important message when part of its client area has become invalidated. Suppose your program doesn't have the focus, and the active window is on top of your window. If the user moves that active window, it's going to reveal a part of your window. Since that part of the window needs to be refreshed, it is said to be invalidated. To handle this, there are a couple of things you can do. The first involves a pair of functions designed exclusively for use with the WM_PAINT message. The first is BeginPaint(). Here's the prototype:

HDC BeginPaint(

HWND hwnd, // handle to window

LPPAINTSTRUCT lpPaint // pointer to structure for paint information );

Before I tell you exactly what the return value is, let's look at the parameters:

HWND hwnd: This is a handle to the window which needs repainting. You should be used to seeing this parameter by now, right?

LPPAINTSTRUCT lpPaint: Here's the important one. This is a pointer to a PAINTSTRUCT structure, which contains all sorts of information about the area to be painted.

And before we go on, I should show you exactly what a PAINTSTRUCT looks like...

typedef struct tagPAINTSTRUCT { // ps

HDC hdc;

BOOL fErase;

RECT rcPaint;

BOOL fRestore;

BOOL fIncUpdate;

BYTE rgbReserved[32];

} PAINTSTRUCT;

And the members of the structure are as follows:

HDC hdc: Aha! I knew there was some reason we went over device contexts, even if it took awile to get here. This is a DC that represents the invalidated area -- the area that needs to be painted.

BOOL fErase: This specifies whether or not the application should erase the background. If set to FALSE, the system has already deleted the background. Remember in our window class when we defined a black brush as the background? This will cause the system to automatically erase the invalidated area with that black brush.

RECT rcPaint: This is the most important member, as it tells you the rectangle that needs to be repainted in order to cover the whole invalidated area. I'll show you the RECT structure in just a bit.

BOOL fRestore, BOOL fIncUpdate, BYTE rgbReserved[32]: Good news! These are reserved and are used by Windows, so you and I don't have to worry about them. :)

Now that I've showed this to you, I can tell you just what BeginPaint() is accomplishing. It actually does three things. First, it validates the window again, so that another WM_PAINT message will not be generated unless the window becomes invalidated again. Second, if your window class has a background brush defined, like ours does, it paints the affected area with that brush. Third, it returns a handle to a device context which represents the area needing to be painted. That area, as we saw, is defined by the important RECT structure:

typedef struct _RECT {

LONG left;

LONG top;

LONG right;

LONG bottom;

} RECT;

You've already figured out that this structure represents a rectangle, but there is one thing that needs to be said about it. RECTs are upper-left inclusive, but lower-right exclusive. What does that mean? Well, let's say you define a RECT like this:

RECT myRect = {0, 0, 5, 5};

This RECT includes the pixel at (0, 0), but it stops short of (5, 5), so that the lower-right corner of the area described by this rectangle is actually at (4, 4). It doesn't seem to make much sense at first, but you'll get used to the idea.

Now, remember what I said about using device contexts? Once you're done using one, you have to release it. In this case, you use the EndPaint() function. Each call to BeginPaint(), which should only be made in response to a WM_PAINT message, must have a matching EndPaint() function to release the DC. Here's the function:

BOOL EndPaint(

HWND hWnd, // handle to window

CONST PAINTSTRUCT *lpPaint // pointer to structure for paint data );

The function returns TRUE or FALSE indicating its success or failure, respectively, and takes two simple parameters:

HWND hWnd: Just the handle to the window. Again.

CONST PAINTSTRUCT *lpPaint: A pointer to the PAINTSTRUCT containing the information about the area in question. Don't let the CONST confuse you. It's just there to denote and ensure that the function does not alter the contents of the structure.

For the record, the other way you can validate a window is with a call to ValidateRect(). If you want to do everything manually instead of letting BeginPaint() handle it, that's fine. There may be some cases where this is necessary. So here's the prototype:

BOOL ValidateRect(

HWND hWnd, // handle of window

CONST RECT *lpRect // address of validation rectangle coordinates );

The return value is TRUE or FALSE for success or failure, and the parameters are easy to figure out:

HWND hWnd: Are you getting tired of seeing this yet? :)

CONST RECT *lpRect: This is a pointer to the RECT to validate. Again, you don't need to declare it as a constant; the CONST is just to make sure the function doesn't go changing things on you. If you pass NULL, the entire client area is validated.

Now, to wrap up our discussion of this message, I'll show you the framework for handling a WM_PAINT message. This would be somewhere in your message handler, as usual. I'm assuming here that we have a global variable called hMainWindow that is the handle to our window.

if (msg == WM_PAINT) {

PAINTSTRUCT ps; // declare a PAINTSTRUCT for use with this message

HDC hdc; // display device context for graphics calls

hdc = BeginPaint(hMainWindow, &ps); // validate the window

// your painting goes here!

EndPaint(hMainWindow, &ps); // release the DC

// tell Windows we took care of it

return(0);

}

The only part of that code that probably doesn't make sense is the place where I have commented, "Your painting goes here!" Well, if you want your window to be refreshed with something other than your window class's default brush, you have to do it yourself, and that involves some graphics work that you haven't seen yet. Never fear, we'll get there in just a minute! While we're on the topic of messages, though, there's something I need to explain.

Closing Your Application

There are three messages that seem to be pratically identical, and all deal with closing things out. They are WM_DESTROY, WM_CLOSE, and WM_QUIT. They're similar, but you need to know the difference! WM_CLOSE is sent when a window or application should be closing. When you receive a WM_CLOSE message, it's a good place to ask the user if they're sure they want to quit, if you want to do it. You know those little message boxes that are always popping up on your screen when errors or notifications occur? Well, they're easy to create. In addition to serving many functions in the final program, they're also handy for reporting debug information. The call to create your very own message box is pretty simple:

int MessageBox(

HWND hWnd, // handle of owner window

LPCTSTR lpText, // address of text in message box

LPCTSTR lpCaption, // address of title of message box

UINT uType // style of message box );

The parameters, especially the last one, require some explanation:

HWND hWnd: Sooner or later we'll get to a function that doesn't have this, I promise!

LPCTSTR lpText: This is the text that will appear in the message box. As always, you can use escape sequences like \n to format the output a little if you want.

LPCTSTR lpCaption: This is the text appearing in the message box's caption bar.

UINT uType: You can combine several different flags in order to create this parameter, which defines what kind of message box it will be. There are a lot of MB_ constants you can use, and you can combine any number of them with the | operator. Here's a list of the useful ones:

Button Definition Flags

MB_ABORTRETRYIGNORECreates a box with "Abort," "Retry," and "Ignore" buttons.

MB_OKCreates a box with an "OK" button.

MB_OKCANCELCreates a box with "OK" and "Cancel" buttons.

MB_RETRYCANCELCreates a box with "Retry" and "Cancel" buttons.

MB_YESNOCreates a box with "Yes" and "No" buttons.

MB_YESNOCANCELCreates a box with "Yes," "No," and "Cancel" buttons.

Icon Definition Flags

MB_ICONEXCLAMATIONAdds an exclamation point icon to the box.

MB_ICONINFORMATIONAdds an information icon to the box.

MB_ICONQUESTIIONAdds a question mark icon to the box.

MB_ICONSTOPAdds a stop sign icon to the box.

Default Button Flags

MB_DEFBUTTON1Defines the first button as the default.

MB_DEFBUTTON2Defines the second button as the default.

MB_DEFBUTTON3Defines the third button as the default.

MB_DEFBUTTON4Defines the fourth button as the default.

Other Flags

MB_HELPAdds a help button to the box. A WM_HELP message is generated if the user chooses it or presses F1.

MB_RIGHTMessage box text is right-justified.

MB_TOPMOSTSets the message box to always be the topmost window.

I don't know about you, but I'm starting to think that Microsoft has a programmer who does nothing but write #define statements all day! Now, the return value is 0 if the box could not be created. Otherwise, the result is one of the following:

IDABORT"Abort" button was selected.

IDCANCEL"Cancel" button was selected.

IDIGNORE"Ignore" button was selected.

IDNO"No" button was selected.

IDOK"OK" button was selected.

IDRETRY"Retry" button was selected.

IDYES"Yes" button was selected.

Those lists were so long I almost forgot what we were originally talking about. Anyway, when you receive a WM_CLOSE message, you can do two things. First, you can allow the default handler to return a value. If you do this, the application or window will close as planned. However, if you return 0, the message will have no effect. This is the basis of the following bit of code:

if (msg == WM_CLOSE) {

if (MessageBox(hMainWindow,

"Are you sure want to quit?",

"Notice",

MB_YESNO | MB_ICONEXCLAMATION) == IDNO)

return(0);

// otherwise, let the default handler take care of it

}

Now, WM_DESTROY is a bit different. It is sent when a window is being closed. By the time you get a WM_DESTROY message, the window it applies to has already been deleted from view. If the main window closes, that does not necessarilly end the application. It will keep running, but without a window. However, when a user closes the main window, they almost always mean to close the application, so you have to post a WM_QUIT message when you receive WM_DESTROY if you want the application to end. You could use PostMessage(), but since this is a special case, there's a special function for it:

VOID PostQuitMessage(int nExitCode);

The parameter is an exit code that your application returns to Windows. Remember, WinMain() returns an int, not a void. The nExitCode parameter also becomes the wparam member of the WM_QUIT message that results. WM_QUIT represents a request to close the application, so when you get one, you should end your main loop and return wparam to Windows. Here's an example of what a simplified WinMain() function might look like with this in place:

int WinMain(HINSTANCE hinstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

// initialization stuff goes here

// main loop - infinite!

while (TRUE)

{

// check the message queue

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

{

if (msg.message == WM_QUIT) // exit main loop on WM_QUIT

break;

TranslateMessage(&msg);

DispatchMessage(&msg);

}

// main program logic goes here

}

// perform any shutdown functions here - releasing objects and such

return(msg.wparam); // return exit code to Windows

}

Sorry about all that stuff, but it's necessary to make sure your program behaves correctly instead of causing errors for Windows -- like that ever happens! Now instead of making you any more impatient with me than you probably already are, let's look at some basic GDI graphics.

Plotting Pixels

At last! Plotting pixels with GDI is a cinch as long as you've got a display device context to work with. Remember, calling GetDC() does this for you. To plot a pixel, not surprisingly, you call SetPixel():

COLORREF SetPixel(

HDC hdc, // handle to device context

int X, // x-coordinate of pixel

int Y, // y-coordinate of pixel

COLORREF crColor // pixel color );

The return type is something we haven't encountered yet, a COLORREF. This is not a structure, but a 32-bit value in the form 0x00bbggrr, where bb is an 8-bit value for the blue component, gg is green, and rr is red. The high byte is unused and is always set to zero. Let's take a look at the parameters for SetPixel():

HDC hdc: This is a device context for your window that you should obtain with a call to GetDC(). You only need to call GetDC() once, and then you can use it for any number of these functions. Don't get a new DC every time you want to plot a pixel!

int X, Y: The x- and y-coordinates of the pixel. These are in client coordinates, meaning that (0, 0) represents the upper-left corner of your window's client area, not the upper-left corner of the screen.

COLORREF crColor: This is the color you want to set the pixel to. To do this, it's easiest to use the RGB() macro, which takes values for red, green, and blue -- in that order -- between 0 and 255. SetPixel() will choose the closest available color to the one you have specified.

If the function succeeds, the return value is the color that the pixel was set to. This may not always be exactly the COLORREF you pass if you're working in less than 24-bit color; Windows will choose the closest match. If the function fails, it returns -1. As an example, if you want to set the upper-left corner of your client area to white, you'd use the following call:

SetPixel(hdc, 0, 0, RGB(255, 255, 255));

This call assumes you've gotten a display device context named hdc. Pretty easy, hey? There's one other way to do it that's just a tad faster. Here's the function:

BOOL SetPixelV(

HDC hdc, // handle to device context

int X, // x-coordinate of pixel

int Y, // y-coordinate of pixel

COLORREF crColor // new pixel color );

The parameters are all the same. The return value is simply TRUE or FALSE for success or failure. SetPixelV() is slightly faster since it doesn't need to return the actual color that was used to plot. You'll probably never even notice the difference, unless you're using it thousands of times per frame, but if you don't need the extra information SetPixel() provides, there's no reason not to take the slightly increased performance, right?

The only other thing you need to know about plotting pixels is how to read the value of a pixel that's already been plotted. It's no problem; a quick call to GetPixel() does the job for you:

COLORREF GetPixel(

HDC hdc, // handle to device context

int XPos, // x-coordinate of pixel

int nYPos // y-coordinate of pixel );

The return value is obviously the color of the pixel at the given coordinates. If the coordinates specified are outside the clipping region (the area represented by the device context), the return value is CLR_INVALID. The parameters are the same as for SetPixel(): a device context to use, and the coordinates to operate on. That's it for plotting pixels. Now let's have a look at GDI's text-rendering functions.

GDI Text Functions

There are two functions for actually plotting text that you need to be concerned with. The simpler of the two is TextOut(), as shown here:

BOOL TextOut(

HDC hdc, // handle to device context

int nXStart, // x-coordinate of starting position

int nYStart, // y-coordinate of starting position

LPCTSTR lpString, // pointer to string

int cbString // number of characters in string );

By now we've seen enough BOOL-returning functions to know what that means: TRUE for success, FALSE for failure. The parameters are:

HDC hdc: The device context to use.

int nXStart, nYStart: These are the coordinates of the starting point for the text, called the reference point. By default, this is the upper-left corner of the rectangular area occupied by the string. You can change this setting, as we'll see in just a bit.

LPCTSTR lpString: The text to print out. Since the number of characters is given in the final parameter, this string does not need to be null-terminated.

int cbString: This is the length of the string, in characters.

TextOut() uses the current settings for text color, background color, and background type. Before looking at the other, more complicated text-rendering function, let's take a look at the functions you can use to control the colors being used.

COLORREF SetTextColor(

HDC hdc, // handle to device context

COLORREF crColor // text color );

COLORREF SetBkColor(

HDC hdc, // handle of device context

COLORREF crColor // background color value );

SetTextColor() sets the active text color, and SetBkColor() sets the active background color. The parameters are obviously the device context to apply the settings to, and the colors to use. Since these are COLORREFs, remember that you can use the RGB() macro for specifying your colors. Each function returns the previous value of the attribute it deals with. For instance, if you call SetTextColor(hdc, RGB(255, 0, 0)), the return value will be the active color that was being used before you turned it red. Finally, to set the background type, use SetBkType() as shown:

int SetBkMode(

HDC hdc, // handle of device context

int iBkMode // flag specifying background mode );

The device context parameter we've seen before, but the other, iBkMode, can take one of two values: TRANSPARENT or OPAQUE. If set to TRANSPARENT, any text you plot will not disturb the background around the text itself. If set to OPAQUE, plotting text will cause the rectangular region surrounding that text to be filled with the active background color. The return value of SetBkMode() is simply the previous background mode.

One more thing about TextOut(). I said you could change the way the reference point is interpreted, and the way to do it is by using SetTextAlign(), whose prototype is shown below.

UINT SetTextAlign(

HDC hdc, // handle to device context

UINT fMode // text-alignment flag );

The parameters are:

HDC hdc: The device context again. No surprises here.

UINT fMode: A flag or set of flags (logically combined with |) that determine the meaning of the reference point in a call to TextOut(). Only one flag can be selected from those affecting horizontal and vertical alignment, and only one of the two flags affecting use of the current position can be used. The flags are:

TA_BASELINEThe reference point will be on the baseline of the text.

TA_BOTTOMThe reference point will be on the bottom edge of the bounding rectangle.

TA_TOPThe reference point will be on the top edge of the bounding rectangle.

TA_CENTERThe reference point will be aligned horizontally with the center of the bounding rectangle.

TA_LEFTThe reference point will be on the left edge of the bounding rectangle.

TA_RIGHTThe reference point will be on the right edge of the bounding rectangle.

TA_NOUPDATECPThe current position is not updated by a call to a text output function. The reference point is passed with each call.

TA_UPDATECPThe current position is updated by each call to a text output function, and is used as the reference point.

The default setting is TA_LEFT | TA_TOP | TA_NOUPDATECP. If you set TA_UPDATECP, subsequent calls to TextOut() will ignore the nXStart and nYStart parameters, and render the text where the last call left off. Now that that's out of the way, let's look at the bells-and-whistles version of TextOut(), called DrawText():

int DrawText(

HDC hDC, // handle to device context

LPCTSTR lpString, // pointer to string to draw

int nCount, // string length, in characters

LPRECT lpRect, // pointer to struct with formatting dimensions

UINT uFormat // text-drawing flags );

This one gets a bit complicated. Since DrawText() formats text, possibly to multiple lines, the return value is the height of the text in pixels, or 0 if the function fails. Let's take a look at the parameters, shall we?

HDC hDC: Nothing new here; it's just our good buddy the DC.

LPCTSTR lpString: This is the string to print.

int nCount: This is the length of the string in characters.

LPRECT lpRect: Here's where things start to get a bit different. DrawText() does several different methods of formatting, including word wrapping, so you must specify a RECT within which to format the text, rather than simply passing coordinates.

UINT uFormat: For this, you can use one or more (logically combined with |) of a long list of flags that represent different methods of formatting. I'll show you a few of them.

DT_BOTTOMJustifies text to the bottom of the RECT. This must be combined with DT_SINGLELINE.

DT_CALCRECTCalculates the RECT needed to hold the text. If the text is on multiple lines, DrawText() uses your RECT's width and alters the height. If the text is on a single line, DrawText() alters your RECT's width. In both cases, DrawText() adjusts the RECT but does not actually draw the text.

DT_CENTERCenters text within the RECT you specify.

DT_EXPANDTABSIf the string contains any tabs (\t), this attribute causes DrawText() to expand them. The default is eight spaces per tab.

DT_LEFTLeft-justifies the text.

DT_NOCLIPDraws without clipping. This speeds up DrawText() a bit.

DT_RIGHTRight-justifies the text.

DT_SINGLELINEDisplays text on a single line only. Carriage returns and line feeds do not overrule this a