🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

02.03 - The Basic Windows Application

Started by
26 comments, last by Teej 21 years, 5 months ago
Console (DOS) vs. Windows At their core, our games are essentially going to be Windows applications. Writing a Windows application is entirely different than writing a console (DOS) application, and you’re going to know at least enough about Windows applications to get a game ‘wedged’ into the operating system. First, both types of application have their own starting point. Take a quick look: DOS Program:

int main()
{
    return 0;
}
 
Windows Program:

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    return 0;
}
 
Both programs don’t do anything, but you can already see that there are differences. When you instruct MSVC to create a project for you, it’s important to select the proper application type (i.e. Win32 Application) or else the compiler is going to complain that it can’t find main() – it’s trying to compile a console application (because of the project type) when you actually want to create a Win32 Application. The Windows Programming Model Here are the steps taken by a typical Windows application in order to get up and running:
  1. The program describes the type of window (on-screen) that it will ‘live’ in. This is referred to as registering the window class
  2. Now that the OS knows what type of window the application desires, it asks the OS to actually create a window based on the description that was registered. Coincidentally, this is referred to as creating the window.
  3. The application instructs the OS to display the window on the screen.
  4. A message loop is entered that receives Windows messages. This code loop continually checks to see if there are any OS messages available, and calls a window procedure to handle the message.
The first three items in the list are mere overhead (after all, the OS is called Windows, so it makes sense that we are to create a window on the display), and the fourth item is the real catch here. Let me quickly explain… Once a Windows program enters its message loop, it’s basically sitting there waiting for something to happen. If the user isn’t clicking on something or typing away, the application has nothing to do (typically), so it continues executing its message loop code. In the event that something of interest to the application does happen, the OS lets the program know by sending it a message. Once a message is received by the application, it can then leave the message loop in order to process the message. Once message processing is complete, it’s back to the message loop again. For this reason, Windows applications are referred to as event-driven. If anything happens in the OS that a program is concerned with (e.g. the user clicked a button in the window), a message is created and sent to the application. This isn’t hard to visualize – take a look at most of the Windows programs on your machine… if you’re not doing anything, it usually isn’t either. Let’s take a look at the code for a typical message loop:

// Main message loop:
while (GetMessage(&msg, NULL, 0, 0)) 
{
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
 
Here you can see that the message loop is implemented as a while statement. So long as GetMessage() returns a ‘true’ value, the loop will continue to execute. Inside the message loop, accelerators are translated (keystroke combinations that have specific meanings), some internal message translation occurs, and finally the message is dispatched (i.e. a function in the program is called to handle the message). It’s not important to spend too much time investigating this code; most every Windows application has a similar message loop, so it’s standard code. What types of messages would a Windows application receive? Well, there are hundreds of different messages, but I can give you some examples of more common messages:
  • the user moves or resizes the window
  • the mouse button is pressed while the mouse is over the window
  • the user is typing on the keyboard while the application has the input focus
  • a button or other control is activated
  • the user is trying to close the application
I think that you get the idea. We’re not interested in delving too deep into the world of Windows application development – we’re here to make games, remember? A Game in an Application It seems to make sense that the typical Windows application is event-driven (i.e. responds to OS messages), but this is certainly a problem when it comes to writing a game. After all, games are constantly running (just like a DOS program), and yet Windows expects us to sit in a message loop waiting for events? Our first notion is to do away with the message loop altogether and just ‘plop’ our game right inside of WinMain(). The reason why this is highly inadvisable is because Windows needs to be able to communicate with our program (e.g. to let us know when the user switches windows or is attempting to close the application). What is typically done for a game is a slight modification of the message loop, like so:

// The main loop.  This will call Game_Main() repeatedly, stopping
// only to process Windows messages, and terminating with WM_QUIT.
while(TRUE)
{
    // Grab any Windows messages that are for us
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    { 
        // Send these messages to WindowProc
        if (msg.message == WM_QUIT)
            break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    // Call our Game function
    else if (G.bActive) Game_Main();
}
 
What this message loop is doing is ‘peeking’ at the message queue for the application. If there’s nothing in it, we go ahead and call our own function (in this case called Game_Main() ). What ends up happening is that our game function gets called over and over, and is interrupted only if there’s an OS message waiting for us. This brings up an interesting point. If our game code is sitting inside of a function called Game_Main(), that means it gets called repeatedly – not just once. It’s up to us to write our game code so that it works in this fashion…we’ll get to that later in the tutorial series. The important thing to note is that we have a technique for writing a game in Windows that behaves like a proper Windows application. Windows Message Processing When there’s a message directed to our application, it’s placed in a message queue – kind of like ‘taking a number’. The messages are taken, one at a time (first-come-first-served) and handed over to another function in our application for processing. You’ve seen the function responsible for handing the message over; it’s called DispatchMessage(). What isn’t apparent though is how the message makes it to another function in our program. When we registered our window class on startup, we provided to the OS the name of the function that will handle all Windows messages for our application. We’ll be looking at a complete example in a moment. As for processing the messages themselves, take a look at this piece of code:

//////////////////////////////////////////////////////////////////////////////
// WindowProc
//
// Whenever the operating system has a message for us, it ends up here.  If
// we''re interested in any particular message, we have to provide code to
// handle it.
//      
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // Process the window message
    switch (msg)
    {
        case WM_ACTIVATE:
            // The active state of our window is changing (e.g. ALT-TAB).
            // We keep track of this so that we don''t bother drawing
            // frames of animation if it can''t be seen anyhow...
            G.bActive = !((BOOL)HIWORD(wParam));
            return 0L;
 
        case WM_KEYDOWN:
            if (wParam == VK_ESCAPE)
            {
                // The ESCAPE key quits...
                G.bQuitting = TRUE;
                PostQuitMessage(0);
                return 0L;
            }
            break;
 
        case WM_DESTROY:
            // We''re done; quit the main loop
            PostQuitMessage(0);
            return 0L;
    }
 
    // Pass all other messages on for default processing
    return DefWindowProc(hWnd, msg, wParam, lParam);
}
 
The Windows messages are numerical, and their names start with WM_ . This particular example cares about WM_ACTIVATE, WM_KEYDOWN and WM_DESTROY. For all of the other hundreds of messages possible, we pass them off to a function called DefWindowProc(), which is supplied by Windows to deal with whatever we don’t. As you can see, this example doesn’t care about a whole lot. If it doesn’t concern terminating our application or switching programs in Windows, we let the OS take care of the housekeeping and we get back to our code inside of Game_Main(). The Least I Could Do… Let’s sum up, shall we? We really want to be a Windows application, because Windows applications get to use the system’s hardware automatically, and we can use components like DirectX for our game’s input, output, sound, etc. The rules for being a Windows application include registering a window class, creating a window, and providing a message loop so that the OS can talk to you. Games however need to be constantly running, and can’t merely respond to messages in order to execute. As a compromise, we supply all of the Windows application prerequisites, and wedge a call to our game code in the message loop so as to maximize execution time. For the purposes of this article, I’m not going to show you any complete sample code. If you’re interested, you can summon the powers of MSVC to create a sample Win32 Application for you and investigate the file(s) it generates, but you’ll be going through more trouble than it’s worth. When we start with the actual game development exercises, you’ll be introduced to the actual Windows component of the game template code, and because you took the time to read this article, things will naturally make sense. We’ve fulfilled our obligation to Windows, so we can move on to our obligation to gamers. Questions or comments? Please, reply to this topic.
Advertisement
Hello, I have a question about the object called "G"... is this an instance of the actual game? If so then bActive is a member of this Game class right? I am guessing that G is instsansiated in the Game_Instansiate() method, I am just confused of what a Game Object would be...

"You won''t get wise with the sleep still in your eyes." - Neil Peart
"You won't get wise with the sleep still in your eyes." - Neil Peart
From what it looks like, "G" is just a structure for the globals in the program. Each of those instances of G in the source code designates a global variable in the source.
PiotyrSoftware Engineer by day, Game developer by nightThe early bird may get the worm, but the second mouse gets the cheese.
"G" is a structure that don''t need to be initilized and it holds all the globals for the game and makes possible the fact of having several modules

d00dzs

I turn pixels into gold.
José Correia AKA d00dzs posting...HEEELLLOO
ahhh I see, I have never seen globals handled that way... btw, where would you declare that struct???

thanks

"You won't get wise with the sleep still in your eyes." - Neil Peart

Edited by - Mucman on April 9, 2001 8:45:50 PM
"You won't get wise with the sleep still in your eyes." - Neil Peart
Well considering that the consensus (and my guess too) is that it holds global variables, it would probably be declared in global scope, ie. at the beginning of the proggy =o)

(o= erydo =o)


[email=erydo@gdnmail.net" style="color: #ff0000; text-decoration:none; cursor:help;](o= erydo =o)[/email]
I though of that too, but would it be better to put it in a pre compiled header file??? Then you could access those variables from other files... or am I completely wrong???

"You won''t get wise with the sleep still in your eyes." - Neil Peart
"You won't get wise with the sleep still in your eyes." - Neil Peart
ack, DP

Edited by - Mucman on April 9, 2001 12:00:07 AM
"You won't get wise with the sleep still in your eyes." - Neil Peart
You may put you struct in a header file within that project and "include" it in the modules that need it.With that you have all your initialization vars(Surfaces, COM objs, etc...)in one place.

I turn pixels into gold!!
José Correia AKA d00dzs posting...HEEELLLOO
Hi,

For those of you who weren''t there in the first version of the tutorial, we finished a tetris game. It''s pretty basic but you can dig the code to see how G is handled.

I hope it won''t screw anything up...

http://sduguay.ctw.net/pclab/tetrzsrc.zip
Get the VC++ source at this URL.

No support provided and don''t bother anyone here about it as it is about the "OLD" forum. I provide it just to help the fasters of you

See ya,
Bios10h.

This topic is closed to new replies.

Advertisement