Basic Win32 game using SpriteBatch

Aug 5, 2012 at 5:27 PM

Hi everybody,

I'm still learning DirectX (and the Windows API... and C++) but, given that there seem to be no samples for DirectXTK at all, I figured I'd post what I've done in order to help others in my situation.

Here's a very basic clone of Brainiac by Michael Morrison. All of the window and DirectX initialisation stuff came straight from the Direct3D Tutorial Win32 Sample and all of the game logic is copied verbatim from Michael Morrison's code. Also, this website really helped me in understand what I was looking at.

I assume you've managed to compile DirectXTK and have copied all the necessary files to all the required locations. You also need to find nine BMP files of size 132 × 128 to act as the tiles in the game.

 

#include <memory>
#include <wrl.h>
#include <d3d11_1.h>
#include <d2d1_1.h>

#include <DirectXTK\SpriteBatch.h>
#include <DirectXTK\WICTextureLoader.h>

using namespace std;
using namespace D2D1;
using namespace DirectX;


//--------------------------------------------------------------------------------------
// Global Variables
//--------------------------------------------------------------------------------------
HINSTANCE                 g_hInst = NULL;
HWND                      g_hWnd = NULL;

D3D_DRIVER_TYPE           g_driverType = D3D_DRIVER_TYPE_NULL;
D3D_FEATURE_LEVEL         g_featureLevel = D3D_FEATURE_LEVEL_11_0;
ID3D11Device*             g_pd3dDevice = NULL;
ID3D11DeviceContext*      g_pd3dContext = NULL;
IDXGISwapChain*           g_pSwapChain = NULL;
ID3D11RenderTargetView*   g_pRenderTargetView = NULL;

unique_ptr<SpriteBatch>   g_spriteBatch;
ID3D11ShaderResourceView* g_pTexture[9];
bool                      g_bTileStates[4][4];
int                       g_iMatches, g_iTries, g_iTiles[4][4];
POINT                     g_ptTile1, g_ptTile2;


//--------------------------------------------------------------------------------------
// Forward declarations
//--------------------------------------------------------------------------------------
HRESULT Initialise(HINSTANCE, int);
HRESULT InitWindow(HINSTANCE, int);
HRESULT InitDevice();
void CleanupDevice();
void LoadContent();
void MouseButtonDown(int, int, bool);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void Draw();


//--------------------------------------------------------------------------------------
// Entry point to the program. Initialises everything and goes into a message processing 
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    if (FAILED(Initialise(hInstance, nCmdShow)))
        return 0;

    LoadContent();

    #pragma region Main Message Loop

    MSG msg = {0};
    while (WM_QUIT != msg.message)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            Draw();
        }
    }

    #pragma endregion

    CleanupDevice();

    return (int)msg.wParam;
}


//--------------------------------------------------------------------------------------
// General initialisation function
//--------------------------------------------------------------------------------------
HRESULT Initialise(HINSTANCE hInstance, int nCmdShow)
{
    if (FAILED(InitWindow(hInstance, nCmdShow)))
        return E_FAIL;

    if (FAILED(InitDevice()))
    {
        CleanupDevice();
        return E_FAIL;
    }

    #pragma region Initialise Game Variables

    // Clear the tile states and images
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            g_bTileStates[i][j] = false;
            g_iTiles[i][j] = 0;
        }
    }

    srand(GetTickCount());
    for (int i = 0; i < 2; i++)
    {
        for (int j = 1; j < 9; j++)
        {
            int x = rand() % 4;
            int y = rand() % 4;
            while (g_iTiles[x][y] != 0)
            {
                x = rand() % 4;
                y = rand() % 4;
            }
            g_iTiles[x][y] = j;
        }
    }
    g_ptTile1.x = g_ptTile1.y = -1;
    g_ptTile2.x = g_ptTile2.y = -1;
    g_iMatches = g_iTries = 0;

    #pragma endregion

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Register class and create window
//--------------------------------------------------------------------------------------
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow)
{
    #pragma region Register Window Class

    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"MainWindowClass";
    wcex.hIconSm = NULL;
    if (!RegisterClassEx(&wcex))
        return E_FAIL;

    #pragma endregion

    #pragma region Create Window

    g_hInst = hInstance;
    RECT rc = { 0, 0, 528, 512 };
    AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
    g_hWnd = CreateWindow(
        L"MainWindowClass",
        L"Concentration",
        WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        rc.right - rc.left,
        rc.bottom - rc.top,
        NULL,
        NULL,
        hInstance,
        NULL
        );
    if (!g_hWnd)
        return E_FAIL;

    #pragma endregion

    ShowWindow(g_hWnd, nCmdShow);

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Called every time the application receives a message
//--------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch(message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
        break;

    case WM_LBUTTONDOWN:
        MouseButtonDown(LOWORD(lParam), HIWORD(lParam), true);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}


