Experiments In Game Programming
Main
 Home
XNA - C#
 Coming Soon!
 Level Editing - GTK
DirectX 9 - C++
 Downloads
 Disclaimer
 Introduction
Part 1 - DirectX
 1 - Breakout
 2 - Create DX
 3 - 2d Images
 4 - 3d Models
 5 - Cameras & Lights
 6 - Animation Timing
 7 - Keyboard/Mouse
 8 - Sound
Part 2 - Breakout
 1 - Art and Sounds
 2 - The Menu
 3 - Starting Breakout
 4 - The Level
 5 - The Paddle
 6 - The Ball
 7 - Finishing Touches

Untitled Document

Chapter 2

 

Getting Started

 

 

In this chapter we will start our project, create a window, initialize DirectX in our window, and then print some text using DirectX. As we do this, we will be creating a framework in which to run our game. I'm going to start slow and cover a little of the visual studio usage and C++ class structure, so if you are unfamiliar with either one at least you will not be totally lost.

Topics Covered in this Chapter:
  • Creating an Empty Project
  • Creating a window
  • Initializing DirectX
  • Creating a main for our game
  • Printing text

The first thing we need to do to get started is create a new project in Visual Studio. 'New project' is on the file menu under new. In the dialog box that comes up select Visual C++ Projects, and then Win32 Application. You also require a title for your project. I've gone ahead and labeled mine breakout, because that is what we will be programming. Once the project has been selected and named, click next to continue.

On the next screen click application settings and under additional options check 'Empty project'. Click finish, and you now have an empty C++ project. On the right of the screen there should be a side bar currently labeled Properties. At the bottom of the box are 3 tabs, Properties, Solution Explorer, and Class View - Select Solution Explorer. The Solution Explorer shows you all of the Source and Header files your project contains, as well as other resources. Right now our project does not contain anything, so the first thing we want to do is create the entry point to our application. Add a new source file to the project by right clicking on the Source Files folder, and selecting 'add new item' from the add menu. Select new C++ Source File, and name it 'winmain'. This file is going to be the entry point to our application, it will setup our window and start our game. Lets take a look at the winmain.cpp in the chapter 2 code.

This is the top of the winmain. It includes the basic windows functions, and creates an instance handle for the application and for the window. This, and the code that will follow, is fairly standard windows initialization code. In fact this is just a modified (stripped down) version of what you get if you don't tell Visual Studio you want an empty project.

#include <windows.h>

HINSTANCE hInst; // holds the instance for this app
HWND wndHandle; // global window handle

bool initWindow(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// Link in the DirectX libraries required to compile
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"dxerr9.lib")
#pragma comment(lib,"dinput8.lib")
#pragma comment(lib,"d3dx9.lib")

This is the WinMain function. If you are familiar with console application programming, you know the entry point for your program is main(). This is the windows equivalent; it is where our program starts, it contains the main message loop, and it will be where our program ends. Notice the comment before the message loop starts, as where we will be creating our main game object, and after the message loop ends as where we will be destroying our game object. When writing a windows application the message loop handles messages passed to the window, such as menu clicks, etc. It is important to keep the message loop flowing, because if your program stops handling messages, windows will report your program as not responding. To keep it simple, our message loop only cares about the quit message (WM_QUIT) and when it has no other messages pending, it will pass control over to our game.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
     // call our function to init and create our window
     if (!initWindow(hInstance))
     {
         MessageBox(NULL, "Unable to create window", "ERROR", MB_OK);
         return false;
     }
     // This is where we will Create our main game object

     // Main message loop:
     // Enter the message loop
     MSG msg;
     ZeroMemory( &msg, sizeof(msg) );
     while( msg.message!=WM_QUIT )
     {
         // check for messages
         if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
         {
             TranslateMessage( &msg );
             DispatchMessage( &msg );
         }
         // if there are no messages waiting update the game
         else
         {
             //update call here
         }
     }

     //this is where we will delete the game object
    

     return (int) msg.wParam;
}

