ゲームプログラミングTips(9):DirectX Graphics(4)(テキスト編)
2013年1月27日……前回の講座より、実に約3ヶ月ぶりである。
実際にこの講座を書いたのは前回の講座を実際に書いてからわりとすぐ(5月29日)だが、まあ色々と忙しかったわけで……(言い訳乙)
1.DirectX Graphicsとは?(Part4)
以下略(´Д`)
2.DirectX Graphicsのゲームプログラミングにおける活用事例(Part4)
今回の内容は、今までの内容を踏まえたものとは若干異なる。
何故なら画像ではなくテキストを扱うからだ、あえていえば基幹部分と若干重なる部分あり。
何故、基幹編にもテキスト描画機能があったのに別個に実装しているのか? 詳細は以降にて解説する。
3.具体例(ヘッダ解説)
本項では、俺が作ったグラフィック管理クラス(テキスト)を掲載する。
/************************************************* ファイル名:IDraw2DText.h 作成者 :あびす 役割 :グラフィック管理クラス(2Dテキスト) *************************************************/ /** * @file IDraw2DText.h * @brief グラフィック(2Dテキスト)の管理を行うクラス関係です。 */ #ifndef DX9A_GRAPHICS_IDRAW2DTEXT_H #define DX9A_GRAPHICS_IDRAW2DTEXT_H namespace nsDX9A{ namespace nsGraphics{ namespace nsIDraw2DText{ //文字テクスチャ /** * @brief 文字テクスチャの情報が格納されている構造体です。 |
ヘッダに対する解説では、本クラスの使用方法ならびに実装技法を説明する。
使用方法は簡単である。
1.インスタンスを生成する
2.SetTextメソッド等でテキストの設定を行う
3.DrawTextメソッドで設定したテキストの描画を行う
具体例 DX9AGraphic* g_DX9AG = new DX9AGraphic(); //DirectX Graphicsラッパークラス CDraw2DText A(g_DX9AG); //グラフィック管理クラス(テキスト) A.SetText("おけぱか", "MS Pゴシック", 20, false, false); //テキストを設定 A.DrawText(100, 100, D3DCOLOR_ARGB(128, 255, 0, 0)); //テキストを描画 |
……これだけである、特に変なところはない筈だ。
では、以下にて具体的な説明を行っていく。
ただし、なるべく説明は簡略に行う。
ヘッダの説明
ChTexture構造体について
基幹編とほぼ同様、クラス使用者が意識する必要のない構造体である。
なんでこんなクラスが要るの?
これまでのTipsを読んでいると一目瞭然なのだが、実はテキスト描画自体はDX9AGraphicクラスでも可能である。
にもかかわらず、CDraw2DTextクラスという新たなクラスでもテキスト描画を可能にしている。それは何故なのか?
理由を簡潔に述べると、「効率良くメモリを使用するため」である。
DX9AGraphicクラスは内部で自動解放を行っているが、明示的な解放は行えないため使用メモリが肥大化しがちである。
それに対し、CDraw2DTextクラスは基幹クラスではない。そのため、解体すれば使用メモリが全て後腐れなく解放される。
またメモリの消費効率も異なる、DX9AGraphicクラスは「文字列」を保持しているのに対しCDraw2DTextクラスは「文字」である。
たとえば「あいあいあいあいあい」というテキスト描画を行う際に前者では10文字分、後者では2文字分のメモリを使用している。
つまり、使い分けが可能である。
DX9AGraphic…メモリの消費効率などを一切考慮しないテキスト描画(ちょっとしたテキスト描画など)
CDraw2DText…局所的に大量の描画を行うためにメモリの消費効率などを考慮するべきテキスト描画(ノベルゲームなど)
上記のような感じである、まあ所詮はテキストのメモリに過ぎないのでそこまで大袈裟に考える必要もないのだが……(;´Д`)
4.具体例(ソース解説)
次はソース解説である。
#include "stdafx.h" #include "../index.h" #include "IDraw2DText.h" namespace nsDX9A{ namespace nsGraphics{ namespace nsIDraw2DText{ //コンストラクタ(DirectX Graphicsラッパーを指定) CDraw2DText::CDraw2DText(DX9AGraphic* Outer){ m_StringTextureNowSize = 0; m_StringTextureMaxSize = 0; m_KeyTextHeight = 0; m_Size = 0; m_IsBold = false; m_IsItalic = false; m_Outer = Outer; } //デストラクタ CDraw2DText::~CDraw2DText(){ for(map< string, ChTexture >::iterator it=m_ChTextures.begin();it!=m_ChTextures.end();it++){ SAFE_RELEASE(it->second.Texture); } } //文字列テクスチャの最大サイズを設定 void CDraw2DText::SetStringTextureMaxSize(unsigned int StringTextureMaxSize){ m_StringTextureMaxSize = StringTextureMaxSize; } //2Dテキストを設定 void CDraw2DText::SetText(const string& Text, const string& Font, unsigned int Size, bool IsBold, bool IsItalic){ //内部関数用クラス class InnerFunction{ public: //文字列を文字群に分解 static void ResolveText(const string& Text, vector< string >& Chs){ Chs.clear(); for(unsigned int i=0;Text[i]!='\0';){ unsigned int ChByte = ((Text[i]>=static_cast< char >(0x81) && Text[i]<=static_cast< char >(0x9F)) || (Text[i]>=static_cast< char >(0xE0) && Text[i]<=static_cast< char >(0xFF))) ? 2 : 1; Chs.push_back(string(Text.c_str()+i, ChByte)); i += ChByte; } } //テキスト(描画時に利用)を生成 static void CreateKeyText(const vector< string >& Chs, const string& Font, unsigned int Size, bool IsBold, bool IsItalic, vector< string >& KeyText){ KeyText.clear(); char Suffix[1024] = {0}; sprintf(Suffix, "_%s_%d_%d", Font.c_str(), Size, (IsBold ? 1 : 0) + (IsItalic ? 2 : 0)); for(vector< string >::const_iterator it=Chs.begin();it!=Chs.end();it++){ if((*it)=="\n"){ KeyText.push_back("\n"); } else{ KeyText.push_back((*it) + Suffix); } } } //GetGlyphOutline用にエンコード static void EncodeText(const string& Src, wstring& Dest){ Dest.clear(); for(unsigned int i=0;Src[i]!='\0';){ unsigned int ChByte = ((Src[i]>=static_cast< char >(0x81) && Src[i]<=static_cast< char >(0x9F)) || (Src[i]>=static_cast< char >(0xE0) && Src[i]<=static_cast< char >(0xFF))) ? 2 : 1; Dest.push_back(ChByte==2 ? static_cast< BYTE >(Src[i]) * 256 + static_cast< BYTE >(Src[i+1]) : static_cast< BYTE >(Src[i])); i += ChByte; } } }; unsigned int StringTextureNowSize = 0; m_Text = Text; m_KeyText.clear(); m_KeyTextHeight = Size; m_Font = Font; m_Size = Size; m_IsBold = IsBold; m_IsItalic = IsItalic; //空文字列の場合 if(Text==""){ return; } //空文字列でない場合 else{ vector< string > Chs; InnerFunction::ResolveText(Text, Chs); //文字列を文字群に分解 InnerFunction::CreateKeyText(Chs, m_Font, m_Size, m_IsBold, m_IsItalic, m_KeyText); //テキスト(描画時に利用)を生成 wstring UniChs; InnerFunction::EncodeText(Text, UniChs); //GetGlyphOutline用にエンコード //フォントを作成 LOGFONT LF = {Size, 0, 0, 0, (IsBold ? FW_BOLD : 0), (IsItalic ? TRUE : FALSE), 0, 0, SHIFTJIS_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FIXED_PITCH | FF_MODERN, ""}; strcpy(LF.lfFaceName, Font.c_str()); HFONT hFont; if((hFont = CreateFontIndirect(&LF))==0){ DX9AErrOut("Failed : CDraw2DText::SetText() CreateFontIndirect\n"); return; } //デバイスコンテキストを取得 HDC hDC = GetDC(NULL); HFONT OldFont = reinterpret_cast< HFONT >(SelectObject(hDC, hFont)); //テクスチャを生成(存在しない場合のみ) for(unsigned int i=0;i< m_KeyText.size();i++){ UINT UniCh = UniChs[i]; string KeyCh = m_KeyText.at(i); //改行:何もせず if(KeyCh=="\n"){ continue; } //テクスチャが未作成:作成する if(m_ChTextures.find(KeyCh)==m_ChTextures.end()){ //テクスチャのサイズを取得 TEXTMETRIC TM; GLYPHMETRICS GM; UINT TexWidth = 0, TexHeight = 0; CONST MAT2 Mat = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; GetTextMetrics(hDC, &TM); TexHeight = TM.tmHeight; GetGlyphOutline(hDC, UniCh, GGO_METRICS, &GM, 0, NULL, &Mat); TexWidth = GM.gmCellIncX; if(IsItalic){ TexWidth += Size / 4; } unsigned int cnt; for(cnt=0;pow(2.0f, static_cast< float >(cnt))< TexWidth;cnt++); TexWidth = static_cast< UINT >(pow(2.0f, static_cast< float >(cnt))); for(cnt=0;pow(2.0f, static_cast< float >(cnt))< TexHeight;cnt++); TexHeight = static_cast< UINT >(pow(2.0f, static_cast< float >(cnt))); //文字列テクスチャのパラメータを設定 ChTexture stTemp; stTemp.Size = 4 * TexWidth * TexHeight; stTemp.Texture = NULL; stTemp.ChHeight = TM.tmHeight; stTemp.ChWidth = GM.gmCellIncX; stTemp.SrcRect.left = 0; stTemp.SrcRect.top = 0; stTemp.SrcRect.right = TexWidth; stTemp.SrcRect.bottom = TexHeight; m_StringTextureNowSize += stTemp.Size; if(UniCh==32/* 半角スペース */ || UniCh==33088/* 全角スペース */){ //半角スペース・全角スペースの場合:テクスチャを確保しない stTemp.Size = 0; } else{ //半角スペース・全角スペース以外の場合:テクスチャを確保する //テクスチャを確保 if(FAILED(D3DXCreateTexture(GetDX9AGraphicAccessor(m_Outer)->GetD3D9Device(), TexWidth, TexHeight, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &(stTemp.Texture)))){ DX9AErrOut("Failed : CDraw2DText::SetText() D3DXCreateTexture\n"); SelectObject(hDC, OldFont); DeleteObject(hFont); ReleaseDC(NULL, hDC); return; } //フォントビットマップ取得→テクスチャに書き込み DWORD Alpha, Color; D3DLOCKED_RECT LockedRect; BYTE* p = new BYTE[stTemp.Size]; int Bmp_W, Bmp_H, Ofs_X, Ofs_Y; if(FAILED(stTemp.Texture->LockRect(0, &LockedRect, NULL, D3DLOCK_DISCARD))){ DX9AErrOut("Failed : CDraw2DText::SetText() LockRect\n"); SelectObject(hDC, OldFont); DeleteObject(hFont); ReleaseDC(NULL, hDC); SAFE_DELETE_ARRAY(p); return; } FillMemory(LockedRect.pBits , LockedRect.Pitch * TexHeight, 0); GetGlyphOutline(hDC, UniCh, GGO_METRICS, &GM, 0, NULL, &Mat); GetGlyphOutline(hDC, UniCh, GGO_GRAY8_BITMAP, &GM, stTemp.Size, p, &Mat); Bmp_W = GM.gmBlackBoxX + (4 - (GM.gmBlackBoxX % 4)) % 4; Bmp_H = GM.gmBlackBoxY; Ofs_X = GM.gmptGlyphOrigin.x; Ofs_Y = TM.tmAscent - GM.gmptGlyphOrigin.y; for(int Y=0;Y< static_cast< int >(GM.gmBlackBoxY);Y++){ for(int X=0;X< static_cast< int >(GM.gmBlackBoxX);X++){ Alpha = (255 * p[Bmp_W * Y + X]) / 64; Color = 0x00ffffff | (Alpha << 24); memcpy((BYTE*)LockedRect.pBits+LockedRect.Pitch*(Ofs_Y+Y)+4*(Ofs_X+X), &Color, sizeof(Color)); } } stTemp.Texture->UnlockRect(0); SAFE_DELETE_ARRAY(p); } m_ChTextures.insert(pair< string, ChTexture >(KeyCh, stTemp)); } StringTextureNowSize += m_ChTextures.find(KeyCh)->second.Size; if(m_ChTextures.find(KeyCh)->second.ChHeight>m_KeyTextHeight){ m_KeyTextHeight = m_ChTextures.find(KeyCh)->second.ChHeight; } m_ChTextures.find(KeyCh)->second.LastRefTime = timeGetTime(); } //後始末 SelectObject(hDC, OldFont); DeleteObject(hFont); ReleaseDC(NULL, hDC); } //生成した文字列テクスチャのサイズ>文字列テクスチャの最大サイズの場合、文字列テクスチャの最大サイズを自動的に拡張する if(StringTextureNowSize>m_StringTextureMaxSize){ m_StringTextureMaxSize = StringTextureNowSize; } //文字列テクスチャの現在サイズ>文字列テクスチャの最大サイズの場合、テクスチャの仕分けを行う if(m_StringTextureNowSize>m_StringTextureMaxSize){ CutChTexture(); } } //テキストを取得 const string& CDraw2DText::GetText() const{ return(m_Text); } //フォントを取得 const string& CDraw2DText::GetFont() const{ return(m_Font); } //サイズを取得 unsigned int CDraw2DText::GetSize() const{ return(m_Size); } //太字かを取得 bool CDraw2DText::IsBold() const{ return(m_IsBold); } //斜体かを取得 bool CDraw2DText::IsItalic() const{ return(m_IsItalic); } //2Dテキストを描画 void CDraw2DText::DrawText(int x, int y, D3DCOLOR Color, float Degree, D3DBLENDOP BlendOp, D3DBLEND SrcBlend, D3DBLEND DestBlend){ //空文字列の場合 if(m_KeyText.empty()){ return; } //空文字列でない場合 else{ int BaseX = x; int BaseY = y; for(vector< string >::iterator it=m_KeyText.begin();it!=m_KeyText.end();it++){ if((*it)=="\n"){ BaseX = x; BaseY += m_KeyTextHeight; } else{ if(m_ChTextures.find(*it)->second.Texture){ RECT SrcRect = {0, 0, m_ChTextures.find(*it)->second.ChWidth, m_ChTextures.find(*it)->second.ChHeight}; m_Outer->GeneralBlt(m_ChTextures.find(*it)->second.Texture, BaseX, BaseY, Color, &SrcRect, BlendOp, SrcBlend, DestBlend, 1.0f, 1.0f, Degree); } m_ChTextures.find(*it)->second.LastRefTime = timeGetTime(); BaseX += m_ChTextures.find(*it)->second.ChWidth; } } } } //文字列テクスチャを削減 void CDraw2DText::CutChTexture(){ DWORD Now = timeGetTime(); map< string, ChTexture >::iterator it; while(m_StringTextureNowSize>m_StringTextureMaxSize){ it = m_ChTextures.begin(); for(map< string, ChTexture >::iterator it2=m_ChTextures.begin();it2!=m_ChTextures.end();it2++){ if(Now-it2->second.LastRefTime>Now-it->second.LastRefTime){ it = it2; } if(Now-it2->second.LastRefTime==Now-it->second.LastRefTime){ if(it2->second.Size>it->second.Size){ it = it2; } } } m_StringTextureNowSize -= it->second.Size; SAFE_RELEASE(it->second.Texture); m_ChTextures.erase(it); } } } } } |
CDraw2DTextメソッド内がわりとややこしいが、あえて説明はしない(´Д`)
いくつかの関数を順にググっていけば、何をしているかは読み解ける筈である。
……以上で本項は終了である、お疲れ様でした(´Д`)
次回からようやくDirectSound編である、難筆の予感……?(難易度的な意味で)