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

Back in Part 1 of this tutorial, we took a look at putting together a game loop for the Windows desktop using C and the Win32 API.

We put the basics together, and so in this episode we’ll look at adding an ability to create your application in either a windowed frame, or as a fullscreen program.

While we can “force” which presentation mode we want to unveil our creation to the player, as a player I must admit I enjoy it when I get the choice; there’s times when I’m playing a game that I want it in a smaller window vs. taking over fullscreen, and vice-versa.

Updating initialization

Let’s start by taking a look at how we’re initializing our main window handle (aka. the CreateWindowEx function call):

// 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;

What we need to do is to keep track of our desired window size, and if we want to work in a windowed or fullscreen mode.

At the top of the file, let’s add some variables:

int width = 640;
int height = 480;
bool windowed = true;

Feel free to make the initial width and height to whatever size you want: 800x600 or 1024x768 are other common window sizes for example.

Usually I find it a better experience (as a player) if the game starts in a windowed mode then I have the option available to go fullscreen; but that’s just me. Again, feel free to set the initial windowed boolean to your own preference.

Now in the same file just before we make the call to CreateWindowEx, we’ll add a bit of logic to help setup our window with our given size and windowed mode.

Just after the RegisterClassEx call, let’s add some new code, and update some parameters to CreateWindowEx:

DWORD style = WS_OVERLAPPEDWINDOW;
    RECT gameWindow = {0, 0, width, height};

    if (!windowed) {
      style = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
    }

    AdjustWindowRect(&rectWindow, style, FALSE);

    // create the actual visual window handle
    if (!(hWnd = CreateWindowEx(NULL,
       className,
       appTitle,
       style | WS_VISIBLE,
       CW_USEDEFAULT,
       CW_USEDEFAULT,
       gameWindow.right - gameWindow.left,
       gameWindow.bottom - gameWindow.top,
       NULL,
       NULL,
       hInstance,
       NULL)))
    return -2;

Toggle Windowed Mode During Runtime

With our window initialization updated, we can turn to focus on being able to support the ability to switch our application between windowed and fullscreen modes during runtime.

As mentioned in the Part 1 tutorial, Windows programs operate by responding to events received by the WindowProc; such as a keyboard keypress for our purposes.

The event we’ll need to listen for is called WM_KEYDOWN, which is triggered whenever a keyboard key is pressed.

switch(uMsg) {
        case WM_KEYDOWN:
            switch(wParam) {
                case VK_F1:
                  // toggle our windowed mode
                break;
                default:
                break;
            }
            break;
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
            return 0;
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
        break;
    }

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

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!

Tie-Fighter Total Conversion Release 1.1

The X-Wing / Tie-Fighter mod communities have been around for many years, with the shared purpose that most mod communities have; which is to further build and enhance upon a (classic or modern) game.

I’ll continue writing and exploring the tooling available for the Tie-Fighter / X-Wing modding communities, but this post is more focused on the actual release of the Tie-Fighter Total Conversion project itself to release 1.1!

From the official Tie-Fighter Total Conversion moddb.com page

The TIE Fighter Total Conversion (TFTC) project is aimed at porting the original classic 1994 LucasArts game TIE Fighter, into the 1999 X-Wing Alliance (XWA) engine.

Based on the original TFTC from 2005 and built upon the X-Wing Alliance Upgrade project, this is a complete overhaul of the game from graphics to gameplay. All 13 battle campaigns and their training missions have been ported over along with 8 Reimagined battle campaigns, taking advantage of the much better XWA engine and imagining how TIE Fighter could’ve been had the technology of the time not limited it.

But here’s a general breakdown of what you can expect from this mod!

  • A full port of all the Classic TIE Fighter missions, including the expansions Defender of the Empire and Enemies of the Empire - a total of 76 campaign missions + the 28 original Training missions. This includes all the original voiceovers and cutscenes.
  • A Reimagined campaign of the first 8 Battles of TIE Fighter. These take the original missions, expand upon them for more ships, bigger battles or in some cases completely build new missions out of them but retaining the same overall narrative and story points of the original Classic. This campaign includes 37 fully playable campaign missions + 4 updated training scenarios.
  • That’s a total of 145 unique missions between both the Classic and Reimagined versions!
  • Enjoy the amazing updated visuals for the X-Wing Alliance engine made possible by the X-Wing Alliance Upgrade project (XWAU), bringing this 22 year old game into the modern era of graphics. VR Support, again thanks to the XWAU project.
  • Enjoy a fully remastered TIE Fighter soundtrack or even load up the old original MIDI soundtrack if you’re feeling more nostalgic!
  • Rank up, collect memorabilia and earn medals in your pilot room as you progress!
  • Join the Secret Order of the Emperor as you play through and rank up those tattoos just like the original.

What you need to install and play Tie Fighter Total Conversion (TFTC)

Note: None of these links are affiliate links.

Note: It’s important to download and install all the XWAU and TFTC mods in the specific order. The latest patches or releases do not also include anything that’s been previously released, and thus rely on them being present and installed.

The official TFTC installation tutorial video