Skip navigation

//////////////////////////////////////////
//                                      //
// A Fast Windows Program               //
//                                      //
// You found this at bobobobo's weblog, //
// http://bobobobo.wordpress.com        //
//                                      //
// Creation date:  Feb 3/08             //
// Last modified:  Feb 9/08             //
//                                      //
//////////////////////////////////////////

#include <windows.h>
#include <math.h>   // for sin() and cos() in calculating npc pos

// Function prototypes.
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam );
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow );

////////////////////////////
// Introduction:
// 
// In the "MostBasicWindow" project, we talked
// about creating a basic window, and how
// MOST NORMAL Windows programs use something
// called "EVENT DRIVEN PROGRAMMING".

// If you don't remember, please review
// PREREQUISITES:  BASIC WINDOWS PROGRAMMING
// (see http://bobobobo.wordpress.com/2008/01/31/how-to-create-a-basic-window-in-c/
// to get started).

// In EVENT DRIVEN PROGRAMMING, your
// program is entirely driven by EVENTS.

// If there are no events, then your
// Windows program DOES NOTHING AT ALL,
// until the next EVENT is passed to it
// by the Windows O/S in the form of a 
// MESSAGE.

// Now, this is a PROBLEM when it comes
// to programming a GAME AS A Windows application.

// If your app NORMALLY does nothing
// except when the user interacts with
// the application, then every time
// the user let go of all of the keys
// on the keyboard, the game would just
// simply PAUSE.  This is because
// the normal structure of a Windows
// application is to execute code
// IN RESPONSE TO EVENTS THAT COME
// to the window.  If there are no
// events, then the application is idle.

// That structure makes perfect sense
// for applications like MSWord or firefox, and its
// the only way things are sane inside
// that Windows Government and the
// processor doesn't blow up when you
// have 50 windows open at the same time.

// However, GAMES are different than normal
// standard windows apps like Firefox or
// MSWord.

// Games need to CONSTANTLY be running.  Games
// need to constantly update their state.
// Even if the player isn't doing anything,
// the game AI has to keep computing what
// the MONSTERS will do next.  The game
// has to have a concept of TIME that keeps
// on going on, even when the player isn't
// pressing any keys too.

///////////////////////
// RECALL:  The NORMAL WINDOWS PROGRAM MESSAGE LOOP:
/*
    while( GetMessage( &msg, NULL, 0, 0 ) )
    {
        // GetMessage is a function that will not
        // return until the Windows O/S HAS A message
        // for our program.

        // Since the GetMessage() function call is
        // INSIDE the bracket for the while Loop,
        // this means that our program is essentially
        // "put on hold" or halted until the GetMessage function
        // returns.

        // If and when the user interacts with our
        // application's window, then the GetMessage()
        // function WILL return and the variable
        // msg will be filled with interesting details
        // about exactly what the user did to the window.

        TranslateMessage( &msg );   // translates 
        // the message so WndProc can process it
        // more easily.

        DispatchMessage( &msg );    // this line RESULTS IN
        // a call to WndProc(), passing the message and
        // the HWND.  This is OUR APPS CHANCE
        // to execute some code in response
        // to user events.
    }
*/

// The point is, GAMES CAN'T _WAIT_ FOR
// MESSAGES FROM THE WINDOWS O/S TO BE
// ALLOWED TO EXECUTE THEIR CODE!!

// Games need to run very quickly, and
// execute the following instructions
// __at least__ 60 times a second!

// {
//      checkWhatKeysUserIsPushingDown();
//      makeChangesToGameStateAndCalculateNextFrame();
//      draw();
// }

// Because GetMessage() blocks (doesn't return
// control to our app immediately!), it really
// creates a bottleneck for us!

// IN OTHER WORDS, WE HAVE TO STOP USING
// THE GetMessage() function!

// For our game, we DO NOT want to be WAITING
// for messages AT ALL.  Instead, we will
// use something else called PeekMessage()
// to VERY QUICKLY check to see if the
// Windows O/S has any messages for our
// Window.

// IF the Windows Government does have a message for us,
// THEN we process that message, then its
// back to our computing and drawing functions
// as usual.

