1 2 3 4 5 6 7
Setting up your Compiler Our Generic Windows Skeleton A Simple Cube Matrices Wazoo of Borg FullScreen Ahead Mr. Crusher On screen. Magnify.

 

 

 

Tutorial #2: Our Generic Windows Skeleton
Erik Yuzwa

Version History

Date Version Description Font Color
July 16th, 2001 1.0 General Public Release White
Sept. 10th, 2001 1.5 Updated skeleton to allow for more flexibility with either OpenGL or Direct3D Red


Okay here we're going to talk a little bit about Windows code with respect to gaming. To help the programmer, you should learn as much as you can about Windows. I'm not going to go into too many details here, but just enough to get us started doing Game Programming, as well as to feel a tad more comfortable around Windows code.

First of all, it's CRUCIAL to realize that Windows is event driven. This is a design completely different from DOS which is procedurally driven. This means for us, we only need to write code to Respond to events. For example, when we display a GUI to the user with buttons and text input fields, our program simply sits there idle UNTIL the user does something like click a button or input text. THEN this message is sent to our code which we must handle.

Now at the same time that our program is running, there are potentially others running on the same system as well. In the (good) old DOS days, we could take over the entire system COMPLETELY doing whatever we wish to it! Nowadays, we have to let Windows know what's going on, and be polite about taking over sections of the OS, which is kind of like needing a note from your mother.

This means that we CANNOT have an infinite While loop controlling our code directly, as it would cripple the proper...cough..functioning of Windows. What we have to do is to go through our game loop ONCE, then return control to Windows, waiting for the next opportunity to do it again...

I can already hear the gasps..."But Wazoo", you may ask, "Doesn't this have a risk of my game not being properly updated?" ..the answer is "it all depends."..we'll discuss more of these issues as they arise, but for now I just wanted to go over a general Windows skeleton.

Having a skeleton enables us to reuse the same code in every Windows game or project we create! Believe me, this saves TONS of time! Why retype the same Windows code when you can just include a generic skeleton???
Note: This skeleton was developed by researching the MSDN as well as through reading material such as Andre Lamothe's "Trick of the Windows Game Programming Gurus" which is HIGHLY recommended.

Let's write some code!
#define STRICT                 // enable strict type-checking
#define WIN32_MEAN_AND_LEAN    // only include the bare minimum windows header files

#include <windows.h>           // standard windows header file

TCHAR *g_szAppClass = NULL;   // the application classname of our window
TCHAR g_szAppTitle[MAX_PATH]; // the application title of our window
BOOL g_bIsActive = FALSE;     //our global boolean specifying if our app is minimized or not
BOOL g_bDisplayReady = FALSE; //our global boolean specifying if our display is ready or not

DWORD g_dwAppWidth = 0;       //our global variable containing the width of our app window
DWORD g_dwAppHeight = 0;      //our global variable containing the height of our app window
DWORD g_dwAppDepth = 0;       //our global variable containing the depth of the app window



// some function declarations
HRESULT  Game_Init(HWND, HINSTANCE);
HRESULT  Game_Shutdown(void *parms=NULL);
HRESULT  Game_Run(void *parms=NULL);
void loadConfigSettings(TCHAR*);



