Direct3D轮回:文字显示及FPS效率统计

组成一个完整游戏的元素大体来说其实只有两种:图形和字体。

前几节涉及的内容大都为图形元素,本节我们来看如何在Direct3D中实现简单的字体绘制~

Direct3D中的文字绘制主要依赖于ID3DXFont接口对象。我们事先构造一个D3DXFONT_DESC结构体,详细描述所要生成字体的信息,而后调用D3DXCreateFontIndirect函数即可获得ID3DXFont接口对象。

那么接下来依然是以人性化接口为目的的二次封装~

/* -------------------------------------

代码清单:D3DFont.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" D3DInit.h "

#pragma  once

class  CD3DFont
{
public :
    CD3DFont(IDirect3DDevice9 
* pDevice, D3DPRESENT_PARAMETERS *  pD3DPP);
    
~ CD3DFont( void );
public :
    
bool  LoadFont( char   * fontName, UINT fontSize);     //  加载字体
     void  Release();                                   //  释放字体
public :
    ID3DXFont
*  GetFontHandle(){ return  m_pFont;}       //  获得字体句柄
    RECT       GetFontArea(){ return  m_FontArea;}      //  获得字体默认有效区域(屏幕区域)
private :
    IDirect3DDevice9
*  m_pDevice;                      //  3D设备
    ID3DXFont *  m_pFont;                               //  字体对象
    RECT m_FontArea;                                  //  字体默认有效区域
};

 

/* -------------------------------------

代码清单:D3DFont.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" D3DFont.h "

CD3DFont::CD3DFont(IDirect3DDevice9 
* pDevice, D3DPRESENT_PARAMETERS *  pD3DPP) : m_pDevice(pDevice), m_pFont(NULL)
{
    m_FontArea.left 
=   0 ;
    m_FontArea.top 
=   0 ;
    m_FontArea.right 
=  pD3DPP -> BackBufferWidth;
    m_FontArea.bottom 
=  pD3DPP -> BackBufferHeight;
}

CD3DFont::
~ CD3DFont( void )
{
}

bool  CD3DFont::LoadFont( char   * fontName, UINT fontSize)
{
    
//  生成D3DXFONT_DESC结构体并初始化
    D3DXFONT_DESC d3dxFont;
    ZeroMemory(
& d3dxFont, sizeof (d3dxFont));
    _tcscpy(d3dxFont.FaceName,fontName);
    d3dxFont.Width 
=  fontSize;  
    d3dxFont.Height 
=  fontSize  *   2
    d3dxFont.Weight 
=   100 ;
    d3dxFont.Italic 
=   false ;
    d3dxFont.CharSet 
=  DEFAULT_CHARSET;
    
//  创建字体对象
     if (FAILED(D3DXCreateFontIndirect(m_pDevice, & d3dxFont, & m_pFont))){
        
return   false ;    
    }
    
return   true ;
}

void  CD3DFont::Release()
{
    ReleaseCOM(m_pFont);
}

 

接下来,为我们先前构造的CD3DSprite添加几个重载函数,赋予其绘制字体的能力:

 

D3DSprite.h
public :
    
void  DrawText(
        CD3DFont
*  pFont,                      //  字体对象
         char   * szString,                       //  文字内容
        RECT  & DesRect,                        //  目标区域
        DWORD AlignFormat,                    //  对齐格式
        D3DCOLOR Color);                      //  字体颜色
     void  DrawText(
        CD3DFont
*  pFont, 
        
char   * szString, 
        RECT 
& DesRect, 
        D3DCOLOR Color 
=  D3DXCOLOR_WHITE);
    
void  DrawText(
        CD3DFont 
* pFont, 
        
char   * szString, 
        D3DXVECTOR2 
& Pos,                     //  字体位置
        D3DCOLOR Color  =  D3DXCOLOR_WHITE);

 

D3DSprite.cpp
void  CD3DSprite::DrawText(CD3DFont  * pFont,  char   * szString, RECT  & DesRect, D3DCOLOR Color)
{
    DrawText(pFont, szString, DesRect, DT_TOP
| DT_LEFT, Color);
}

void  CD3DSprite::DrawText(CD3DFont  * pFont,  char   * szString, D3DXVECTOR2  & Pos, D3DCOLOR Color)
{
    RECT DesRect;
    DesRect.left 
=  Pos.x;
    DesRect.top 
=  Pos.y;
    DesRect.right 
=  pFont -> GetFontArea().right;
    DesRect.bottom 
=  pFont -> GetFontArea().bottom;
    DrawText(pFont, szString, DesRect, Color);
}

void  CD3DSprite::DrawText(CD3DFont *  pFont,  char   * szString, RECT  & DesRect, DWORD AlignFormat, D3DCOLOR Color)
{
    pFont
-> GetFontHandle() -> DrawText(m_pSprite, szString,  - 1 & DesRect, AlignFormat, Color);
}

 

看到这里,大家可能会觉得奇怪。由第三个重载函数我们不难看出:最终的DrawText调用方依然是ID3DXFont接口对象,那么为什么我们要将字体绘制的功能封装到CD3DSprite对象中呢?

由DirectX说明文档可知,ID3DXFont.DrawText函数的第一个参数是一个宿主ID3DXSprite接口对象,尽管允许其为空,但要想实现更高效率的字体绘制,则我们需要指定一个专属的ID3DXSprite接口对象。

从Xna的SpriteBatch对象封装的手法上,我们可以略微看出端倪,尽管巧合的几率更大些,但各种因素促使我们有理由这么做~

 

实现字体绘制功能之后,我们再来实现一个可以统计游戏运行效率的计时装置。

FPS 即 Frames Per Second(每秒传输帧数),是我们用于测算游戏运行效率的通用标准。如果游戏在执行密集型计算或大批量渲染时,FPS值依然能达到一个正常标准(通常为60帧/s),则说明我们的游戏运行效率良好。

下面就来看这个游戏计时器的实现及使用方法:

 

/* -------------------------------------

代码清单:GameTime.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
< stdio.h >
#include 
" mmsystem.h "

#pragma  once

class  CGameTime
{
public :
    CGameTime(
void );
    
~ CGameTime( void );
public :
    
void  Start();      //  游戏计时器启动
     void  Tick();       //  游戏计时器心跳(每次Update时调用一次)
     void  Stop();       //  游戏计时器停止
public :
    
float  GetTotalTicks()     { return  m_totalTicks;}           //  获得系统启动时间
     float  GetTotalGameTime()  { return  m_totalGameTime;}        //  获得游戏运作时间
     float  GetElapsedGameTime(){ return  m_elapsedGameTime;}      //  获得单帧时间差
public :
    
void   CalcFPS();                                           //  计算FPS(每次Update时调用一次)(需显式调用,默认不执行)
     char *  ShowFPS()           { return  m_strFPS;}               //  显示FPS(字符串)
     float  GetFPS()            { return  m_FPS;}                  //  获得FPS(float数值)
private :
    
float  m_totalTicks;           //  系统启动时间
     float  m_totalGameTime;        //  游戏运作时间
     float  m_elapsedGameTime;      //  单帧运作时间
     float  m_previousTicks;        //  前次时间戳
     float  m_startTicks;           //  启动时间戳
    DWORD m_FrameCnt;             //  帧总数(计算FPS辅助变量)
     float  m_TimeElapsed;          //  消耗时间(计算FPS辅助变量)
     float  m_FPS;                  //  FPS值
     char   m_strFPS[ 13 ];           //  FPS串
};

 

Direct3D轮回:文字显示及FPS效率统计 GameTime.cpp
/* -------------------------------------

代码清单:GameTime.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" GameTime.h "

#pragma  comment(lib, "winmm.lib")

CGameTime::CGameTime(
void ) : m_totalTicks( 0.0f ),
                             m_totalGameTime(
0.0f ),
                             m_elapsedGameTime(
0.0f ),
                             m_previousTicks(
0.0f ),
                             m_startTicks(
0.0f ),
                             m_FrameCnt(
0 ),
                             m_TimeElapsed(
0.0f ),
                             m_FPS(
0.0f )
{
    
//  初始FPS串
     char *  strFPS  =   " FPS:0000000\0 " ;
    memcpy(m_strFPS,strFPS,
13 );
}

CGameTime::
~ CGameTime( void )
{
}

void  CGameTime::Start()
{
    
//  计时器启动时先捕捉起始时间戳及前次时间戳
    m_startTicks  =  ( float )timeGetTime();
    m_previousTicks 
=  m_startTicks;
}

void  CGameTime::Tick()
{
    
//  每次心跳时更新系统时间戳
    m_totalTicks  =  ( float )timeGetTime();
    
//  单帧时间差 = 系统时间戳 - 前次时间戳
    m_elapsedGameTime  =  m_totalTicks  -  m_previousTicks;
    
//  游戏运作时间 = 系统时间戳 - 起始时间戳
    m_totalGameTime  =  m_totalTicks  -  m_startTicks;
    
//  更新前次时间戳
    m_previousTicks  =  m_totalTicks;
}

void  CGameTime::Stop()
{
    
//  计时器停止时全部变量归0
    m_totalTicks  =   0.0f ;
    m_totalGameTime 
=   0.0f ;
    m_elapsedGameTime 
=   0.0f ;
    m_previousTicks 
=   0.0f ;
    m_startTicks 
=   0.0f ;
    m_FrameCnt 
=   0 ;
    m_TimeElapsed 
=   0.0f ;
    m_FPS 
=   0.0f ;
}

void  CGameTime::CalcFPS()
{
    
//  帧数递增
    m_FrameCnt ++ ;
    
//  累计时间递增
    m_TimeElapsed  +=  m_elapsedGameTime;
    
//  当累计时间超过1秒时
     if (m_TimeElapsed  >=   1000.0f )
    {
        
//  FPS测算
        m_FPS  =  ( float )m_FrameCnt  *   1000.0f   /  m_TimeElapsed;
        sprintf(
& m_strFPS[ 4 ],  " %f " , m_FPS);
        m_strFPS[
12 =   ' \0 ' ;
        
//  处理累计时间及帧数
        m_TimeElapsed  -=   1000.0f ;
        m_FrameCnt    
=   0 ;
    }
}

 

很显然,CGameTime的Start和Stop是超越了游戏主循环的,而这部分对CD3DGame而言是透明的,我们需要将CGameTime部分相关代码放置到Win32App的默认代码中:

 

int  APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     
int        nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

     
//  TODO: 在此放置代码。
    MSG msg;
    HACCEL hAccelTable;

    
//  初始化全局字符串
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_KEN3DGAME, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    
//  执行应用程序初始化:
     if  ( ! InitInstance (hInstance, nCmdShow))
    {
        
return  FALSE;
    }

    hAccelTable 
=  LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_KEN3DGAME));

    LoadContent();
    ZeroMemory(
& msg,  sizeof (MSG));

    
//  声明游戏计时器对象,该对象随入口方法的结束而自动释放(生命周期终止)
    CGameTime GameTime;
    
//  计时器随主循环的启动而启动
    GameTime.Start();
    
while (msg.message  !=  WM_QUIT) {
        
if (PeekMessage( & msg, NULL,  0 0 , PM_REMOVE)) {
            TranslateMessage(
& msg);
            DispatchMessage(
& msg);
        }
        
//  计时器心跳
        GameTime.Tick();
        Update(
& GameTime);
        Draw(
& GameTime);
    }
    
//  计时器随主循环的停止而停止
    GameTime.Stop();
    UnloadContent();
    Dispose();

    
return  ( int ) msg.wParam;
}

 

以上为变更之后的Win32入口函数。我们可以看到CD3DGame的Update和Draw函数参数不再是单纯的float值,而是功能完整的游戏计数器指针。

接下来,我们来看CD3DGame主体代码:

 

Direct3D轮回:文字显示及FPS效率统计 D3DGame.cpp
/* -------------------------------------

代码清单:D3DGame.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
" StdAfx.h "
#include 
" D3DGame.h "
#include 
" D3DSprite.h "
#include 
" SpriteBatch.h "
#include 
" D3DFont.h "
#include 
" D3DCamera.h "
#include 
" BaseTerrain.h "
#include 
< stdio.h >
#include 
< time.h >

// ---通用全局变量

HINSTANCE  g_hInst;
HWND       g_hWnd;
D3DXMATRIX g_matProjection;
D3DPRESENT_PARAMETERS g_D3DPP;

// ---D3D全局变量

IDirect3D9       
* g_pD3D            =  NULL;
IDirect3DDevice9 
* g_pD3DDevice      =  NULL;
CMouseInput      
* g_pMouseInput     =  NULL;
CKeyboardInput   
* g_pKeyboardInput  =  NULL;
CD3DSprite       
* g_pSprite         =  NULL;
CD3DCamera       
* g_pD3DCamera      =  NULL;
CBaseTerrain     
* g_pBaseTerrain    =  NULL;
CSpriteBatch     
* g_pSpriteBatch    =  NULL;
CD3DFont         
* g_pFont           =  NULL;
CD3DFont         
* g_pFont2          =  NULL;

void  Initialize(HINSTANCE hInst, HWND hWnd)
{
    g_hInst 
=  hInst;
    g_hWnd  
=  hWnd;
    InitD3D(
& g_pD3D,  & g_pD3DDevice, g_D3DPP, g_matProjection, hWnd);
    g_pMouseInput 
=   new  CMouseInput;
    g_pMouseInput
-> Initialize(hInst,hWnd);
    g_pKeyboardInput 
=   new  CKeyboardInput;
    g_pKeyboardInput
-> Initialize(hInst,hWnd);
    srand(time(
0 ));
}

void  LoadContent()
{
    g_pD3DCamera 
=   new  CD3DCamera;
    g_pSprite 
=   new  CD3DSprite(g_pD3DDevice);
    
//  声明并加载两种不同的字体
    g_pFont  =   new  CD3DFont(g_pD3DDevice, & g_D3DPP);
    g_pFont
-> LoadFont( " 宋体 " , 8 );
    g_pFont2 
=   new  CD3DFont(g_pD3DDevice, & g_D3DPP);
    g_pFont2
-> LoadFont( " 隶书 " , 16 );
}

void  Update(CGameTime *  gameTime)
{
    
//  统计FPS
    gameTime -> CalcFPS();
    g_pMouseInput
-> GetState();
    g_pKeyboardInput
-> GetState();
    g_pD3DCamera
-> Update();
    g_pBoundingFrustum
-> Update(g_pD3DCamera -> GetViewMatrix()  *  g_matProjection);
}

void  Draw(CGameTime *  gameTime)
{
    g_pD3DDevice
-> SetTransform(D3DTS_VIEW, & g_pD3DCamera -> GetViewMatrix());
    g_pD3DDevice
-> Clear( 0 , NULL, D3DCLEAR_TARGET  |  D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA( 100 , 149 , 237 , 255 ),  1.0f 0 );
    
if (SUCCEEDED(g_pD3DDevice -> BeginScene())) 
    {
        
//  开启绘制
        g_pSprite -> Begin(D3DXSPRITE_ALPHABLEND);
        
//  显示FPS
        g_pSprite -> DrawText(g_pFont, gameTime -> ShowFPS(), D3DXVECTOR2( 100 , 100 ), D3DXCOLOR_WHITE);
        
//  绘制具体文字
        g_pSprite -> DrawText(g_pFont2,  " 花瓣散落了满地的记忆,拾起来放入口中\r\n细细咀嚼,再也找不回原来的味道… " , D3DXVECTOR2( 100 , 200 ), D3DCOLOR_ARGB( 128 , 0 , 0 , 255 ));
        
//  结束绘制
        g_pSprite -> End();
        g_pD3DDevice
-> EndScene();
    }
    g_pD3DDevice
-> Present(NULL, NULL, NULL, NULL);
}

void  UnloadContent()
{
    ReleaseCOM(g_pFont2);
    ReleaseCOM(g_pFont);
    ReleaseCOM(g_pSprite);
}

void  Dispose()
{
    ReleaseCOM(g_pKeyboardInput);
    ReleaseCOM(g_pMouseInput);
    ReleaseCOM(g_pD3DDevice);
    ReleaseCOM(g_pD3D);
}

 

如下效果图:

Direct3D轮回:文字显示及FPS效率统计

 

可能大家会有疑问:只绘制了缪缪几行文字,简单统计一下数据,为什么FPS只有60帧左右呢?

DirectX9.0及之后版本在原有基础上做了改进,我们可以通过D3DPRESENT_PARAMETERS.PresentationInterval认为的控制3D设备的刷新行为。

其中,D3DPRESENT_INTERVAL_IMMEDIATE代表即时刷新;D3DPRESENT_INTERVAL_ONE代表屏幕刷新率标准;D3DPRESENT_INTERVAL_DEFAULT为默认方式,与D3DPRESENT_INTERVAL_ONE接近但FPS稍低。

如我们所知,超出显示器刷新频率的刷新标准没有实际意义,而且会空耗D3D设备性能。因此,如果不是效率极端测试,不建议用D3DPRESENT_INTERVAL_IMMEDIATE,只需用D3DPRESENT_INTERVAL_ONE即可。

除去以上三种标准,剩余的还有D3DPRESENT_INTERVAL_TWO、D3DPRESENT_INTERVAL_THREE、D3DPRESENT_INTERVAL_FOUR,它们不太常用,而且使用方式也不像前三种那样单纯,大家感兴趣可自行翻看DX帮助文档,在此不再赘述。

 

由于ID3DXFont底层是基于Gdi的,因此虽然功能强大,但却在效率上不被看好。不过由于游戏大都以图形元素为主要表现形式,而文字大都为辅助作用,因此在一般情况下上述方法应该是够用的。

 

以上,谢谢 ^ ^

 

你可能感兴趣的:(FP)