The next function is initWindow. initWindow registers a window class, and then creates our window.
bool initWindow(HINSTANCE hInstance)
{
     WNDCLASSEX wcex;
     wcex.cbSize = sizeof(WNDCLASSEX);
     wcex.style = CS_HREDRAW | CS_VREDRAW;
     wcex.lpfnWndProc = (WNDPROC)WndProc;
     wcex.cbClsExtra = 0;
     wcex.cbWndExtra = 0;
     wcex.hInstance = hInstance;
     wcex.hIcon = 0;
     wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
     wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
     wcex.lpszMenuName = NULL;
     wcex.lpszClassName = "Breakout";
     wcex.hIconSm = 0;
     RegisterClassEx(&wcex);

     ULONG window_width=800;
     ULONG window_height=600;

     DWORD style=WS_POPUP|WS_VISIBLE;

     style = WS_OVERLAPPEDWINDOW;

     wndHandle = CreateWindow("Breakout",
     "Breakout",
     style,
     CW_USEDEFAULT,
     CW_USEDEFAULT,
     window_width,
     window_height,
     NULL,
     NULL,
     hInstance,
     NULL);

    
     if (!wndHandle){
         return false;
     }
    
     // Set the global instance handle
     hInst = hInstance;

     ShowWindow(wndHandle, SW_SHOW);
     UpdateWindow(wndHandle);

     return true;
}

The last function in the winmain class is WndProc. It's the windows procedure, and the last function we require for a functioning windows program. The windows procedure handles events from the system that relate to our application. However we aren't going to need to worry about anything other than quit.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
         case WM_DESTROY:
         PostQuitMessage(0);
         break;
     }
     return DefWindowProc(hWnd, message, wParam, lParam);
}

That concludes the winmain file, you should now be able to compile and run your project. It will produce an empty window.

Now that we have a window, we need to initialize DirectX to use our window. Initializing DirectX to use our display, requires us to instantiate 2 objects, the Direct3D object, and the Direct3D Device object. The Direct3D object takes one parameter (D3D_SDK_VERSION), this gives you access to Direct3D with the current version of the SDK you have installed. My understanding is you could pass previous versions of DirectX 9 if you needed to use them for some reason. The Direct3D Device object handles communication with your video card. Unlike the Direct3D object, it is not a simple object to create. Let's take a look at the dxMgr class, and the creation of these two objects.

The dxMgr class, short for DirectX Manager class, consists of 2 files, the header dxMgr.h and the main C++ file dxMgr.cpp. Now I am assuming you know some C++, but I'll go over this anyway just in case. The header file contains the class definition - The name of your class and its functions and variables. Where the main C++ file contains the actual code for the functions. Lets start by taking a look at dxMgr.h from the chapter 2 code.
The following is the header for the dxMgr class. It shows we are going to have 6 functions, and 2 private variables. As we go through the main file dxMgr.cpp you will see the header was just a forward declaration of what was going to be in the main file.

#include <d3d9.h>


class dxMgr
{
     public:
     dxMgr(void); //Constructor
     ~dxMgr(void); //deconstructor

     bool init(HWND hwnd, int width, int height, bool fullscreen); //initializes      our interface
     void beginRender(void); //starts the scene render
     void endRender(void); //ends the scene render and presents to the screen
     LPDIRECT3DDEVICE9 getD3DDevice(); //returns the device

     private:

     LPDIRECT3D9 direct3d; //This is the Direct3D object
     LPDIRECT3DDEVICE9 direct3dDevice; //This is our device object

};

At the top of the dxMgr.cpp we include the header file.
#include ".\dxMgr.h"

Next we have the constructor. The constructor is the first function called when you create an instance of an object, often it is used to zero out memory that is going to be used. In this case, we are nulling our Direct3D object and our Device object.
dxMgr::dxMgr(void)
{
     direct3d = NULL; //The Direct3D Object
     direct3dDevice = NULL; //The Device object
}

Next is the deconstructor. The deconstructor called when an object is deleted, it is used to free up any memory used by an object. In this case we are freeing our Direct3D object and our Device object.
dxMgr::~dxMgr(void)
{
     if( direct3dDevice != NULL)
     {
         direct3dDevice->Release();
         direct3dDevice = NULL;
     }
     if( direct3d != NULL)
     {
         direct3d->Release();
         direct3d = NULL;
     }
}

