Выбрать главу

 HFONT hFont;

 m_pD3DDevice = pD3DDevice;

 int nWeight = FW_NORMAL;

 DWORD dwItalic = 0;

 DWORD dwUnderlined = 0;

 if (fBold) {

  nWeight = FW_BOLD;

 }

 if (fItalic) {

  dwItalic = 1;

 }

 if (fUnderlined) {

  dwUnderlined = 1;

 }

 hFont = CreateFont(nHeight, 0, 0, 0, nWeight, dwItalic, dwUnderlined, 0, ANSI_CHARSET, 0, 0, 0, 0, pFontFace);

 D3DXCreateFont(m_pD3DDevice, hFont, &m_pFont);

 LogInfo("<li>Font loaded OK");

}

CFont::~CFont() {

 SafeRelease(m_pFont);

 LogInfo("<li>Font destroyed OK");

}

void CFont::DrawText(LPSTR pText, int x, int y, D3DCOLOR rgbFontColour) {

 RECT Rect;

 Rect.left = x;

 Rect.top = y;

 Rect.right = 0;

 Rect.bottom = 0;

 m_pFont->Begin();

 m_pFont->DrawTextA(pText, –1, &Rect, DT_CALCRECT, 0); //Calculate the size of the rect needed

 m_pFont->DrawTextA(pText, –1, &Rect, DT_LEFT, rgbFontColour); //Draw the text

 m_pFont->End();

}

In the constructor of CFont, we use the CreateFont function to get a handle to a new font (HFONT). Now, to use this font in your DirectX application you need to create a font object for the device. To do this, we use the D3DXCreateFont function, passing in the device pointer and font handle. We then store the resulting font pointer (LPD3DXFONT) as a member variable of the CFont class.

In the destructor we simply release the stored font object.

The final function, DrawText, renders the text. We pass in the text to render, the x and y position of the upper left corner (in screen coordinates) and the colour of the text. We use two calls to the DrawTextA method. The first calculates the dimensions of the bounding rectangle of the text and the second draws the text inside the rectangle. The text is left aligned inside the rectangle.

To use the CFont class we use the following code to create a CFont pointer:

m_pFont = new CFont(m_pD3DDevice, "Verdana", 12, false, false, false);

Once we have a pointer to the CFont object we can start drawing text. So, we have a new CGame method called RenderText which is called from inside the main Render method.

void CGame::RenderText() {

 //Draw some text at the top of the screen showing stats

 char buffer[255];

 DWORD dwDuration = (timeGetTime() – m_dwStartTime) / 1000;

 if (dwDuration > 0) {

  sprintf(buffer, "Duration: %d seconds. Frames: %d. FPS: %d.", dwDuration, m_dwFrames, (m_dwFrames / dwDuration));

 } else {

  sprintf(buffer, "Calculating…");

 }

 m_pFont->DrawText(buffer, 0, 0, D3DCOLOR_XRGB(255, 255, 255));

}

So for every frame, the RenderText method is called. RenderText simply displays the length of time that the application has been running (in seconds), the number of frames so far and the average FPS count.

Steps for 2D in 3D Rendering

Now, to create 2D graphics is really pretty simple. All we need to do, is the following steps for each frame:

· Setup camera for 3D as usual

· Enable the z-buffer and lighting

· Render 3D objects as usual

· Setup camera for 2D

· Disable the z-buffer and lighting

· Render 2D objects

2D Camera

To setup the camera for 2D objects we need to change the projection matrix from a perspective one used in 3D to an orthogonal projection. When using a perspective projection, objects that are further away from the camera are smaller than objects that are closer to the camera. When using an orthogonal projection, an object will be the same size no matter how far it is from the camera. For example, when you use your 3D modelling program, you normally get three orthogonal projections (top, side and front) and a perspective projection (3D). The code for setting the camera for 2D is shown below:

void CGame::Setup2DCamera() {

 D3DXMATRIX matOrtho;

 D3DXMATRIX matIdentity;

 //Setup the orthogonal projection matrix and the default world/view matrix

 D3DXMatrixOrthoLH(&matOrtho, (float)m_nScreenWidth, (float)m_nScreenHeight, 0.0f, 1.0f);

 D3DXMatrixIdentity(&matIdentity);

 m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &matOrtho);

 m_pD3DDevice->SetTransform(D3DTS_WORLD, &matIdentity);

 m_pD3DDevice->SetTransform(D3DTS_VIEW, &matIdentity);

 //Make sure that the z-buffer and lighting are disabled

 m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);

 m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

}

In addition to changing the projection matrix, we also set the world and view matrices to an identity matrix and disable the z-buffer and lighting. We disable the z-buffer so that everything that is rendered from then on will appear on top of objects that have already been rendered (3D objects).

2D Objects

A 2D object is really two textured triangles in a triangle strip that form a rectangle. The z value for each of the vertices is the same so that the 2D object faces the camera flat. I have created a class called CPanel which we will use as our 2D object.

CPanel is very similar to the objects we have already created. It has a vertex buffer which contains four vertices (one for each corner of the panel), and the panel is centred about the origin. The code for CPanel is shown below.

CPaneclass="underline" :CPanel(LPDIRECT3DDEVICE8 pD3DDevice, int nWidth, int nHeight, int nScreenWidth, int nScreenHeight, DWORD dwColour) {

 m_pD3DDevice = pD3DDevice;

 m_pVertexBuffer = NULL;

 m_pTexture = NULL;

 m_nWidth = nWidth;

 m_nHeight = nHeight;

 m_nScreenWidth = nScreenWidth;

 m_nScreenHeight = nScreenHeight;

 m_dwColour = dwColour;

 //Initialize Vertex Buffer

 if (CreateVertexBuffer()) {

  if (UpdateVertices()) {

   LogInfo("<li>Panel created OK");

   return;

  }

 }

 LogError("<li>Panel failed to create");

}

CPaneclass="underline" :~CPanel() {

 SafeRelease(m_pTexture);

 SafeRelease(m_pVertexBuffer);

 LogInfo("<li>Panel destroyed OK");

}

bool CPaneclass="underline" :CreateVertexBuffer() {

 //Create the vertex buffer from our device.

 if (FAILED(m_pD3DDevice->CreateVertexBuffer(4 * sizeof(PANEL_CUSTOMVERTEX), 0, PANEL_D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVertexBuffer))) {

  LogError("<li>CPaneclass="underline" Unable to create vertex buffer.");