// IF the Windows O/S DOES NOT
// have any messages for our Window, WE
// WANT TO CONTINUE RUNNING OUR OWN CODE
// ANYWAY -- WE DO NOT WANT TO GIVE UP
// CONTROL TO THE WINDOWS O/S.

#pragma region globals
HWND hwnd;      // store the HWND in a global now,
                // so our DRAW function can use it freely.

// player x and y positions.
float playerXPos = 50;
float playerYPos = 50;

// NPC x and y positions.
float npcXPos = 80;
float npcYPos = 80;
#pragma endregion

void check_for_user_input_through_keyboard()
{
    float diff = 0.01f;

    if( GetAsyncKeyState( VK_UP ) )
    {
        // up key being pushed, move player up
        playerYPos -= diff;
    }

    if( GetAsyncKeyState( VK_DOWN ) )
    {
        playerYPos += diff;
    }

    if( GetAsyncKeyState( VK_RIGHT ) )
    {
        playerXPos += diff;
    }

    if( GetAsyncKeyState( VK_LEFT ) )
    {
        playerXPos -= diff;
    }
}

// In this function, we're calculating
// the motion of the "npc unit" - very simple
void calculate_next_frame_of_our_program_by_shuffling_npc_around()
{
    // let's move the NPC unit around, in a circle.
    static float pos = 0.0f;
    pos += 0.1f;

    npcXPos += 0.01*sin( 0.001*pos );
    npcYPos += 0.01*cos( 0.001*pos );
}

void draw()
{
    HDC hdcTotalArtist = GetDC( hwnd ); // get the "handle to device context"
                                        // for our Window.

    // The "device context" is like a "total artist"
    // that has pens, brushes, and a canvas to draw to.

    // I explain that more below, but for now, let's
    // draw the player:
    Ellipse( hdcTotalArtist, (int)playerXPos, (int)playerYPos, (int)(playerXPos + 10), (int)(playerYPos + 10) );

    ////////////
    // Changing the pen color:
    // Now the npc unit should be a different color.
    // so this is REALLY QUICKLY how you change
    // the color of the "pen" that you're using
    // to draw.

    // The "device context" of our window is
    // like a TOTAL ARTIST.
    // The "device context" contains:
        // canvas
        // pens
        // brushes
        // and more!

    // In order to change the style in which the
    // standard GDI functions (like Ellipse) are
    // drawing, we would have to CHANGE THE
    // PROPERTIES OF THE DEVICE CONTEXT TO WHICH
    // WE ARE DRAWING.

    // So what we do is, we:
        // 1.  CREATE A NEW redpen,
        //      WITH ALL THE PROPERTIES
        //      WE WANT IT TO HAVE (solid lines, can select width as well)
        // 
        // 2.  GIVE THAT redpen TO
        //     THE device context "total artist"
        //     by using the SelectObject() function.
        //     Note that the hdc total artist can
        //     only hold ONE PEN at a time.  So
        //     you'll see in the code that when we
        //     give the hdc total artist
        //     a new pen, WE MUST CATCH THE OLD ONE
        //     IN A VARIABLE!
        //
        // 3.  DRAW AGAIN using the Ellipse()
        //     gdi function.  Since the Ellipse()
        //     function WILL draw to an HDC,
        //     then we will see RED ELLIPSE, since
        //     we just gave the hdc total artist
        //     a red pen.

    // "select the pen into" the device context
    // by using the SelectObject() function.

    // This in a sense is like GIVING the device
    // context a new brush with which to draw,
    // or a new pen with which to draw.
    HPEN redpen = (HPEN)CreatePen( PS_SOLID, 2, RGB( 255, 0, 0 ) );
    HPEN oldpen = (HPEN)SelectObject( hdcTotalArtist, redpen );

    // draw with redpen
    Ellipse( hdcTotalArtist, (int)npcXPos, (int)npcYPos, (int)npcXPos + 10, (int)npcYPos + 10 );

    // select back the old pen, then delete the red pen
    redpen = (HPEN)SelectObject( hdcTotalArtist, oldpen );
    DeleteObject( redpen );

    ReleaseDC( hwnd, hdcTotalArtist );
}

