Topic : DirectX Programming
Author : Ian Fleeton
Page : << Previous 3  Next >>
Go to page :


inside just one function?! Well we'll use this one function to contain our basic game but later we'll dabble in function pointers. Oh, what fun that will be!

The WM_ACTIVATE message handling code is quite compact but it basically checks whether the window is minimised or not and if the window is active. You can find more about WM_ACTIVE in the platform SDK documentation.

#include <windows.h>
#include <ddraw.h>

// dissect globals
HINSTANCE   g_hinstance;
HWND        g_hwnd;
bool        g_running;
bool        g_active;
const char* g_appName = "My DirectDraw Program";

void update() { /* Update game */ }
bool createWindow();
LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM);

// dissect WinMain
int APIENTRY WinMain(HINSTANCE hinstance, HINSTANCE,
                     LPSTR cmdLine, int)
{
    g_hinstance = hinstance; // Make a copy so we can use it later

    if(!createWindow())
        return -1; // Failed to create a window

    SetFocus(g_hwnd);
    ShowWindow(g_hwnd, SW_SHOWNORMAL);
    UpdateWindow(g_hwnd);

    // Set up DirectDraw (we'll do this later)
    
    g_running = true;

    while(true)
    {
        MSG msg;
        if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            if(msg.message == WM_QUIT)
                return msg.wParam;

            // Send messages to WindowProc
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if(g_running && g_active)
            update();
    }

    // Prevent compiler warning even though we never get here
    return -1;
}

// dissect createWindow
bool createWindow()
{
    WNDCLASSEX wc;
    ZeroMemory(&wc, sizeof(wc));
    
    wc.hbrBackground    = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.hCursor          = LoadCursor(0, IDC_ARROW);
    wc.hIcon            = LoadIcon(0, IDI_APPLICATION);
    wc.hIconSm          = LoadIcon(0, IDI_WINLOGO);
    wc.hInstance        = g_hinstance;
    wc.lpfnWndProc      = wndProc;
    wc.lpszClassName    = g_appName;
    wc.cbSize           = sizeof(wc);
    wc.style            = CS_VREDRAW | CS_HREDRAW;
    
    if(RegisterClassEx(&wc))
    {
        g_hwnd = CreateWindowEx(
            WS_EX_TOPMOST,
            g_appName,
            g_appName,
            WS_POPUP | WS_VISIBLE,
            CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, CW_USEDEFAULT,
            0, 0, g_hinstance, 0
        );
        return g_hwnd != 0;
    }

    return false;
}

// dissect wndProc
LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM w, LPARAM l)
{
    switch(msg)
    {
    case WM_DESTROY:
        // Clean up (we'll do this later)
        PostQuitMessage(0);
        return 0;
    case WM_ACTIVATE:
        g_active = (LOWORD(w) != WA_INACTIVE) && !((BOOL)HIWORD(w));
        break;
    }

    return DefWindowProc(hwnd, msg, w, l);
}



DirectDraw


That's the window code out of the way - now on to the fun stuff - DirectDraw! First you are going to have to get hold of a DirectDraw interface which represents the computer's main video card. Once we've done that we'll set the co-operation level to fullscreen exclusive mode and set the display mode to 640x480x8 and finally set up a pair of flipping surfaces.

What you'll see in the remainder of the tutorial are just snippets of code. However, you can download ddraw1_b which contains a working example of the code. You can quit out of the program by pressing Alt+F4.


DirectDraw Interface


Before you can do anything with DirectDraw you need to get hold of a DirectDraw interface. This is based on COM but you'll hardly notice it. Our C++ link to this is with a pointer, which we'll call dd. Many people use lpdd and lpDD but that's because of Microsoft's use of Hungarian Notation which I don't use that much. Note that we are using the version 7 interface because it hasn't been updated since. You can define the pointer in two ways:

LPDIRECTDRAW7 dd;

or

IDirectDraw7* dd;

Whichever you use is a matter of style. I prefer latter because it is clearer C++ code. Microsoft uses a lot of macros to make its libraries compatible with different languages.

At the moment we have a useless pointer. If your DirectDraw pointer isn't global, be sure to set it to 0 or NULL to be safe. The next bit of code will get you that interface.

HRESULT r = DirectDrawCreateEx(0, (void**)&dd, IID_IDirectDraw7, 0);
if(FAILED(r)) { /* Error */ }


There is another way by using CoCreateInstance() and you can look into that if you're a COM fanatic. This alternative method is described in the SDK documentation.

IID_IDirectDraw7 is a value that identifies the interface we want to be created. Although DirectDrawCreateEx() only accepts this as a valid parameter, its value is incremented in the header with each change to the SDK. DirectX uses this to make sure that we are using the correct header and not some outdate beta or something. (Thanks to Fabien Tragen for pointing this out to me.)

FAILED is a macro that you're going to see a lot of. You should prefer this over the following code:

if(r != DD_OK) { /* Error */ }

This is because Microsoft may change their error reporting system to use more than one OK code.


Setting the Co-operation Level


We've now got a grasp on the video card, so now let's take over completely! We're now going to change something known as the "co-operation level" to fullscreen exclusive mode. That means that no other program has access to the display as long as our game is active in the foreground.

The code for this is:

r = dd->SetCooperativeLevel(g_hwnd, DDSCL_EXCLUSIVE |
    DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT);
if(FAILED(r)) { /* Error */ }


You may also want to OR the value DDSCL_NOWINDOWCHANGES to prevent the player from minimising the game by pressing Alt-Tab or Ctrl-Esc. Why would you want to do this to your player? Well, when the player does this, you lose control of the video card as well as the memory. Many of your loaded bitmaps will be overwritten so you'll have to regain all the memory and reload the bitmaps. I will show you how to do this later but for now use DDSCL_NOWINDOWCHANGES.


Setting the Display Mode


Now that we have total control, let's set the display mode. You should check which display modes the user's hardware supports but I've already kept you waiting too long. So we'll dive in and hope for the best. We'll choose a reasonable, well-supported mode - 640x480x8.

I hope you know what 640x480x8 means. If not I'll just have to explain. It is shorthand for a resolution of 640 pixels across by 480 pixels down and with a bit depth of 8 bits per pixel, which happens to be 256 colour paletted mode. Later we'll cover 16, 24, and 32 bits per pixel.

Because we're using 256 colours only, you'll notice that we're a bit limited. I'm not going to tell you how to change the palette in this tutorial so we'll be stuck with the standard Windows palette.

The code to set the display mode is as follows:

r = dd->SetDisplayMode(640, 480, 8, 0, 0);
if(FAILED(r)) { /* Error */ }


The last two parameters are refresh rate and extra flags. Use 0 to

Page : << Previous 3  Next >>