///////////////////////////////////////////////////////////////////////////////
// WinProc - This is the windows main callback procedure
//           that handles our messages...of which almost all will be passed on
///////////////////////////////////////////////////////////////////////////////
static LRESULT CALLBACK WindowProc(HWND hwnd,      //the window which generated the message
				   UINT msg,       //the id of our message
                            	   WPARAM wparam,
                            	   LPARAM lparam)
{

// this is the main message handler of the system
PAINTSTRUCT	ps;	// used in WM_PAINT
HDC		hdc;	// handle to a device context

// what is the message?
switch(msg)
	{
	case WM_ACTIVATE: //our window is activated
            g_bIsActive = (BOOL)wparam;

            if( g_bIsActive )
            {
                
                // if we are active, then our display is ready to be drawn
                // upon!
                g_bDisplayReady = TRUE;

            }

            break;

	case WM_SETCURSOR:
           SetCursor( LoadCursor( NULL, IDC_ARROW ) );
          return TRUE;

    	case WM_PAINT: {
        
        	// This is called automatically when our application view is refreshed
        	// we do NOT want to do any screen updating here, but we need to perform
        	// "dummy" code to appease Windows
        	// start painting
        	hdc = BeginPaint(hwnd,&ps);

         	EndPaint(hwnd,&ps); // end painting
         	return(0);          // let the system know we handled it
        } break;


	case WM_DESTROY: {
		// WM_DESTROY is sent when the program receives the signal from the
		// user that he wishes to quit the program. NOTE: You DO NOT call this
		// function directly, BUT send a WM_CLOSE message to our application
		// window handle

		// kill the application
		PostQuitMessage(0);
		return(0);
		} break;

	default:break;

    } // end switch

// let the OS process any messages that we didn't take care of
return (DefWindowProc(hwnd, msg, wparam, lparam));

} // end WinProc
/////////////////////////////////////////////////////////

Okay THAT was a lot of code! hehehehe. Don't worry, nothing to be worried about. All we are doing here is writing our Windows Callback function. A Callback function is the function we want to handle our application's messages. Basically it's the heart of the Event driven model, handling our messages that we generate with our application. EVERY Windows application MUST have a Callback function. The beauty of it is that we only need to handle the messages we want to. We don't need to take care of every single type of message, otherwise this Callback function would be MASSIVE!

The only important thing to realize right now, is that we need to handle the WM_ACTIVATE, WM_PAINT and WM_DESTROY messages. The WM_ACTIVATE message is sent out automatically by Windows when our application's main window changes display state (ie. you either minimize or maximize the window). The WM_PAINT message is sent every time we wish to refresh our main view, or have Windows do it for us. "But Wazoo" you may ask, "wouldn't this be an ideal place for our rendering code?"...in short, NO. I don't want to go into too many details right now, but this is a bad idea, as the WM_PAINT message may not be reliably sent consistently. A situation may occur where the system needs to do something else, leaving our application out in the cold...ie. leaving you open and vulnerable for a good fragging by a buddy while you are waiting for the screen to refresh..hehehe

Okay I think enough has been said about that...let's move on
// WINMAIN ////////////////////////////////////////////////

int WINAPI WinMain(HINSTANCE hInstance,    //the current instance of our app
		   HINSTANCE hPrevInstance,//the previous instance of our app
		   LPSTR lpcmdline,        //command line parameters
		   int ncmdshow)           //initial display parameter
{

WNDCLASSEX winclass;// this will hold the class we create
HWND	 hWnd;	   // temporary window handle
MSG	 msg;      // message to handle system events


// first fill in the window class stucture to properly register
// our application with Windows

winclass.cbSize		= sizeof(WNDCLASSEX);
winclass.style	       = CS_DBLCLKS | CS_OWNDC |
                         CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc   = WindowProc; // pointer to our Callback function defined above
winclass.cbClsExtra    = 0;
winclass.cbWndExtra    = 0;
winclass.hInstance     = hInstance;
winclass.hIcon	       = LoadIcon(NULL, IDI_APPLICATION); //load a generic Window icon
winclass.hCursor       = LoadCursor(NULL, IDC_ARROW);     //load a generic mouse pointer
winclass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH); // to clear the screen black
winclass.lpszMenuName	= NULL;  // no menu 
winclass.lpszClassName	= g_szAppClass; // give it a class name
winclass.hIconSm        = LoadIcon(NULL, IDI_WINLOGO);


// register the window class
// if it failed, end the app

if (!RegisterClassEx(&winclass))
	return(0);

//load up our configuration settings
loadConfigSettings("WazooGame.ini");

DWORD dwStyle;
DWORD dwExStyle;
RECT  windowRect;