////////////////////////////
// In a C++ Windows app, the starting point is WinMain().
int WINAPI WinMain( HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR szCmdLine,
                    int iCmdShow )
{
    #pragma region part 1 - STARTUP STUFF
    WNDCLASS wc;
    wc.cbClsExtra = 0; 
    wc.cbWndExtra = 0; 
    wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );         
    wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );     
    wc.hInstance = hInstance;         
    wc.lpfnWndProc = WndProc;         
    wc.lpszClassName = TEXT("Philip");
    wc.lpszMenuName = 0; 
    wc.style = CS_HREDRAW | CS_VREDRAW;

    // B.  Register the WNDCLASS with Windows, THEN
    //     create the window.
    RegisterClass( &wc );
    hwnd = CreateWindow(
        TEXT("Philip"),
        TEXT("Fast windows app!!"),
        WS_OVERLAPPEDWINDOW,
        10, 10,
        400, 400,
        NULL, NULL,
        hInstance, NULL );      

    // Next, SHOW and PAINT the window!
    // You won't see the window if you DO NOT
    // call ShowWindow();
    ShowWindow(hwnd, iCmdShow );
	UpdateWindow(hwnd);
    #pragma endregion

    // first, we create the MSG structure.
    MSG msg;

    #pragma region OLD GETMESSAGE LOOP -- NORMALLY YOU SHOULD JUST DELETE THIS PART
    // -- commented out, but I STRONGLY ENCOURAGE
    // you to try commenting it back in to see
    // how it behaves and why we CAN'T be using
    // GetMessage() for our games.

    // IT DOES WORK, but its INCREDIBLY, INCREDIBLY
    // slow.

    // And on top of the extreme SLOW, you have to constantly
    // be inputting "messages" into the window in order
    // for anything to happen! (for the npc to move!)

    /*
    SetWindowText( hwnd, TEXT("SLOW MODE!!! - Press ESC for fast mode") );
    while( GetMessage( &msg, NULL, 0, 0 ) )
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg ); 

        check_for_user_input_through_keyboard();
        calculate_next_frame_of_our_program_by_shuffling_npc_around();
        draw();
    }
    */
    #pragma endregion

    #pragma region part 2 - ENTER A LOOP TO CONTINUALLY KEEP CHECKING WITH WIN O/S FOR USER INTERACTION

    // Notice we changed the loop structure from the old
    // GetMessage() structure.  We're using PeekMessage() now.

    // It goes like this now:

    // while(FOREVER)
    // {
    //    if ( there's a message for our window )
    //    {
    //      translate and dispatch to WndProc for processing();
    //    }
    //  
    //    check_for_user_input_through_keyboard();
    //    calculate_next_frame_of_our_program_by_shuffling_npc_around();
    //    draw();
    // 
    // }
    SetWindowText( hwnd, TEXT("Fast windows app!!") );  // (in case you uncommented
                                                        // the GetMessage() above )
    while( 1 )  // LOOP FOREVER
    {
        // 1.  PROCESS MESSAGES FROM WINDOWS
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            // Now, this is DIFFERENT than GetMessage()!
            // PeekMessage is NOT a BLOCKING FUNCTION.
            // That is, PeekMessage() returns immediately
            // with either a TRUE (there was a message
            // for our window)
            // or a FALSE (there was no message for our window).

            // If there was a message for our window, then
            // we want to translate and dispatch that message.

            // otherwise, we want to be free to run
            // the next frame of our program.
            if( msg.message == WM_QUIT )
            {
                break;  // BREAK OUT OF INFINITE LOOP
                        // if user is trying to quit!
            }
            else
            {
              TranslateMessage( &msg );   // translates 
              DispatchMessage( &msg );    // this line RESULTS IN
              // a call to WndProc(), passing the message and
              // the HWND.

              // Note that in this program, all we're really using
              // the messaging system for is for
              // processing the QUIT message that occurs
              // when the user clicks the X on the window
              // to close it.

              // ALL OTHER application processing happens
              // in the 3 lines of code below
              // (the 'check_for_user ... ' stuff)
           }
        }
        else
        {
           check_for_user_input_through_keyboard();
           calculate_next_frame_of_our_program_by_shuffling_npc_around();
           draw();
        }
    }
    #pragma endregion

    return msg.wParam;
}

