Erik Yuzwa

Fullstack web developer with a touch of PC game development for good measure

25 July 2021

Creating a Windows Game Loop With C and the Win32 API (part 1)

by erikyuzwa

Hello my friends, I’m so excited for this post!

In this post we’re going to put together a game loop using the Win32 API and C. If you’ve always been curious about digging into the basics of a Windows game loop, then hopefully this satisfies your cravings.

The best thing about this is that once we go through and create it, we can use it for just about any project we want to for Windows games; more specifically, I’m going to be referencing this same tutorial in some future content I have planned.

Requirements

Introducing the Windows Event Model

While you don’t actually need to dig too deeply into the nitty gritty depths of the Win32 API (or Windows itself), the important thing to learn and keep in mind, is that the softw…

The WinMain entry point

Without further ado, let’s get into the entry point of every Windows program; WinMain

#define WIN32_LEAN_AND_MEAN

#include <Windows.h>


const WCHAR* className = L"WIN32GAME";
const WCHAR* appTitle = L"My win32 game";

extern int game_startup();
extern void game_update();
extern void game_shutdown();

int WINAPI WinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,
  int nCmdShow)
{

    WNDCLASSEX winclass;
    HWND       hWnd;
    MSG        msg;

    // setup the class instance of our Win32 application
    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_WINLOGO);
    winclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    winclass.lpszMenuName  = NULL;
    winclass.lpszClassName = className;
    winclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    // attempt to register the win32 application class
    if (!RegisterClassEx(&winclass)) {
      return -1;
    }

    // create the actual visual window handle - 640x480
    if (!(hWnd = CreateWindowEx(NULL,
       className,
       appTitle,
       WS_OVERLAPPEDWINDOW | WS_VISIBLE,
       CW_USEDEFAULT,
       CW_USEDEFAULT,
       640,
       480,
       NULL,
       NULL,
       hInstance,
       NULL)))
    return -2;

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    SetFocus(hWnd);

    // with the window created, run your game initialization
    if (game_startup() < 0) {
       // depending on which startup initialization failed
       // goto our shutdown pathway to ensure resource cleanup
       goto SHUTDOWN_AND_EXIT;
    }

    // enter the main event loop
    while(TRUE) {
        // test if there is a message in queue, if so get it
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
           //
           if (msg.message == WM_QUIT)
               break;

           // translate any accelerator keys
           TranslateMessage(&msg);

           // send the message to the window proc
           DispatchMessage(&msg);
        }

        // run your game frame processing here
        game_update();

   } // end while

SHUTDOWN_AND_EXIT:
   // shutdown and cleanup any resource
   game_shutdown();

   return (int)msg.wParam;
}

Define a WindowProc

Back in the setup of our winclass, you might notice that we needed to define a function pointer for winclass.lpfnWndProc.

But what is that?

Remember that Windows applications function as programs that respond to Windows event messages. The WindowProc is this main message event receiver for our application.

For the purposes of game development, we really don’t need to respond to anything other then an actual WM_QUIT event message, which is sent when the user closes your application via the “X” button. (How dare they!?)

Otherwise, just funnel every event message we didn’t need to the default event message handler with DefWindowProc.

Here’s the full function for you.

LRESULT CALLBACK WindowProc(HWND hWnd,
                            UINT uMsg,
                            WPARAM wParam,
                            LPARAM lParam) {

    PAINTSTRUCT        ps;
    HDC                hdc;

    // what is the message
    switch(uMsg) {
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
            return 0;
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
        break;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

Define our (empty) stub functions

Now that we have the bare bones Win32 specific code defined, you might notice that we left a few “stub” game functions that we are responsible for defining in any game program we create with this code.

For now, we can define the 3 functions with empty bodies.

They can either go in the same file, or in a new one, such as game.cpp for instance:

int game_startup() { return 1; }

void game_update() {}

void game_shutdown() {}

I feel that for these lower-level Win32 functions, it’s a lot easier to work with straight “C”.

The main benefit of this, is that we leave the option available of dropping in any C++ classes we might want to use, or just keep using C for everything.

The choice is up to you!

tags: