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
- Any version of Visual Studio or CLion
- A cup of coffee
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!