//fill our RECT structure with our application window sizes
windowRect.left   = (long)0;
windowRect.right  = (long)g_dwWidth;
windowRect.top    = (long)0;
windowRect.bottom = (long)g_dwHeight;

dwExStyle = WS_EX_APPWINDOW |
            WS_EX_WINDOWEDGE;

dwStyle = WS_OVERLAPPEDWINDOW;

AdjustWindowRectEx(&windowRect,
		  dwStyle,
		  FALSE,
		  dwExStyle);


// create the window, note the use of WS_POPUP
// if we failed, then again, bail out!
if (!(hWnd = CreateWindowEx(NULL, //extended window style
			  g_szAppClass,                        //application class
			  g_szAppTitle,	                       //application title
			  dwStyle |
			  WS_CLIPCHILDREN |
			  WS_CLIPSIBLINGS,                     //window style
			  0,0,	                               //upper x,y coordinates
			  windowRect.right - windowRect.left,  //width of the window
              windowRect.bottom - windowRect.top, // height of the window
			  NULL,	                              // handle to parent
			  NULL,	                              // handle to menu
			  hInstance,                          // instance of our application
			  NULL));                             // window creation params
return(0);

// we must tell Windows to show our window at least once right now
// otherwise we get weird flicker issues that just looks too wrong

ShowWindow(hWnd, ncmdshow);
UpdateWindow(hWnd);

// perform all game console specific initialization

if(FAILED(Game_Init(hWnd, hInstance))){
	MessageBox(NULL, "Failed to Initialize Project", "Init Error", MB_OK);
	Game_Shutdown(); // make sure we cleanup any allocated memory
	return FALSE;
}


// enter main event loop
// continue doing this loop until the user wishes to quit the game
// Now we're ready to recieve and process Windows messages.

BOOL bGotMsg = FALSE;
PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE );
while(WM_QUIT != msg.message ){
	
	// Use PeekMessage() if the app is active, so we can use idle time to
        // render the scene. Else, use GetMessage() to avoid eating CPU time.
        
        if( g_bIsActive )
            bGotMsg = PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE );
        else
            bGotMsg = GetMessage( &msg, NULL, 0U, 0U );

	if( bGotMsg )
        {
            // Translate and dispatch the message
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        } else {

		// main game processing goes here
		if(g_bDisplayReady){
			Game_Run();
		}

	}

} // end while


// shutdown game and release all resources
Game_Shutdown();

UnregisterClass(g_szAppClass, hInstance);

// return to Windows like this
return(msg.wParam);

} // end WinMain
////////////////////////////////////////////////////////
PHEW!! Okay we just did a lot here, and quite a few of you are probably reeling from the sheer massness of this code...not to worry my friends, we are okay. We'll walk through this step by step, and I'll explain just what the heck is going on..YAY!

First of all, we have to Register our application with the operating system. In essence we're letting Windows know that we are about to create an application and we need to tell Windows how to initially display it, and where to send our application's messages.

Next, we create our window. Nothing too puzzling here, we just tell Windows to create a new Window, passing our RECT structure to let Windows know how big our window is.

In our next section, we simply need to let Windows show the window and update it just once. I've done a lot of reading that this needs to be done right about now, otherwise we get those weird screen flickers and ugly stuff happening before we jump into our game...

Now we save our window handle and instance handle into some global variables
Note: You may have noticed in our WinMain function definition an HInstance and an hPrevInstance. This is left over from the old Windows3.11 days when all processes ran in the same context...nowadays Windows runs each process in a different context, allowing for..cough...easier crash recovery should our code crater the system...the long and short of this is that you need to include hPrevInstance in our WinMain function definition, but you'll never use it

Now we want to call our generic Game_Init function, which will handle ALL of our game initialization routines..ie. starting up our graphics, sound and input devices..

This next part is also an important necessity for a Windows application. We simply put our program into an infinite loop until we signal the app that we wish to quit. Now THIS infinite loop is different than my warning above, as we are NOT taking control of the system completely. Each time through the loop we're
  1. Grabbing the first message from the top of the message queue
  2. If it's a WM_QUIT message, then signal the app we wish to quit
  3. Translate any Accelerator commands ie. CTRL+S (which is normally "File->Save")
  4. Send the message to our Callback routine which we defined earlier
  5. Go into our Game_Frame function which is our main loop of our game
  6. Keep looping

Once we've received the WM_QUIT message from the user, we call the Game_Shutdown function which handles the destruction of our game and responsible for cleaning up any used memory Note: Cleaning up used memory is CRITICAL and very polite. You will get VERY bad PR for yourself and your game if you use up massive blocks of memory without releasing them later on.. this can cause MANY memory leaks in Windows, which is not good. But then again, are leaks ever associated to good things??
Our final piece of code in our Windows skeleton...
////////////////////////////////////////////////////////
// loadConfigSettings
// This module handles loading application settings from
// an .ini file, thereby negating the need to recompile
// with each small change
//

void loadConfigSettings(TCHAR *szIniFile){

	TCHAR szIniPath[MAX_PATH];
	DWORD dwRetVal = 0;

	ZeroMemory(&szIniPath, sizeof(MAX_PATH)); //clear out our allocated memory

	GetCurrentDirectory(MAX_PATH, szIniPath); //find our current directory
	strcat(szIniPath, "\\");
	strcat(szIniPath, szIniFile);             //concatonate the name of the configuration file



	g_dwAppWidth = GetPrivateProfileInt("Application",  // section name
	                                    "WindowWidth",  // key name
	                                    300,            // return value if key name not found
	                                    szIniPath);

	g_dwAppHeight = GetPrivateProfileInt("Application",   // section name
	                                     "WindowHeight",  // key name
	                                     300,             // return value if key name not found
	                                     szIniPath);

	g_dwAppDepth = GetPrivateProfileInt("Application", // section name
	                                    "WindowDepth", // key name
	                                    16,            // return value if key name not found
	                                    szIniPath);

	dwRetVal = GetPrivateProfileString("Application",     // section name
	                                   "ApplicationTitle",// key name
	                                   "WazooGame v1.5",  // default string
	                                   g_szAppTitle,      // destination buffer
	                                   MAX_PATH,          // size of destination buffer
	                                   szIniPath);        // initialization file name


}//end loadConfigSettings
/////////////////////////////////////////////////////////////////


Well this is the newest function to hit the WIN32 skeleton file. It doesn't do anything all that fancy.
First, we grab the current directory of the executable, then we concatonate the name of the .ini file that we wish to read our configuration information from.
Then we proceed to read in values from the .ini file. That's about it..
////////////////////////////////////////////////////////
// GAME_INIT
// This module handles the initialization of our game
//

HRESULT Game_Init(HWND hWnd, HINSTANCE hInstance) {

	return S_OK; //no error

}//end Game_Init

////////////////////////////////////////////////////////
// GAME_RUN
// This module handles the main loop of our game
//

void Game_Run(void *params) {

}//end Game_Frame

////////////////////////////////////////////////////////
// GAME_SHUTDOWN
// This module handles the shutdown and cleanup of our game
//
void Game_Shutdown(void *params) {

}//end Game_Shutdown

////////////////////////////////////////////////////////



Okay the last 3 functions are all pretty much self-explanatory. Our GAME_INIT function handles the creation and initialization of our game specific objects.
Our GAME_RUN function is basically our main game loop. Where we get the input from the user, move objects, play sounds, and render the graphics.
Our GAME_SHUTDOWN function handles the destruction of our game specific objects and cleans up any used memory

Well that's about it for this tutorial. I know I only briefly mentioned basic Windows programming, but I just wanted to sort of let you get your feet wet without drowning you. There are already many very fine tomes on Windows Programming, so you can check with them for more details..

[End of Tutorial]

Back to the Index

 

[top]

 
 
© 2003, Silver.
Wind, two Tears, and a Funeral...