이 장을 끝으로 이 부분은 끝나네. DX에 대해서 더 알고 싶다면 어떤 것을 더 읽어보아야 할까?
이 장에서 만들어 볼것은 우선 레이더 스크린과 체력바, 그리고 총알 숫자 표시다.
1. A Too-Simple Wrapper for Sprites처음으로
Wrapper라는 단어가 나온듯 하다. 원래 부터 복잡한 DX의 속성상, 모든것을 단순화 시켜주는 함수가 있다면 좋을 것이다.
이 튜토리얼에서는 두개의 함수에 대해서 랩 함수를 만드는데 하나는 텍스쳐 파일 로딩 ㅎ마수 다른 하나는 파일의 일부만 렌더링 하는 함수다.
고로 의역하자면 wrapper 함수란 복잡한 인자를 가지는 함수를 간단하게 표현하는 둘러싸기 함수다.
1-1. LoadTexture()
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;
}
복잡했던 텍스쳐 로딩 함수를 단 두인자로 압축해 버렸다. 특이점은 color key가 핫 핑크로 설정된점, 그리고 alpha 채널이 포함된점이다.
1-2. DrawTexture()이 놈은 쉽지 않다는 구나. 여기는 더 많은 인자가 필요로 하다. 각각의 그래픽이 그려지기 위해서는 텍스쳐의 위치 사이즈 스크린에서의 위치등을 정해 줘야 한다. 게다가 어떤 텍스쳐를 사용할지도 지정해야 한다. 벌써 7개의 인자가 필요하다.
일단 RECT 구조체를 사용해서 텍스쳐의 사이즈와 위치를 나타내자.
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;
}
2. Constructing the Radar
Image 5.1 - The Radar Sprites
위의 그림이 레이더를 구성하는 요소들이다. 중요한건 각각의 스프라이트는 핑크로 배경색이 채워져 있어 컬러키를 이용해서 필요한 부분만 렌더링이 가능하다.
2-1. The Code레이더를 그리기 위해서는 그리는 순서를 정확하게 할 필요가 있다. 왜냐구? 만약 순서가 틀리면 (빨간점이 가장 나중에 그려지지 않으면) 잘못된 결과가 나온다. 그래서 그리는 순서는 1. 투명한 그리드 2. 빨간점. 3. 테두리 이다.
// 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;
}
여기서 보면 두개의 함수로 하는데 LoadDisplay() 함수와 DrawDisplay() 함수다.
우선 LoadDisplay() 함수로 텍스쳐를 로딩해 놓는다. 이 텍스쳐에는 필요한 모든 이미지가 들어가 있고 배경은 핑크색이다. 그런다음 SetRect 함수를 이용해서 원하고자 하는 영역만 설정한다. (그리고자 하는 영역만 설정, 텍스쳐 안에서) 그런다음에 적당한 좌표에다가 그려준다.
2-2. Fixing the Radar이것의 문제점이 하나 있으니 바로 적군을 표시하는 빨간점이 레이다 영역 밖에서도 표시된다는 점이다. 고로 이걸 해결하기 위해서는 빨간점을 특정한 영역 내에 있을때만 표시하도록 해야 한다.
점 1과 점 2사이의 거리를 측정하는 방법은 다음과 같지요.
Distance = √(x1 - x2)² + (y1 - y2)²
이걸 이용해서 빨간점과 중심과의 거리를 잰다. 만약 넘어가면 그리지 않는다. 고로 다음과 같이 코드가 바뀐다.
// 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;
}
3. Constructing the Health Bar이제 피가 남아있는 걸 보여주는 바를 만들 차례인데, 이 아이템은 딱 두개의 그래픽을 가진다.
Image 5.2 - The Health Bar Sprites
그림이 조금 잘 안보이지만 윗 그림의 제일 오른쪽 편에 보면 조그마한 막대로 하나 있다.
방법은 간단하다. 우선 바를 그리고 %만큼 필터를 채워나가면 된다. 여기서 필터란 윗 그림에서 잘 안보이는 조그만 막대를 말한다.
Image 5.3 - The Health Bar Pieced Together at Varying Percentages
// 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;
}
흠. 복잡해 보이지만 (상수가 많아서) 그닥 어려운 코드는 아니다. 다만 정확한 텍스쳐 내부의 좌표를 꿰고 있어야 한다는 단점이 있네... 왜 텍스쳐를 따로 쓰지 않을까? :)
4. Constructing the Ammo Indicator
Image 5.4 - The Ammo Indicator Sprites
위의 두 그림을 이용할 텐데 오른쪽 그림 하나만 있어도 될거 같은디?
// 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;
}
위에서 보면 _itoa_s 함수로 정수를 문자열로 바꾼다. 근데 저기 계속 해서 등장하는 LPCSTR등은 참으로 짜증난다.
5. The Finished Program
// 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;
}
Image 5.5 - The enemy is coming, and you're low on health. Good thing there's LOTS of ammo!