Now on to the more complicated part, actually initializing DirectX. The init function takes 4 parameters: hwnd - our window, width - the width of the display we are going to create, height - the height of the display, and fullscreen - true or false, if we want the display to be fullscreen or not. This is my simplified init, as you will see there is a lot more we could have put into the init, but for now that's all we are going to worry about. The width and height are the width and height of the backbuffer, in a window you can set this to any size you like, however you will get the best picture with a buffer that is the size of your window. If fullscreen is true, the DirectX device will take over the display, in this case the width and height must be a valid supported resolution. Fullscreen also requires the BackBufferFormat to be specifed. The BackBufferFormat specifies how color information is going to be stored, common formats are 16 and 32 bit. I have coded it to D3DFMT_R5G6B5, which is a 16 bit format, with 5 Red bits, 6 Green bits and 5 Blue bits. If you would like to have a higher color format, you can use D3DFMT_X8R8G8B8, which is a 32 bit format, with 8 bits for each color. Keep in mind, going from a 16 bit format to a 32 format means your buffers are going to take twice as much memory; on older graphics cards this can mean quite a hit in performance.
bool dxMgr::init(HWND hwnd, int width, int height, bool fullscreen)
{
     HRESULT hr;
     direct3d = Direct3DCreate9( D3D_SDK_VERSION );
     if(direct3d == NULL)
     {
         return false;
     }

     //Decide if windowed or fullscreen
     bool windowed;
     if (fullscreen){
         windowed = false;
     }

     //Setup the present parameters
     D3DPRESENT_PARAMETERS d3dpp;
     ZeroMemory( &d3dpp, sizeof(d3dpp) );
     d3dpp.Windowed = windowed; //Windowed or Fullscreen
     d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; //discards the previous frames
     d3dpp.BackBufferFormat = D3DFMT_R5G6B5; //The display format
     d3dpp.BackBufferCount = 1; //Number of back buffers
     d3dpp.BackBufferHeight = height; //height of the backbuffer
     d3dpp.BackBufferWidth = width; //width of the backbuffer
     d3dpp.hDeviceWindow = hwnd; //handle to our window
     d3dpp.AutoDepthStencilFormat = D3DFMT_D16; //The stencil format
     d3dpp.EnableAutoDepthStencil = TRUE;
     d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
     d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;

    
     //Create the Video Device
     hr = direct3d->CreateDevice( D3DADAPTER_DEFAULT, //The default adapter is the      primary display adapter
     D3DDEVTYPE_HAL, //the HAL (hardware accelerated layer) uses your 3d accelerator      card
     hwnd,
     D3DCREATE_HARDWARE_VERTEXPROCESSING, //sets the graphic card to do the hardware      vertexprocessing
     &d3dpp, //The present parameters we created above
     &direct3dDevice
     );
     if( FAILED(hr)){
         return false;
     }

     direct3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE); //this normalizes      the normal values (this is important for how lighting effects your models)

     return true;
}

The functions that follow are functions that let us use the device we have created. The next function is beginRender, its purpose is to tell the Device that we are starting rendering. It starts by making sure we have a device to render to. Then we clear the device, and tell the device to begin the scene.
void dxMgr::beginRender()
{
     if( NULL == direct3dDevice )
     return;
     // Clear the backbuffer to a black color
     direct3dDevice->Clear( 0, //number of rectangles to clear, 0 for all
     NULL, //rects to be cleared, NULL for entire buffer
     D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, //the buffers to clear
     D3DCOLOR_XRGB(55,55,55), //the Color to fill the buffer with
     1.0f, //depth for the z-buffer to clear
     0 //stencil buffer clear
     );

     direct3dDevice->BeginScene();

}

Logically following beginRender is endRender. Endrender is fairly simple, we tell the device we have finished rendering by calling EndScene(), and then we present the buffer we created during rendering to the screen with Present. Present takes a number of parameters for setting how much of the buffer we wish to present and where we wish to present it on the screen. In this function Present( NULL, NULL, NULL, NULL ) just presents the whole buffer to the screen.
void dxMgr::endRender(void)
{
     direct3dDevice->EndScene();
     direct3dDevice->Present( NULL, NULL, NULL, NULL );
}

There is one last function in the dxMgr class, and that is the getD3DDevice function. getD3DDevice simply returns the device we created. As we go along you will see that many of the useful built-in DirectX functions need the device. So we defiantly need to be able to access it.
LPDIRECT3DDEVICE9 dxMgr::getD3DDevice()
{
     return direct3dDevice;
}

That concludes the dxMgr class. If you have been following the code and typing it in as you go, you should have added the header, dxMgr.h, and the main file, dxMgr.cpp, to your project. Although we haven't included or called our dxMgr yet compiling your project is still a good idea to see if we have made any mistakes. If your project compiles and runs we know we at least haven't made any syntax errors in our dxMgr.

Now that we have a Window and a DirectX Manager we are going to tie them together with a class called GameMain. In the same way that the winmain handles setting up your window and looping through the windows messages, the GameMain will handle setting up our window to be a game window (by initializing DirectX), and will handle our game update and render loop. Let's start by looking at the GameMain header file. Notice there are 2 class definitions, the top one, class dxMgr, is a forward declaration for the dxMgr class, it lets the compiler know we are going to be using a dxMgr object somewhere in the header. We need to do this if we want to declare a variable of type dxMgr, or if we want to pass a dxMgr as a parameter, or have it as a return type. The second class definition is the GameMain, and it contains the function forward definitions and variables as we would expect.

class dxMgr;


class GameMain
{
     public:
     GameMain(void);
     ~GameMain(void);

     bool init(HWND wndHandle);
     void update(void);

     private:

     dxMgr* dxManager;
};


Next on to the main file for GameMain. At the top of GameMain are the includes we need, the DirectX manager include, and the GameMain header file.
#include "dxMgr.h" //the directx manager class
#include ".\GameMain.h" //the GameMain header

The first function in GameMain that has anything in it is init, (both the constructor and deconstructor are currently empty), init only takes one argument, the handle to our window. Currently the only thing init does is create a new instance of our DirectX manager, and then initialize it. Looking at the init call to the DirectX manager we see we are calling it with the window handle passed to the GameMain init, a buffer width and height of 800x600 respectively and in windowed mode (fullscreen is false).
bool GameMain::init(HWND wndHandle)
{
     dxManager = new dxMgr(); //create our direct Manager
     dxManager->init(wndHandle, 800, 600, false); //Initialize our DirectX Manager
     if (!dxManager){
         return false;
     }

     return true;
}

The next, and last function in GameMain is update. The update function takes no parameters, and will be called by our winmain whenever there are no messages pending. The update functions purpose is to call the update and render functions in game objects. Currently we have no game objects, so we have an empty update function with comments reminding us where we need to make our calls.
void GameMain::update(void)
{
     //call our update function
     // prepare our render device for rendering to
     dxManager->beginRender();
     //game render calls go here

     //end the render and present the buffer to the screen
     dxManager->endRender();

}

That completes our GameMain framework, now we need to call it from our winmain. Going back to the winmain.cpp file we need to included the GameMain class, create and initialize a GameMain instance, and call update in the appropriate location. At the top of winmain, we need to included the GameMain header.

#include <windows.h>
#include "GameMain.h"

Next we need to create our object, and initialize it. Once it is initialized, we place an update call into the no message pending part of the message loop. Lastly, we add a delete call to free up the memory our object was using when the message loop is broken with an exit message. (Changes are in Bold)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
     // call our function to init and create our window
     if (!initWindow(hInstance))
     {
         MessageBox(NULL, "Unable to create window", "ERROR", MB_OK);
         return false;
     }
     // Create the game main
     GameMain *game = new GameMain();


     // Initialize the game object
     if (game->init(wndHandle) == false){
         return 0;
     }


     // Main message loop:
     // Enter the message loop
     MSG msg;
     ZeroMemory( &msg, sizeof(msg) );
     while( msg.message!=WM_QUIT )
     {
         // check for messages
         if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
         {
             TranslateMessage( &msg );
             DispatchMessage( &msg );
         }
         // if there are no messages waiting update the game
         else
         {
             game->update();
         }
     }

     // Delete the game object
     if (game){
         delete game;
     }


     return (int) msg.wParam;
}

Now that we have added the GameMain object and needed calls to winmain we are ready to test our project again. Compile and run your project. If you have inputted everything correctly you will be presented with an empty window with a dark gray background. You have now created a window and initialized Direct3d in it.

Alright, so now we have a DirectX window, and its all ready to go, but right now there is nothing in it. We are going to change that by drawing some text to it. There is a lot more to drawing text than you might think. So we are going to create a text class to handle drawing text. The class I created is called dxText, lets start by looking at its header. Note there are two new includes, d3dx9tex.h, which is needed to use the directx text functions, and string, which is needed to use the standard C++ string object. In the private variables we create an ID3DXFont object to be our font.
#include <d3d9.h>
#include <d3dx9tex.h> //needed for text
#include <string> //need for using std:string
class dxText
{
     public:
     dxText(void); //Constructor
     ~dxText(void); //deconstructor

     bool init(DWORD size, LPDIRECT3DDEVICE9 device); //initializes our interface
     void drawText(std::string text, int x, int y, int width, int height); //draw      a text string

    
     private:
     ID3DXFont *g_font; //font
};

The first function in the main file for the dxText class we are going to look at is init.

The init function currently only takes 2 arguments, the size of the font, and the Direct3d Device. Inits purpose is to create a font to use for rendering text. Looking over the CreateFont function you can see it would not be hard to setup the init to also take parameters for bold or italic, or the particular font you wished to use. This text class will mainly be used for debugging, so we won't worry about setting up nice looking fonts quite yet.
bool dxText::init(DWORD size, LPDIRECT3DDEVICE9 device)
{
     // Create a font
     D3DXCreateFont(device, //D3D Device
     size, //Font height
     0, //Font width
     FW_NORMAL, //Font Weight
     1, //MipLevels
     false, //Italic
     DEFAULT_CHARSET, //CharSet
     OUT_DEFAULT_PRECIS, //OutputPrecision
     ANTIALIASED_QUALITY, //Quality
     DEFAULT_PITCH|FF_DONTCARE,//PitchAndFamily
     "Arial", //pFacename,
     &g_font); //ppFont

     return true;
}

Once we have created a font, we need a function to draw our text. The drawText function draws the text we give it at the location we specify. It takes 5 parameters: the text we wish to output, the starting coordinates x and y, and the width and height of the rectangle which we are drawing the text. The font_rect is a rectangle that specifies where we want the text to be clipped. When we specify a width and height we generally want to make it big enough that our text doesn't get clipped. SetRect positions our font_rect on the screen, and DrawText does just what you would think, and actually draws the text to the buffer.
void dxText::drawText(std::string text, int x, int y, int width, int height){
RECT font_rect = {0,0,width,height}; //sets the size of our font surface      rect

     //set the destination for our text, by moving the font rect.
     SetRect(&font_rect, //our font rect
     x, //Left
     y, //Top
     width, //width
     height //height
     );

     //draw our text
     g_font->DrawText(NULL, //pSprite
     text.c_str(), //pString
     -1, //Count
     &font_rect, //pRect
     DT_LEFT|DT_NOCLIP,//Format,
     0xFFFFFFFF); //Color (white)

}

That completes our dxText class. The last thing we are going to do in this chapter is add test our text class by calling it from our GameMain and drawing some text. In order to use it in our GameMain we need to first include it, create a variable to instantiate it to, and then call it in our render loop with some text to draw. As I did above, I will mark the additions in bold. Starting with the GameMain header, we include dxText.h, and declare a variable to hold our object.

class dxMgr;
class dxText;


class GameMain
{
     public:
     GameMain(void);
     ~GameMain(void);

     bool init(HWND wndHandle);
     void update(void);

     private:

     dxMgr* dxManager;
     dxText* textManager;
};

In the main file, we create and initialize the text class in GameMain's init function
bool GameMain::init(HWND wndHandle)
{
     dxManager = new dxMgr(); //create our direct Manager
     dxManager->init(wndHandle, 800, 600, false); //Initialize our DirectX Manager
     if (!dxManager){
         return false;
     }

     textManager = new dxText(); //create our text Manager
     textManager->init(32,dxManager->getD3DDevice()); //initialize our text      Manager
     if (!textManager){
         return false;
     }


    
     return true;
}


Lastly, we add a call to render some text in GameMain's update function .
void GameMain::update(void)
{
     //call our update function
     // begin rendering
     dxManager->beginRender();
     //game render calls go here
     textManager->drawText("Hello World", 30, 30, 300, 300);

     //end render
     dxManager->endRender();

}

That's everything, compile and run your project. Your previously blank window should now be graced with the words "Hello World".



Summing Up - Chapter 2

In this chapter we have Created our window, and three useful classes - the DirectX Manager, the GameMain framework, and the Text Manager. Before moving on, you might want to play with some of the settings we called the classes with. Run it fullscreen instead of in a window, or adjust the size of your window and buffer. When we finish the project we may wish to add the option of selecting windowed or fullscreen think about how we might do that. An important note about fullscreen however is it makes debugging with breakpoints awkward because visual studio doesen't pull itself to the front, so you have to alt-tab between them which can be quite slow.

 

 


 ©2008 David Whittaker