LRESULT CALLBACK WndProc(   HWND hwnd,      
                            UINT message,  
                            WPARAM wparam,  
                            LPARAM lparam ) 
{
    switch( message )
    {
    case WM_CREATE:
        // upon creation, let the speaker beep at 50Hz, for 10ms.
        Beep( 50, 10 );
        return 0;
        break;

    case WM_PAINT:
        {
            // we would place our Windows painting code here.
            HDC hdc;
            PAINTSTRUCT ps;
            hdc = BeginPaint( hwnd, &ps );

            // draw nothing here.
            // drawing happens in the 
            // draw() function that
            // we MANUALLY call ourselves.

            // We DO NOT rely on messages to
            // tell us when to draw when creating
            // game!  Instead, we draw when
            // we want to.

            // WHY?  Because the WM_PAINT message
            // only comes to your window WHEN
            // the Windows O/S THINKS your window
            // needs to be redrawn.  That includes
            // only events like "if your window
            // was blocked by another window,
            // then it needs to be repainted"

            // Our fast game NEEDS to be redrawn
            // 60 times a second AT LEAST, so we
            // totally BYPASS the normal windows
            // system, and draw in our draw()
            // function of our own accord.

            EndPaint( hwnd, &ps );
        }
        return 0;
        break;

    case WM_KEYDOWN:
        switch( wparam )
        {
        case VK_ESCAPE:
            PostQuitMessage( 0 );
            break;
        default:
            break;
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage( 0 ) ;
        return 0;
        break;
    }

    return DefWindowProc( hwnd, message, wparam, lparam );
}

///////////////////////
// Closing notes:
//
// IF you'll notice, when you run this
// program, if you look at your task
// manager, (CTRL+ALT+DEL), NOTICE your
// windows application (should show up
// in processes list as BasicWindowsProgramming_GameStructure.exe)
// is now a CPU HOG.  Its taking somewhere
// close to 40-50% of CPU time.

// THIS IS GOOD. YOUR APP IS TAKING
// CONTROL OF THE SYSTEM RESOURCES
// (CPU TIME).
//
// If its a game, THIS IS EXACTLY WHAT YOU
// WANT.  Games need to hog the system
// resources in order to run the best
// they possibly can given the user's
// hardware.

// Using this Windows program structure -
// a "greedy" structure if you will -
// we can create games that run as
// fast as they can.

/* 
     ____   __   __      __   __  ___
    / _  \ /  / /  /    /  /  \ \/  /
   / _/ / /  / /  /    /  /    \   /
  / _/ \ /  / /  /__  /  /__   /  /
 /_____//__/ /______//______/ /__/

*/

Visual Studio 2005 Project files hosted by esnips! (thanks esnips!)

About these ads

7 Comments

  1. thanks!

    • chromogrinch
    • Posted February 29, 2008 at 2:57 pm
    • Permalink

    No frills and straight to the point.
    Thanks man!

    • Steeve
    • Posted April 23, 2009 at 2:50 pm
    • Permalink

    “Its taking somewhere close to 40-50% of CPU time. THIS IS GOOD.” – well, this just means that the program is eating up unnecessary cpu time. it should have a SLEEP() call somewhere in the main loop.

  2. The game is simple now, but as the game grows you’ll need that CPU time and you’ll have to take the sleep call out.

    • Eman
    • Posted April 21, 2013 at 4:27 am
    • Permalink

    bobobobo thanks much! I’ve been having a hard time understanding the windows messaging but your program was very helpful.

    • Anton
    • Posted May 27, 2013 at 7:55 am
    • Permalink

    if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
    it have to be while, because windows can send couple messages in one tick

    • Anonymous
    • Posted May 30, 2013 at 8:32 pm
    • Permalink

    @Anton Perhaps you don’t understand how the messaging system works in Windows. Messages are posted into the message queue and then retrieved via GetMessage / PeekMessage. On every iteration of the loop, it will attempt to pull out the next message in the queue; thus, no messages are skipped.


2 Trackbacks/Pingbacks

  1. [...] // Windows app functions. If need help // understanding these, see MostBasicWindow // and FastWindowsProgram LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam ); int WINAPI [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 37 other followers

%d bloggers like this: