Now that we have covered the important parts of sprites and text, let's build an
actual display out of them. There are various techniques you will need to
know before you can actually build a good display, and we'll cover some of them in here.
The display we will build will have three parts. First will be a radar screen
(not a functioning one, because there aren't any enemies to show just yet).
Next there will be a health bar (manually controlled, as there aren't any enemies
to kill you just yet). Finally there will be a numerical ammo indicator (also
manually controlled, as there aren't any enemies to shoot just yet).
Because of the complex nature of DirectX, it is probably best to build a few functions
to get everything simplified. To do otherwise will make the code completely
unreadable, and that is not what we want in a tutorial, right?
I've written two functions here that will greatly cut down on the code. There
is one to load a texture file and one to render a portion of the file.
For loading we will need a two of parameters. We'll need a place to store
the texture and a filename.
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL,
texture);
return;
}
It's easy enough. It takes the texture location and the filename, loads the
file into the texture with the hot-pink background. It is set for alpha, so
it can be used with semi-transparency.
This one aint so easy.
There are more parameters. For each graphic drawn, we need to specify its
location in the texture, its size, and its location on the screen. We also
need to specify which texture we are using. This results in seven parameters!
Yikes!
Okay, let's cut it down a little. We'll use a RECT to simplify the location
in the texture and the size. Now we have a RECT, two integers, and a pointer
to a texture. Four parameters. Much better.
Wait, what about alpha? We'll add another integer for this. Five parameters.
Not too great, but there isn't much we can do about it, really. We could build
a complex class to take care of it, but you can do that on your own if you like.
Let's look at the function as is:
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, int x, int y, int a)
{
D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
d3dspt->Draw(texture, texcoords, ¢er, &position, D3DCOLOR_ARGB(a,
255, 255, 255));
return;
}
For the rest of this lesson, we will be using these functions so that we can focus
on building the display, not on managing needlessly large functions.
Let's start looking into the actual display itself. The first part we will look into is the radar. The radar will require three
separate sprites:

Image 5.1 - The Radar Sprites
The first sprite is the inside of the radar circle, and is drawn at 50% transparency.
It is made to fit exactly inside the white circle.
The second sprite is a small red dot, which is used to indicate the alleged enemies.
The third sprite is the circle, which is drawn opaque. Notice that there is
a dot in the middle, representing the player. The player is always in the center of the radar,
so we don't need to make an extra sprite for that.
Of course, each sprite is filled in by pink for color-keying.
In drawing the radar, we need to get the order of drawing exact. If we draw
it in the wrong order, some things will be covered by the wrong sprites, making
the image look incorrect. The order we want to draw our sprite in is first
the transparent background, second the enemy blip, third the white circle.
We will put our code into two functions. One to load the graphics, and one
to draw them. These functions we will call LoadDisplay() and DrawDisplay().
// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
void LoadDisplay()
{
LoadTexture(&DisplayTexture, L"DisplaySprites.png");
return;
}
void DrawDisplay()
{
RECT Part;
// DRAW THE RADAR
// display the backdrop
SetRect(&Part, 2, 14, 169, 181);
DrawTexture(DisplayTexture, Part, 10, 10, 127);
// display the enemy
SetRect(&Part, 341, 14, 344, 17);
DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
// display the border
SetRect(&Part, 171, 13, 340, 182);
DrawTexture(DisplayTexture, Part, 9, 9, 255);
return;
}
This code shows and displays all three parts on top of one another, just as they
should be...except for...
If at this point you tried changing the location of the enemy blip around, you will have noticed that the blip still
shows even when it is off the radar completely. The solution to this is to
only draw the blip if it is within a certain distance of the center of the radar.
If you don't recall how to find distances, here is the formula. For the distance
between Point 1 and Point 2:
Distance = √(x1 - x2)² + (y1 - y2)²
Here, x1 and y1 are the coordinates of the center of the radar, while x2 and y2
are the coordinates of the enemy blip. If you use this formula, you can determine
whether the distance is greater than the radius of the radar. If it is, then
you don't draw it, and if it isn't you do. Simple.
Here is the code you add to determine the distance:
// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
void LoadDisplay()
{
LoadTexture(&DisplayTexture, L"DisplaySprites.png");
return;
}
void DrawDisplay()
{
RECT Part;
// DRAW THE RADAR
// display the backdrop
SetRect(&Part, 2, 14, 169, 181);
DrawTexture(DisplayTexture, Part, 10, 10, 127);
// if the enemy is within 84 units of the player, display the enemy
if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 -
enemyY)) < 84)
{
SetRect(&Part, 341, 14, 344, 17);
DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
}
// display the border
SetRect(&Part, 171, 13, 340, 182);
DrawTexture(DisplayTexture, Part, 9, 9, 255);
return;
}
Now, when you run that, the radar only shows those enemies that are in range.
Of course, you'll have to get the blip moving around if you want to see it working.
Now let's go onto the next item in our series: the health bar. This item will
have only two graphics. Take a look at them here, then we'll go over
what they are:

Image 5.2 - The Health Bar Sprites
The first, and more obvious, sprite is the bar. The second, and less obvious
one, is the filler.
The task at hand is pretty simple. We draw the bar, then we draw repeated
instances of the filler. We keep drawing the filler
until we have draw enough
to indicate the level of health/mana/whatever.
The end result is this:

Image 5.3 - The Health Bar Pieced Together at Varying Percentages
Now let's look at our display code with the health bar included:
// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;
void LoadDisplay()
{
LoadTexture(&DisplayTexture, L"DisplaySprites.png");
return;
}
void DrawDisplay()
{
RECT Part;
// DRAW THE RADAR
// display the backdrop
SetRect(&Part, 2, 14, 169, 181);
DrawTexture(DisplayTexture, Part, 10, 10, 127);
// if the enemy is within 84 units of the player, display the enemy
if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92
- enemyY))
< 84)
{
SetRect(&Part, 341, 14, 344, 17);
DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
}
// display the border
SetRect(&Part, 171, 13, 340, 182);
DrawTexture(DisplayTexture, Part, 9, 9, 255);
// DRAW THE HEALTHBAR
// display the bar
SetRect(&Part, 1, 1, 505, 12);
DrawTexture(DisplayTexture, Part, 11, 456, 255);
// display the health "juice"
SetRect(&Part, 506, 1, 507, 12);
for(int index = 0; index < (health * 490 / maxhealth); index++)
DrawTexture(DisplayTexture, Part, index + 18, 456, 255);
return;
}
Finally, let's take a look at the ammo indicator. This indicator is numerical,
so we will be using a font. I will be using a boring, italic Arial for now,
but you are free to search for your own font that better suits the motif.
The ammo indicator has just two graphics:

Image 5.4 - The Ammo Indicator Sprites
These are the obvious parts: the opaque border and the transparent background.
It is the font that makes this one tricky. Let's study the code added.
// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;
int ammo = 10394;
LPD3DXFONT dxfont;
void LoadDisplay()
{
LoadTexture(&DisplayTexture, L"DisplaySprites.png");
D3DXCreateFont(d3ddev, 12, 0, FW_NORMAL, 1, false, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
L"Arial", &dxfont);
return;
}
void DrawDisplay()
{
RECT Part;
// DRAW THE RADAR
// display the backdrop
SetRect(&Part, 2, 14, 169, 181);
DrawTexture(DisplayTexture, Part, 10, 10, 127);
// if the enemy is within 84 units of the player, display the enemy
if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92
- enemyY))
< 84)
{
SetRect(&Part, 341, 14, 344, 17);
DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
}
// display the border
SetRect(&Part, 171, 13, 340, 182);
DrawTexture(DisplayTexture, Part, 9, 9, 255);
// DRAW THE HEALTHBAR
// display the bar
SetRect(&Part, 1, 1, 505, 12);
DrawTexture(DisplayTexture, Part, 11, 456, 255);
// display the health "juice"
SetRect(&Part, 506, 14, 507, 12);
for(int index = 0; index < (health * 490 / maxhealth); index++)
DrawTexture(DisplayTexture, Part, index + 18, 456, 255);
// DRAW THE AMMO INDICATOR
// display the backdrop
SetRect(&Part, 351, 14, 456, 40);
DrawTexture(DisplayTexture, Part, 530, 449, 127);
// display the border
SetRect(&Part, 351, 45, 457, 72);
DrawTexture(DisplayTexture, Part, 530, 449, 255);
// display the font
SetRect(&Part, 535, 453, 630, 470);
static char strAmmoText[10];
_itoa_s(ammo, strAmmoText, 10);
dxfont->DrawTextA(NULL,
(LPCSTR)&strAmmoText,
strlen((LPCSTR)
&strAmmoText),
&textbox,
DT_RIGHT,
D3DCOLOR_ARGB(255,
120, 120, 255));
return;
}
Easily the scariest part of this code is the string management where we convert the integer ammo to the string strAmmoText.
However, all that really happens here is
that the integer ammo is converted into strAmmoText using the _itoa_s() function,
and the address of that string is passed into the char* of the DrawTextA function.
Of course, strlen() is used to determine the length of the string, so we don't
get something like this 127□ÀÀÀÀÀÀ appearing on our screen when we only want 127.
The rest is just typical drawing from our sprite functions.
Now let's take the fragments we've gone over and put this whole program together.
[
Show Code]
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
LPD3DXSPRITE d3dspt; // the pointer to our Direct3D Sprite interface
LPD3DXFONT dxfont; // the pointer to the font object
float enemyX = 60.0f, enemyY = 60.0f; // the enemy position
int health = 300; // the player's current hitpoints
int maxhealth = 1000; // the player's max hitpoints
int ammo = 10394; // the player's current ammo
// sprite declarations
LPDIRECT3DTEXTURE9 DisplayTexture;
// the pointer to the texture
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
void load_display();
void draw_display();
void LoadTexture(LPDIRECT3DTEXTURE9*
texture, LPCTSTR filename);
void DrawTexture(LPDIRECT3DTEXTURE9 texture,
RECT texcoords, float x, float y, int a);
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
initD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
DWORD starting_point = GetTickCount();
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
render_frame();
// check the 'escape' key
if(KEY_DOWN(VK_ESCAPE))
PostMessage(hWnd, WM_DESTROY, 0, 0);
while ((GetTickCount() - starting_point) < 25);
}
// clean up DirectX and COM
cleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = FALSE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
// create a device class using this information and the info from
the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
D3DXCreateSprite(d3ddev, &d3dspt); // create the
Direct3D Sprite object
load_display();
return;
}
// this is the function used to render a single frame
void render_frame(void)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0,
0),
1.0f, 0);
d3ddev->BeginScene(); // begins the 3D scene
d3dspt->Begin(D3DXSPRITE_ALPHABLEND); // begin
sprite drawing with transparency
draw_display();
d3dspt->End(); // end sprite drawing
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL);
return;
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
DisplayTexture->Release();
d3ddev->Release();
d3d->Release();
return;
}
// this loads the display graphics and font
void load_display()
{
LoadTexture(&DisplayTexture, L"DisplaySprites.png");
D3DXCreateFont(d3ddev, 20, 0, FW_BOLD, 1, false, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
L"Arial", &dxfont);
return;
}
// this draws the display
void draw_display()
{
RECT Part;
// DRAW THE RADAR
// display the backdrop
SetRect(&Part, 2, 14, 169, 181);
DrawTexture(DisplayTexture, Part, 10, 10, 127);
// if the enemy is within 84 units of the player, display the enemy
if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92
- enemyY)) < 84)
{
SetRect(&Part, 341, 14, 344, 17);
DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
}
// display the border
SetRect(&Part, 171, 13, 340, 182);
DrawTexture(DisplayTexture, Part, 9, 9, 255);
// DRAW THE HEALTHBAR
// display the bar
SetRect(&Part, 1, 1, 505, 12);
DrawTexture(DisplayTexture, Part, 11, 456, 255);
// display the health "juice"
SetRect(&Part, 506, 1, 507, 12);
for(int index = 0; index < (health * 490 / maxhealth); index++)
DrawTexture(DisplayTexture, Part, index + 18, 456, 255);
// DRAW THE AMMO INDICATOR
// display the backdrop
SetRect(&Part, 351, 14, 456, 40);
DrawTexture(DisplayTexture, Part, 530, 449, 127);
// display the border
SetRect(&Part, 351, 45, 457, 72);
DrawTexture(DisplayTexture, Part, 530, 449, 255);
// display the font
SetRect(&Part, 535, 453, 630, 470);
static char strAmmoText[10];
_itoa_s(ammo, strAmmoText, 10);
dxfont->DrawTextA(NULL,
(LPCSTR)&strAmmoText,
strlen((LPCSTR)
&strAmmoText),
&Part,
DT_RIGHT,
D3DCOLOR_ARGB(255,
120, 120, 255));
return;
}
// this loads a texture from a file
void LoadTexture(LPDIRECT3DTEXTURE9*
texture, LPCTSTR filename)
{
D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);
return;
}
// this draws a portion of the specified texture
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a)
{
D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
d3dspt->Draw(texture, &texcoords, ¢er, &position,
D3DCOLOR_ARGB(a,255, 255, 255));
return;
}
This program runs on the file "DisplaySprites.png". It contains all the sprites that
make up the display panel.
If you use this image and run the code, you will get something like this:

Image 5.5 - The enemy is coming, and you're low on health. Good thing there's LOTS of ammo!
This tutorial has covered the basics of building a game display. Every game
display is, of course, unique, and each is quite different from others. The
difficult task is to figure out how to create the display that will best fit your
game. And so I challenge you with these tasks:
1. Design a rough layout your own game display.
2. Create the graphics for your display. Make them look cool!
3. Write a program to show and manipulate your display.
Next Tutorial: DirectInput
GO! GO! GO!
© 2006-2009 DirectXTutorial.com. All Rights Reserved.