|
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
#define WIN32_MEAN_AND_LEAN
#include <windows.h>
TCHAR *g_szAppClass = NULL;
TCHAR g_szAppTitle[MAX_PATH];
BOOL g_bIsActive = FALSE;
BOOL g_bDisplayReady = FALSE;
DWORD g_dwAppWidth = 0;
DWORD g_dwAppHeight = 0;
DWORD g_dwAppDepth = 0;
HRESULT Game_Init(HWND, HINSTANCE);
HRESULT Game_Shutdown(void *parms=NULL);
HRESULT Game_Run(void *parms=NULL);
void loadConfigSettings(TCHAR*);
static LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;
switch(msg)
{
case WM_ACTIVATE:
g_bIsActive = (BOOL)wparam;
if( g_bIsActive )
{
g_bDisplayReady = TRUE;
}
break;
case WM_SETCURSOR:
SetCursor( LoadCursor( NULL, IDC_ARROW ) );
return TRUE;
case WM_PAINT: {
hdc = BeginPaint(hwnd,&ps);
EndPaint(hwnd,&ps);
return(0);
} break;
case WM_DESTROY: {
PostQuitMessage(0);
return(0);
} break;
default:break;
}
return (DefWindowProc(hwnd, msg, wparam, lparam));
}
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
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpcmdline,
int ncmdshow)
{
WNDCLASSEX winclass;
HWND hWnd;
MSG msg;
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC |
CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hInstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = g_szAppClass;
winclass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
if (!RegisterClassEx(&winclass))
return(0);
loadConfigSettings("WazooGame.ini");
DWORD dwStyle;
DWORD dwExStyle;
RECT windowRect;
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);
if (!(hWnd = CreateWindowEx(NULL,
g_szAppClass,
g_szAppTitle,
dwStyle |
WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
0,0,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
hInstance,
NULL));
return(0);
ShowWindow(hWnd, ncmdshow);
UpdateWindow(hWnd);
if(FAILED(Game_Init(hWnd, hInstance))){
MessageBox(NULL, "Failed to Initialize Project", "Init Error", MB_OK);
Game_Shutdown();
return FALSE;
}
BOOL bGotMsg = FALSE;
PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE );
while(WM_QUIT != msg.message ){
if( g_bIsActive )
bGotMsg = PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE );
else
bGotMsg = GetMessage( &msg, NULL, 0U, 0U );
if( bGotMsg )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
} else {
if(g_bDisplayReady){
Game_Run();
}
}
}
Game_Shutdown();
UnregisterClass(g_szAppClass, hInstance);
return(msg.wParam);
}
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
- Grabbing the first message from the top of the message
queue
- If it's a WM_QUIT message, then signal the app we wish
to quit
- Translate any Accelerator commands ie. CTRL+S (which is
normally "File->Save")
- Send the message to our Callback routine which
we defined earlier
- Go into our Game_Frame function which is our main
loop of our game
- 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...
void loadConfigSettings(TCHAR *szIniFile){
TCHAR szIniPath[MAX_PATH];
DWORD dwRetVal = 0;
ZeroMemory(&szIniPath, sizeof(MAX_PATH));
GetCurrentDirectory(MAX_PATH, szIniPath);
strcat(szIniPath, "\\");
strcat(szIniPath, szIniFile);
g_dwAppWidth = GetPrivateProfileInt("Application",
"WindowWidth",
300,
szIniPath);
g_dwAppHeight = GetPrivateProfileInt("Application",
"WindowHeight",
300,
szIniPath);
g_dwAppDepth = GetPrivateProfileInt("Application",
"WindowDepth",
16,
szIniPath);
dwRetVal = GetPrivateProfileString("Application",
"ApplicationTitle",
"WazooGame v1.5",
g_szAppTitle,
MAX_PATH,
szIniPath);
}
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..
HRESULT Game_Init(HWND hWnd, HINSTANCE hInstance) {
return S_OK;
}
////////////////////////////////////////////////////////
// GAME_RUN
// This module handles the main loop of our game
//
void Game_Run(void *params) {
}
////////////////////////////////////////////////////////
// GAME_SHUTDOWN
// This module handles the shutdown and cleanup of our game
//
void Game_Shutdown(void *params) {
}
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] |
|
|