//--------------------------------------------------------------------------------------
// Create Direct3D device and swap chain
//--------------------------------------------------------------------------------------
HRESULT InitDevice()
{
    HRESULT hr = S_OK;

    RECT rc;
    GetClientRect(g_hWnd, &rc);
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = ARRAYSIZE(driverTypes);

    #pragma region Feature Levels

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1,
    };
    UINT numFeatureLevels = ARRAYSIZE(featureLevels);

    #pragma endregion

    #pragma region Swap Chain Description

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = g_hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

    #pragma endregion

    #pragma region Create Device and Swap Chain

    for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
    {
        g_driverType = driverTypes[driverTypeIndex];
        hr = D3D11CreateDeviceAndSwapChain(
            NULL,
            g_driverType,
            NULL,
            createDeviceFlags,
            featureLevels,
            numFeatureLevels,
            D3D11_SDK_VERSION,
            &sd,
            &g_pSwapChain,
            &g_pd3dDevice,
            &g_featureLevel,
            &g_pd3dContext
            );
        if (SUCCEEDED(hr))
            break;
    }
    if (FAILED(hr))
        return hr;

    #pragma endregion

    #pragma region Create a Render Target View

    // Get the address of the back buffer
    ID3D11Texture2D* pBackBuffer = NULL;
    hr = g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
    if (FAILED(hr))
        return hr;

    // Use the back buffer address to create the render target
    hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
    pBackBuffer->Release();
    if (FAILED(hr))
        return hr;

    // Set the render target as the back buffer
    g_pd3dContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL);

    #pragma endregion

    // Setup the viewport
    CD3D11_VIEWPORT viewport(
        0.0f,
        0.0f,
        (FLOAT)width,
        (FLOAT)height,
        0.0f,
        1.0f
        );
    g_pd3dContext->RSSetViewports(1, &viewport);

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Render the frame
//--------------------------------------------------------------------------------------
void Draw()
{
    FLOAT colour[4] =
    {
        ColorF(ColorF::CornflowerBlue).r,
        ColorF(ColorF::CornflowerBlue).g,
        ColorF(ColorF::CornflowerBlue).b,
        1.0f
    };

    g_pd3dContext->ClearRenderTargetView(g_pRenderTargetView, colour);

    int tileWidth = 132;
    int tileHeight = 128;

    g_spriteBatch->Begin();
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            if (g_bTileStates[i][j] || ((i == g_ptTile1.x) && (j == g_ptTile1.y)) || ((i == g_ptTile2.x) && (j == g_ptTile2.y)))
            {
                g_spriteBatch->Draw(g_pTexture[g_iTiles[i][j]], XMFLOAT2((FLOAT)i * tileWidth, (FLOAT)j * tileHeight));
            }
            else
            {
                g_spriteBatch->Draw(g_pTexture[0], XMFLOAT2((FLOAT)i * tileWidth, (FLOAT)j * tileHeight));
            }
        }
    }
    g_spriteBatch->End();

    g_pSwapChain->Present(0, 0);
}


//--------------------------------------------------------------------------------------
// Load game resources
//--------------------------------------------------------------------------------------
void LoadContent()
{
    g_spriteBatch = unique_ptr<SpriteBatch>(new SpriteBatch(g_pd3dContext));

    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tileblank.bmp", NULL, &g_pTexture[0], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile1.bmp", NULL, &g_pTexture[1], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile2.bmp", NULL, &g_pTexture[2], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile3.bmp", NULL, &g_pTexture[3], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile4.bmp", NULL, &g_pTexture[4], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile5.bmp", NULL, &g_pTexture[5], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile6.bmp", NULL, &g_pTexture[6], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile7.bmp", NULL, &g_pTexture[7], NULL);
    CreateWICTextureFromFile(g_pd3dDevice, g_pd3dContext, L"tile8.bmp", NULL, &g_pTexture[8], NULL);
}


//--------------------------------------------------------------------------------------
// Do something when player left clicks
//--------------------------------------------------------------------------------------
void MouseButtonDown(int x, int y, bool left)
{
    int tileX = x / 132;
    int tileY = y / 128;

    if (!g_bTileStates[tileX][tileY])
    {
        if (g_ptTile1.x == -1)
        {
            g_ptTile1.x = tileX;
            g_ptTile1.y = tileY;
        }
        else if ((tileX != g_ptTile1.x) || (tileY != g_ptTile1.y))
        {
            if (g_ptTile2.x == -1)
            {
                g_iTries++;

                g_ptTile2.x = tileX;
                g_ptTile2.y = tileY;

                if (g_iTiles[g_ptTile1.x][g_ptTile1.y] == g_iTiles[g_ptTile2.x][g_ptTile2.y])
                {
                    g_bTileStates[g_ptTile1.x][g_ptTile1.y] = true;
                    g_bTileStates[g_ptTile2.x][g_ptTile2.y] = true;

                    g_ptTile1.x = g_ptTile1.y = g_ptTile2.x = g_ptTile2.y = -1;

                    if (++g_iMatches == 8)
                    {
                        TCHAR szText[64];
                        wsprintf(szText, L"You won in %d tries.", g_iTries);
                        MessageBox(g_hWnd, szText, L"Concentration", MB_OK);
                    }
                }
            }
            else
            {
                g_ptTile1.x = g_ptTile1.y = g_ptTile2.x = g_ptTile2.y = -1;
            }
        }

        InvalidateRect(g_hWnd, NULL, false);
    }
}


//--------------------------------------------------------------------------------------
// Clean up the objects we've created
//--------------------------------------------------------------------------------------
void CleanupDevice()
{
    if (g_pd3dContext) g_pd3dContext->ClearState();

    if (g_pRenderTargetView) g_pRenderTargetView->Release();
    if (g_pSwapChain) g_pSwapChain->Release();
    if (g_pd3dContext) g_pd3dContext->Release();
    if (g_pd3dDevice) g_pd3dDevice->Release();
}

I hope someone finds this code useful (although I doubt it). I'm playing around with SpriteFont at the moment and am working on making a cheap rip-off of the Game State Management sample from XNA. If I don't get banned, I'll post it when I'm done.

 

Sep 22, 2012 at 7:19 AM

how about metro app? can you write another one.