青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

Code Knight

Programming is so cool
隨筆 - 52, 文章 - 0, 評論 - 14, 引用 - 0
數據加載中……

[轉]游戲中的點陣字體和TrueType字體

點陣字庫
  包括現在,有很多游戲都還是使用的點陣字庫。因為操作起來比較方便,加上這方面的經驗已經積累了好幾年了。通常如果只是一種字體就可以滿足需要的話,它會是一個比較好、快的解決辦法。但是它有3個缺點:
1. 如果放大顯示,不做處理的話,顯示出來的漢字,是很難看的。
2. 像是UCDOS所提供的點陣字庫,只有24點陣的有幾種字體,如:宋體、黑體、揩體…,而16點陣的好象就只有宋體一種。
3. 點陣字庫,通常是有版權的,尤其是第三方制作的漢字庫(如:方正)。
  在這樣的情況下,當我們寫好這樣的一個顯示函數,就算是解決了如:放大、快速顯示等問題的話,可供選擇的字體還是太過于局限了。所以,在字體的要求比較強的情況下,點陣字庫并不是一個好的解決方法,他不夠靈活。盡管我們對于它的操作是如此得熟練,可以寫出優美的代碼來展示我們的編程技巧。


TTF
  TTF是True Type Font的簡稱。在Windows\Fonts目錄下面,我們可以看到許多后綴為ttf的文件,它就是接下來我們接下來所要談到的。
  TTF是一種矢量字庫。我們經常可以聽到矢量這個詞,像是FLASH中的矢量圖形,在100*100分辨率下制作的flash,就算它放大為全屏,顯示出的畫面也不會出現馬賽克。所謂矢量,其實說白了就是用點和線來描述圖形,這樣,在圖形需要放大的時候,只要把所有這個圖形的點和線放大相應的倍數就可以了。而且,在網站上有很多的TTF字庫可以下載,或者你可以去買一些專門的字庫光盤。然后在你發行你精心制作的游戲時,可以順便捎上這些后綴為.ttf 的文件就行了。包括Quake這樣的驚世之作,也都是用的TTF字庫。
  這樣,我們就可以解決點陣漢字的一些問題。通過TTF,我們在字體的質量和字庫的數量上獲得了暫時性的勝利。


字庫的讀取和顯示
  先前談到點陣字庫,只需要很簡單的一些操作,就可以顯示出想要的漢字。下面我給出一個讀取hzk16的函數,它需要一個Surface以供顯示用:
  #include <io.h>
  #include <stdio.h>
  #include <conio.h>
  
  // 讀取16x16
  void DispHZ16(int x, int y, BYTE *Str, LPDIRECTDRAWSURFACE surf)
  {
   const int Mask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
   FILE *HzkFp;
   WORD i, j, k=0, m;
   WORD HzNum;
   WORD QuHao;
   WORD WeiHao;
   long offset;
   BYTE dotBuffer[32];
  
   HzkFp = fopen("HZK16", "rb");
  
   HzNum = strlen((const char *)Str)/2;
  
   DDSURFACEDESC ddsd;
   LPWORD lpSurface;
   HRESULT ddrval;
  
   ddsd.dwSize = sizeof(ddsd);
  
   while((ddrval=surf->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING);
   if(ddrval == DD_OK)
   lpSurface = (LPWORD)ddsd.lpSurface;
  
   for(i = 0; i<HzNum; i++)
   {
   QuHao = Str[i*2]-160;
   WeiHao = Str[i*2+1]-160;
  
   offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;
  
   fseek(HzkFp, offset, SEEK_SET);
   fread(dotBuffer, 32, 1, HzkFp);
  
   for(j=0;j<16;j++)
   for(k=0;k<2;k++)
   for(m=0;m<8;m++)
   if(dotBuffer[j*2+k] & Mask[m])
   {
   lpSurface[ddsd.lPitch*(y+j+1) + x+k*8+m] = 0x000000;
   }
   x+=16;
   }
  
   surf->Unlock(NULL);
  
   fclose(HzkFp);
  }
  其實原理很簡單:
1. 打開字庫
2. 計算字符串長度(這個函數只支持中文),并且Lock Surface
3. 依次計算出每個漢字所對應的區碼和位碼(漢字的第1個字節是區碼,第2個字節就是位碼),然后通過公式計算出這個漢字在字庫中的偏移量:
offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;
4. 讀出一個32個字節的點陣
5. 繪制到Surface上
  以上只是16*16點陣字庫的顯示方法,24*24的讀取方法與之類似,大家可以參照相關資料來書寫出自己的代碼。
  
  如何顯示TTF字庫呢,有很多種手段,下面我按從簡單到復雜的的順序依次介紹:
1. 使用Windows API,也就是大家所熟悉的TextOut。通過它,還需要一個HDC(設備句柄),我們就可以隨意地在屏幕任何地方顯示出文字了。
2. 在http://www.freetype.org,有一個FreeType的免費庫,而且是OpenSource的。它目前有2個版本:1.0和 2.0。其區別在于,1.0只能讀取TTF格式的,而2.0支持更多的文件格式,在使用它之前請詳細閱讀所要遵循的Licence,以下是摘自 FreeType2.0對字庫的支持列表:
o TrueType fonts (and collections)
o Type 1 fonts
o CID-keyed Type 1 fonts
o CFF fonts
o OpenType fonts (both TrueType and CFF variants)
o SFNT-based bitmap fonts
o X11 PCF fonts
o Windows FNT fonts
3. 自己研究TTF的格式,然后自己來操作。

....... ╮╮
    \█/倒!
    ●
雖然我們想要把每一件事情都做好,但是也不是每一件事情都要親歷親為。如果你非要這樣,也行^____^,但是過不了多久,你就會陷入泥沼,到時候你會發現自己的熱情正在慢慢被磨滅,什么叫做抓狂,相信你很快就會知道^_^。

在有多種選擇可以取舍的情況下,我們需要考慮一下,對比一下各種解決方法的優劣。

  在DirectDraw時代,我們都不自覺地喜歡上了GetDC,因為……多方便啊。可是現在已經到了DirectX8.1時代了(我要使勁地搖那些還沉醉于DirectX7中,為如何在使用alpha時提升那可憐的1、2個fps的朋友們:醒醒,該起床了!),HDC已經被M$列為禁用品。怎么辦呢?是的,你可能已經想到了,我們還一直保存著窗口的hWnd呢,可以通過它來得到hdc,從而調用那些需要hdc的API,可是,這樣做是更為愚蠢的,這樣對你是沒有一點好處的,不信,你就試試吧。有一句話,請牢記:要想你的游戲有更快的速度的話,請不要再去碰HDC了。
  我們非常清楚hdc是一個超慢的解決辦法,它無法在我們的高速游戲中滿60分及格。下面來看看FreeType,它更像是一個Service。它的解決方法是,先通過一系列的初始化和設置,告訴FreeType字體的名字和大小等,然后它會動態地申請一個Graphic,再把我們要顯示的字畫到這個 Graphic上,你還可以把它保存為tga格式。不過我們最終所想要的不是這個,所以可能我們還需要從這個Graphic上逐點讀取或者用 CopyRect,然后再畫到我們的畫面上。其實它已經是很方便的了,可是需要你去學習如何配置和使用它,這是很花時間的一件事情,而且它最大的優點是可以跨平臺,我們需要它嗎?如果有一個更為簡單的辦法,像是如果Textout不是那么慢的話,就好了……
  在這里,順便談一下另2個字體顯示類:ID3DXFont和CD3DFONT。可能早就有人會說怎么在上面的列表中沒有它們?原因我會在下面慢慢地說明:
  ID3DXFont,它存在于D3DX庫中,一個現成的字體類,不過對于它的處理方法……我實在不敢恭維,就引用一位大師所說的話來表達我的看法吧: 在內部實現中, ID3DXFont::DrawText()函數確實做了我上面討論的工作,先建立一張GDI兼容的位圖,把文本繪制到位圖上,而后把位圖拷貝到紋理貼圖上去,最后把紋理渲染到屏幕上。這樣你就聚齊了所有的龜速的原始GDI函數,還包括了一大堆的額外開銷 — 最終,這個函數比原來GDI的DrawTextEx()函數要慢上超過六倍……
  CD3DFONT,是由M$在D3D的框架代碼中提供。不過它只能顯示英文,有很多朋友通過自己定制和修改這個類,來實現自己的中文顯示。不過效果都不是很好。其實原理,跟ID3DXFont的方法差不多,不過處理方法要聰明了一點。


分析與思考
  那么我們應該怎么辦呢?通常我們會幻想,如果可以像處理英文那樣,把所有的漢字都保存在一張位圖里,該有多好。這樣,顯示的速度就不是問題了,直接可以CopyRect上去。可是,這樣可能嗎?首先,必須每一種字體都要生成這樣的一個巨型位圖。而且據說在GB2312中,一共有6000多個漢字,就算是用16*16,oh my god,這個位圖該有多大啊(據說會有2.5M^__^)!!!而且在DirectX8.1中,對于Texture(顯示的最小單位,就好象是原來 DirectSurface的概念一樣。說過多少遍了,不要再用DirectX8以前的東西了。不要試著去回憶那些美好的過去,我很明白,要你一下子放棄原來多年所獲得的成就,是一件很痛苦的事情,但是包袱太重,是會影響進步的。就像是我們的國家……扯遠了),不同的顯卡,支持的最大容量也是不同的。比方說早期的Voodoo,只支持256*256大小的Texture。而在我的顯卡(Geforce2 MX 200)上測試,支持最大2048*2048大小的Texture。對于這樣的硬件不確定性,我們只能取其最小值,也就是256*256。
  漢字雖然很多,但是常用的漢字,其實也就只有那么幾百個。像這樣的字:鬯、鞴,你一輩子會看到多少次呢?如果可以做一個類似于Cache的東西,保存著常用的那些個漢字,在需要顯示的的時候,先在Cache中查找,如果有的話,就馬上畫上去;如果沒有,就從字庫中提取到Cache中。這樣的話,在使用 Texture來保存漢字的位圖信息的同時,對于每個漢字,我們還要定義一個結構,然后用一個東西把它串起來,綜合它們2個,也就實現了我們所要的 Cache了。剛開始,我所定義的結構是這樣的:
  struct Char{
   char hz[3]; // 保存漢字
   int frequency;// 使用頻率
   RECT rect; // 這個字對應位圖的區域
   Bool isUsing; // 是否使用
  }
  對于漢字和英文,我在這里大概地講一下原理:漢字是由2個字節保存,而英文只需要1個。而判斷一個字是否是漢字,只需判斷第1個byte是否>128(在原來的GB2312中,漢字的2個字節都是>128的。而新的GBK字庫,漢字的第2個字節不一定>128,我想這是擴大了字庫容量的原因。我的意思是說,如果給一個字符串你,隨機給其中一個位置,然后我問你這個位置是什么?你的回答只能是:1 英文 2 漢字的首字節 3 漢字的尾字節。而這個問題的解法,為了穩妥起見,你必須從字符串的開始判斷起)。也就是說在char[3]中,如果保存的是漢字,則char[0]保存漢字第1個字節,char[1]保存漢字第2個字節,第3個存放’\0’;如果是英文的話,則只用到char[0],其它的全部為’\0’。
  接下來,對于使用char[3]來保存漢字,是否真的很合適呢?因為如果把它當作一個字符串來看的話,在查找時就需要使用 strcmp 來比較字符串了,這樣一定是會影響速度的。如果不把它看作字符串(字符串的最后一個字節需要以’\0’結尾),只用char[2]的話,我們可以只是簡單地調用宏MAKEWORD,把2個byte壓成1個WORD。當把文字作為一個WORD來看的時候,這樣查找比較時可以用WORD內建的==操作,這樣要比調用strcmp函數要快得多。
  int frequency用來標志每個WORD的使用頻率。設想,如果一個字已經存在于Cache中,以后每對它調用一次,就讓frequency++。這樣做還有一個用意是,是否可以在一個合適的時候,以frequency為參照來對這整個Cache排個序,把常用的字放在前面。那么在顯示時,可以先在 Cache中查找所要顯示的字是否已經存在于Cache中,如果有則直接顯示,沒有的話才需要采取某種手段將字加入到Cache中。一些常用的字(像:我、的、著、了、過……),使得顯示的速度將會大大提高。
  其實上面說了半天的Cache,它具體是什么呢?其實就是指的最小繪制單位,在DirectX7里是Surface,而在DirectX8中就是 Texture。使用它來存放顯示過的漢字,這樣,就不用每次都從字庫中讀取或是調用如TextOut這類GDI超慢的函數了。因為每次在繪制一個文字之前,都會先在這個Cache中找,有的話就直接畫上去,沒有才會調用TextOut操作。而這樣做的原因,我們先設想一下:游戲一般會控制為30fps或是60fps的速度不停地刷新,如果在GameLoop中有任何的代碼是龜速級的話,這樣就會導致fps的最大數的降低,也就意味著在保證30fps或 60fps的同時,能繪制到屏幕上的物體的數量減少了。這就是我們為什么要使用Texture來作為Cache的實現的原因。再一個,文字在屏幕上顯示時一般會保持一段時間,這個時間可能是1秒-3秒,我們的游戲也就會相應地更新60fps或180fps,這是因為人們需要閱讀它們。或者是一些如標題這樣的文字,它們總是不會更新的,或是更新得很慢。我們完全可以在第一時間,比方說我們的畫面有60fps,在第1個fps時,我們得知要顯示文字”唐”,然后先在Cache中找,結果很糟:沒有找到!這時馬上用TextOut寫到Texture上(現在還是屬于第1個fps的時間范圍內),而接下來的59個 fps(甚至更多),都不用再調TextOut了,而是直接從我們的Cache:Texture上Copy到屏幕上,速度得到了保障。談到GDI的函數,為了實現設備無關性,它們的速度都很慢。其實它們也不像說得那么慢,如果不是每一幀都要調用它們,也算是蠻快的^_^。那么這個RECT rect,就代表著這個文字所對應在Texture上的區域位置。
  使用什么東西來把這n個Char串起來呢,一般會想到的是鏈表,原因無非有2個:1 隨時有新的字加進來,而內存是不連續的 2 它幾乎沒有容量的限制(除非是內存用完了)。不過鏈表的訪問速度是很慢的,如果使用像數組這樣的東西就好了。仔細想想,在這里,我們用來存儲的 Cache,最大也就是256*256(理由上面說了),所以大小應該會是固定的。我們只需要在數組中的給每一個漢字加上一個標志,說明這個位置的使用情況。那么就使用數組吧,這樣的話,訪問的速度要更快一些,直接首地址+偏移量就夠了,不必像鏈表,在查找時需要逐node訪問。當然,我絕不會想到用 new Char來申請這個數組。因為這樣做實在沒有必要,請不要過于迷信自己的能力,在STL中已經有vector了,為什么還要自己寫呢?^_^最后的一個 bool成員變量isUsing,也就是上面所說,用來標志使用情況的。


實際的操作
  上面考慮了那么多,我認為都是實際操作之前所應該有的。先談談如何顯示吧,因為在DirectX8.1中已經將DirectDraw和 Direct3D融合為DirectGraphics了。所以無法像原來那樣了…………哦,實在有太多東西要講了,我還是推薦幾篇文章給你吧^_^:
  http://vip.6to23.com/mays/develop/directx/200201/Geczy3Din2D.htm
  http://vip.6to23.com/mays/develop/directx/200201/GESurface.htm
  http://vip.6to23.com/mays/develop/directx/200112/2DGtoDX8.htm
  http://vip.6to23.com/mays/develop/directx/200201/DX8adv2D.htm
  接下來,我會假設你已經具備了在DirectX8.1中繪圖的基本概念了,所以在你繼續往下閱讀之前,請務必先仔細閱讀以上推薦的文章。
  前面提到,需要一個vector來對應Texture上各個位置文字的信息,上面已經創建了一個結構Char,則這個vector的定義為:
   vector <Char> _vBuf; // 記錄緩沖中現有的文字情況
  首先,由于可以利用硬件的放大縮小機能,所以字體的大小精度要求不是很高,只需要支持16*16和24*24大小的字體就可以了。我們需要一個這樣的初始化函數:
  bool CFont::
  /*-------------------------------------------------------------
  LPDIRECT3DDEVICE8 pd3dDevice --- D3DDevice設備
  char szFontName[] --- 字體名(如: 宋體)
  int nSize --- 字體大小, 只支持16和24
  int nLevel --- 紋理的大小級別
  -------------------------------------------------------------*/
  Init( LPDIRECT3DDEVICE8 pd3dDevice, char szFontName[], int nSize, int nLevel )。
  
  在DirectX8.1中,由SetTexture(…)所貼的圖的大小,也就是Texture的大小,是有大小限制的,長和寬都必須是2^n,而且位圖越大,所花費的顯存越大,這樣留給其他顯示用的顯存就少了。所以,必須根據需求的不同,來自定Texture(也就是Cache)的大小。因為漢字點陣大小的原因,所以從實用角度而言(比方說只是顯示fps或是短小的標題),開辟一個64*64大小的Texture,才能滿足最低情況下的需要(這時如果選擇16點陣的話可以存放16個漢字,24點陣可以存放7個,依次類推……)。
  根據設置,創建Texture:
   _TextureSize = 32 << nLevel; // 紋理大小
   _TextSize = nSize; // 文字大小
   _TextureSize = 32 << nLevel; // 紋理大小
  
   _RowNum = _TextureSize / _TextSize; // 計算一行可以容納多少個文字
   _Max = _RowNum * _RowNum; // 計算緩沖最大值
  
  創建字體,還是需要使用Win32 API。也就是先創建一個HDC:
   _hDc = CreateCompatibleDC(NULL);
  
  然后創建一個BITMAP和一個FONT,將它們與HDC關聯起來。
   LOGFONT LogFont;
   ZeroMemory( &LogFont, sizeof(LogFont) );
   LogFont.lfHeight = -_TextSize;
   LogFont.lfWidth = 0;
   LogFont.lfEscapement = 0;
   LogFont.lfOrientation = 0;
   LogFont.lfWeight = FW_BOLD;
   LogFont.lfItalic = FALSE;
   LogFont.lfUnderline = FALSE;
   LogFont.lfStrikeOut = FALSE;
   LogFont.lfCharSet = DEFAULT_CHARSET;
   LogFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
   LogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
   LogFont.lfQuality = DEFAULT_QUALITY;
   LogFont.lfPitchAndFamily = DEFAULT_PITCH;
   lstrcpy( LogFont.lfFaceName, szFontName );
  
   _hFont = CreateFontIndirect( &LogFont );
   if ( NULL == _hFont )
   {
   DeleteDC( _hDc );
   return false;
   }
  
  (只需要創建一個字體大小的BITMAP即可)
   BITMAPINFO bmi;
   ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
   bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   bmi.bmiHeader.biWidth = _TextSize;
   bmi.bmiHeader.biHeight = -_TextSize;
   bmi.bmiHeader.biPlanes = 1;
   bmi.bmiHeader.biBitCount = 32;
   bmi.bmiHeader.biCompression = BI_RGB;
  
  (這里需要定義一個指針指向位圖的數據:
   DWORD * _pBits; // 位圖的數據指針)
  
   _hBmp = CreateDIBSection( _hDc, &bmi, DIB_RGB_COLORS,
   (void **) &_pBits, NULL, 0 );
   if ( NULL == _hBmp || NULL == _pBits )
   {
   DeleteObject( _hFont );
   DeleteDC( _hDc );
   return false;
   }
  
   // 將hBmp和hFont加入到hDc
   SelectObject( _hDc, _hBmp );
   SelectObject( _hDc, _hFont );
  
  接著設置背景色和文字色:
   SetTextColor( _hDc, RGB(255,255,255) );
   SetBkColor( _hDc, 0 );
  
  設置文字為上對齊:
   SetTextAlign( _hDc, TA_TOP );
  
  創建Texture所需要的頂點緩沖:
   if ( FAILED( _pd3dDevice->CreateVertexBuffer( _Max * 6 * sizeof(FONT2DVERTEX),
   D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,
   D3DPOOL_DEFAULT, &_pVB ) ) )
   {
   DeleteObject( _hFont );
   DeleteObject( _hBmp );
   DeleteDC( _hDc );
   return false;
   }
  
  創建Texture
   if ( FAILED( _pd3dDevice->CreateTexture( _TextureSize, _TextureSize, 1, 0,
   D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &_pTexture ) ) )
   {
   DeleteObject( _hFont );
   DeleteObject( _hBmp );
   DeleteDC( _hDc );
   SAFE_RELEASE(_pVB);
   return false;
   }
  
  設置渲染設備的渲染屬性:
   _pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
   _pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
   _pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
   _pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE );
   _pd3dDevice->SetRenderState( D3DRS_ALPHAREF, 0x08 );
   _pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL );
   _pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
  
   _pd3dDevice->SetTexture( 0, _pTexture );
   _pd3dDevice->SetVertexShader( D3DFVF_FONT2DVERTEX );
   _pd3dDevice->SetStreamSource( 0, _pVB, sizeof(FONT2DVERTEX) );
  
  設置緩沖的最大容量
   _vBuf.resize( _Max );
  
  這樣,初始化完成了。接下來是如何把一個漢字寫到Texture中,以及如何進行管理。定義函數:
  // 得到文字在紋理中的位置
  void CFont::
  /*-------------------------------------------------------------
  char c1 --- 文字的第1個字節
  char c2 --- 文字的第2個字節
  int & tX --- 寫入紋理中的坐標x
  int & tY --- 寫入紋理中的坐標y
  -------------------------------------------------------------*/
  Char2Texture( char c1, char c2, int & tX, int & tY )
  {
   WORD w = MAKEWORD(c1, c2); // 把此字變為WORD
   vector<Char>::iterator it = find( _vBuf.begin(), _vBuf.end(), w );
   if ( it == _vBuf.end() ) // 如果沒找到
   {
   it = find( _vBuf.begin(), _vBuf.end(), 0 ); // 查找空閑位置
   if ( it == _vBuf.end() ) // 緩沖已滿
   {
   for(; it!=_vBuf.begin(); it-- )
   {
   it->hz = 0;
   }
  // Log.Output( "字體緩沖已滿, 清空!" );
   }
  
   // 計算當前空閑的Char在緩沖中是第幾個
   int at = it-_vBuf.begin();
  
   // 得到空閑位置的坐標
   tX = (at % _RowNum) * _TextSize;
   tY = (at / _RowNum) * _TextSize;
  
   // 設置這個Char為使用中
   (*it).hz = w;
  
   RECT rect = {0, 0, _TextSize, _TextSize};
   char sz[3] = {c1, c2, '\0'};
   // 填充背景為黑色(透明色)
   FillRect( _hDc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );
   // 往hBitmap上寫字
   ::TextOut( _hDc, 0, 0, sz, c1 & 0x80 ? 2 : 1 );
  
   // 鎖定表面, 把漢字寫入紋理, 白色的是字(可見), 黑色為背景(透明)
   D3DLOCKED_RECT d3dlr;
   _pTexture->LockRect(0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
   BYTE * pDstRow = (BYTE*)( (WORD *)d3dlr.pBits + tY * _TextureSize + tX );
  
   for (DWORD y=0; y<_TextSize; y++)
   {
   WORD * pDst16 = (WORD*)pDstRow;
   for (DWORD x=0; x<_TextSize; x++)
   {
   BYTE bAlpha = (BYTE)((_pBits[_TextSize * y + x] & 0xff) >> 4);
   if (bAlpha > 0)
   *pDst16++ = (bAlpha << 12) | 0x0fff;
   else
   *pDst16++ = 0x0000;
   }
   pDstRow += d3dlr.Pitch;
   }
   _pTexture->UnlockRect( NULL );
   }
   else
   {
   // 計算當前空閑的Char在緩沖中是第幾個
   int at = it-_vBuf.begin();
  
   // 得到這個字的坐標
   tX = (at % _RowNum) * _TextSize;
   tY = (at / _RowNum) * _TextSize;
   }
  }
  以上代碼中的注釋已經很清楚了,相信無須我多言。這里唯一需要聲明的是:原來所定義的Char結構是這樣的
  struct Char{
   char hz[3]; // 保存漢字
   int frequency;// 使用頻率
   RECT rect; // 這個字對應位圖的區域
   Bool isUsing; // 是否使用
  }
  后來因為將char hz[3]合成為WORD,所以改為WORD hz。然后對于int frequency,這個詞頻應該如何表現,我一直沒有想到很好的方法。frequency應該在何時++呢?是在每次被使用的時候嗎?但是這樣的話,上面說過,游戲是以60fps的速度在刷新,如果停上1分鐘的話,變量很快就會溢出了。就算是使用像是DWORD或__int64這樣的巨型變量保存,也是不安全的。除非能在某個合適的時候將frequency清零,但是這個“時候”是什么時候呢?或者設置一個最大值,如65535,但是這樣也基本上沒什么用途,很快,所有在vector中的Char中的frequency都會++成65535的。回憶一下最初,是因為想把常用字放到vector的前面,以便每次find操作可以最快返回結果的。而經過我的測試,即使不做這樣的優化操作,速度也是很快的,畢竟Cache不是很大,加上vector是連續內存空間。所以可以放棄使用int frequency。
  然后對于RECT rect,因為沒有了int frequency,意味著一旦將漢字寫入到Texture,其位置就不會變動了。所以,很容易根據find函數操作后的iterator,直接計算出這個漢字所在Texture的位置。這樣,RECT rect也不再必須。
  而bool isUsing,它本身就是個雞肋,要也可以,這樣結構更加清晰。不過,直接通過觀察WORD hz為0或非0,即可實現isUsing的作用了。
  為什么要對結構Char這么精雕細琢呢?
1. 既然沒有必要的東西,就應該刪除
2. Char結構的大小越大,vector所要求的內存越大
3. 小的結構,find可以更快地查找出所結果
  為什么find會正常工作呢?這里我要大概地講一下find是如何查找出所需的位置的:它只是簡單地使用while從vector的begin一直遍歷到end,逐個判斷,直到找到為止。find要求必須實現自己的operator ==(),進一步跟蹤到find的源碼中,發現也是這樣。于是前面的結構Char變成了現在這樣:
   struct Char{
   WORD hz; // 文字
  
   Char() : hz(0) {}
  
   // 用作查找文字
   inline bool operator == ( WORD h ) const
   {
   return hz==h ? true : false;
   }
   };
  是不是很簡單?^___^
  
  終于到了顯示的函數了:
  // 得到文字在紋理中的位置
  bool CFont::
  /*-------------------------------------------------------------
  char szText[] --- 顯示的字符串
  int x --- 屏幕坐標x
  int y --- 屏幕坐標y
  D3DCOLOR --- 顏色及alpha值
  int nLen --- 字符串長度
  float fScale --- 放大比例
  -------------------------------------------------------------*/
  TextOut( char szText[], int x, int y, D3DCOLOR color, int nLen, float fScale )
  {
   Assert( szText!=NULL );
  
   float sx = x, sy = y,
   offset=0, w=0, h=0, tx1=0, ty1=0, tx2=0, ty2=0;
   w = h = (float)_TextSize * fScale;
  
   char ch[3] = {0,0,0};
   FONT2DVERTEX * pVertices = NULL;
   UINT wNumTriangles = 0;
   _pVB->Lock(0, 0, (BYTE**)&pVertices, D3DLOCK_DISCARD);
  
   if ( -1 == nLen || // 默認值-1
   nLen > lstrlen( szText ) ) // 如果nLen大于字符串實際長度, 則nLen=實際長度
   nLen = lstrlen( szText );
   for (int n=0; n<nLen; n++ )
   {
   ch[0] = szText[n];
  
   if ( ch[0]=='\n' )
   {
   sy+=h;
   sx=x;
   continue;
   }
  
   if ( ch[0] & 0x80 )
   {
   n++;
   ch[1] = szText[n];
   offset = w;
   }
   else
   {
   ch[1] = '\0';
   offset = w / 2 ;
   }
  
   int a, b;
   Char2Texture( ch[0], ch[1], a, b );
  
   // 計算紋理左上角 0.0-1.0
   tx1 = (float)(a) / _TextureSize;
   ty1 = (float)(b) / _TextureSize;
   // 計算紋理右上角 0.0-1.0
   tx2 = tx1 + (float)_TextSize / _TextureSize;
   ty2 = ty1 + (float)_TextSize / _TextureSize;
  
   // 填充頂點緩沖區
   *pVertices++ = FONT2DVERTEX(sx, sy + h, 0.9f, color, tx1, ty2);
   *pVertices++ = FONT2DVERTEX(sx, sy, 0.9f, color, tx1, ty1);
   *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f, color, tx2, ty2);
   *pVertices++ = FONT2DVERTEX(sx + w, sy, 0.9f, color, tx2, ty1);
   *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f, color, tx2, ty2);
   *pVertices++ = FONT2DVERTEX(sx, sy, 0.9f, color, tx1, ty1);
  
   wNumTriangles+=2;
  
   sx+=offset; // 坐標x增量
   }
   _pVB->Unlock();
   _pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, wNumTriangles );
  
   return true;
  }


結束語
  記得有一句名言: Keep it simple and stupid.在實現功能的同時,保持代碼簡單、清晰是非常重要的一件事。相信在往后的日子里,在不論是別人閱讀或是你自己回顧的時候,你都會發現一如既往地遵守這個守則,是多么得重要!
  相信通過上面我那無數的廢話,加上代碼中還算足夠的注釋,聰明的你一定能夠明白這其中的原理了吧。如果以上的內容還不足以讓你完全搞清楚的話,你可以登錄我的主頁:
  

炎龍工作室
  上面不僅包括了上面所寫的程序代碼,還有一個用來演示效果的一個很簡單的demo。
  說明,以上所實現的CFont是包含在我的游戲引擎中的一個部件,而目前已經實現的部件包括有:
1. CGameFrame(游戲框架類) ----- 封裝了窗口及D3D設備的建立,需要派生出自己的子類
2. CAudio和CSound(聲音類) ----- 支持wav/mid/mp3的播放
3. CDirectInput(控制類) ----- 鍵盤、鼠標操作
4. CDirectShow(視頻類) ----- 支持avi/mpg/mov等的播放
5. CSpriteX(精靈類) ----- 方便游戲中對精靈的控制
6. CFont(字體類) ----- 中英文字體的顯示
7. CTimer(時間類) ----- 高精度時間的控制
8. FPS(fps 類) ----- fps的計算
9. LOG(日志類) ----- 游戲中的錯誤反應以及狀態記錄
  最重要的是,這個Game Engine完全是開放源代碼的。關于更新的情況、版本說明以及源碼下載,請隨時關注我的主頁!
接下來,我將會繼續完善這個Engine,可能加入的有:高效粒子系統、斜45度角地圖……

posted on 2010-02-28 20:19 Code Knight 閱讀(2843) 評論(0)  編輯 收藏 引用 所屬分類: GUI

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美日韩精品一区二区天天拍小说 | 亚洲成人在线视频播放| 日韩午夜高潮| 91久久久久久久久| 亚洲人妖在线| 99精品欧美一区二区三区 | 性欧美xxxx视频在线观看| 欧美一区午夜精品| 老司机午夜精品视频在线观看| 你懂的网址国产 欧美| 亚洲国产欧美另类丝袜| 亚洲视频欧美视频| 欧美一区二区私人影院日本| 久久一区二区三区四区| 欧美日韩国产色综合一二三四| 国产精品日韩欧美| 亚洲国产精品ⅴa在线观看| 亚洲视频大全| 久久亚洲免费| 在线视频亚洲| 免费不卡亚洲欧美| 国产精品影视天天线| 亚洲欧洲精品一区二区| 亚洲免费中文| 欧美wwwwww| 亚洲欧美乱综合| 欧美极品在线播放| 国产日韩精品在线播放| 在线视频精品| 欧美www视频在线观看| 亚洲一区二区三区视频播放| 欧美jizz19性欧美| 国产日韩欧美一区二区三区四区| 日韩亚洲欧美综合| 欧美aⅴ一区二区三区视频| 午夜亚洲福利| 欧美午夜不卡在线观看免费| 亚洲黄网站在线观看| 久久国产夜色精品鲁鲁99| 99热在线精品观看| 欧美11—12娇小xxxx| 国产专区一区| 欧美中文字幕精品| 亚洲一区二区三区色| 欧美日韩在线观看视频| 亚洲免费成人av电影| 美女国产一区| 久久久久综合网| 亚洲欧美中文在线视频| 欧美久久九九| 日韩一级视频免费观看在线| 欧美肥婆bbw| 久久夜色精品国产欧美乱极品| 国产色爱av资源综合区| 欧美综合国产精品久久丁香| 亚洲一区二区三区在线播放| 欧美午夜精品久久久久久浪潮 | 久久久噜噜噜久久中文字幕色伊伊| 国产精品日韩精品| 欧美资源在线观看| 欧美一级久久| 激情av一区二区| 蜜臀av在线播放一区二区三区 | 国产一区二区三区久久悠悠色av| 亚欧成人精品| 欧美影院午夜播放| 一区二区三区中文在线观看| 牛人盗摄一区二区三区视频| 免费视频一区| 中日韩高清电影网| 亚洲一区在线直播| 精品粉嫩aⅴ一区二区三区四区| 久久精品一级爱片| 乱中年女人伦av一区二区| 亚洲黄色av一区| 日韩一级片网址| 国产日韩欧美中文在线播放| 久久综合国产精品台湾中文娱乐网| 久久久久久国产精品mv| 亚洲精品小视频| 亚洲一区二区不卡免费| 黄色日韩网站| 亚洲日本中文字幕| 欧美亚洲第一区| 久久五月天婷婷| 欧美日产一区二区三区在线观看| 亚洲欧美日韩在线不卡| 久久永久免费| 亚洲综合另类| 久久综合影视| 欧美一二三区在线观看| 欧美v日韩v国产v| 欧美一区二区三区男人的天堂 | 欧美国产视频日韩| 欧美一区二区三区在线视频| 久久欧美中文字幕| 亚洲欧美日韩在线不卡| 欧美成人精品高清在线播放| 欧美在线精品一区| 欧美日韩国产系列| 欧美不卡一卡二卡免费版| 国产精品老女人精品视频| 欧美成人综合网站| 国产婷婷一区二区| 一区二区三区视频在线| 亚洲精品美女在线观看| 久久精品免费电影| 国产精品久久久久久久久久免费看 | 一本到12不卡视频在线dvd| 原创国产精品91| 亚洲一区二区综合| 99国产精品久久久| 美女视频网站黄色亚洲| 久久午夜电影| 国产一区二区三区奇米久涩| 亚洲小说区图片区| 亚洲深夜福利网站| 欧美激情无毛| 亚洲高清久久网| 亚洲国产精品va在线看黑人动漫| 欧美中文字幕在线观看| 欧美在线观看视频在线 | 国产亚洲欧美日韩在线一区| 一本色道久久综合一区| 一本大道久久a久久综合婷婷| 老鸭窝91久久精品色噜噜导演| 久久九九99| 国产欧亚日韩视频| 亚洲嫩草精品久久| 欧美伊人久久大香线蕉综合69| 国产精品黄色| 亚洲一区二区少妇| 羞羞答答国产精品www一本| 国产精品夜色7777狼人| 亚洲欧美日韩天堂一区二区| 午夜亚洲视频| 国产麻豆日韩欧美久久| 欧美一区亚洲| 久久婷婷久久| 亚洲人成久久| 欧美视频在线观看免费网址| 一区二区高清视频在线观看| 亚洲在线观看免费视频| 国产精品一区二区三区四区| 性8sex亚洲区入口| 噜噜噜躁狠狠躁狠狠精品视频| 在线电影欧美日韩一区二区私密| 久久亚洲图片| 亚洲美女免费精品视频在线观看| 亚洲午夜激情网页| 国产女人18毛片水18精品| 久久狠狠亚洲综合| 欧美成人综合网站| 亚洲欧美日本伦理| 激情久久婷婷| 欧美理论大片| 亚洲午夜精品久久久久久app| 久久精品动漫| 亚洲人精品午夜| 国产精品萝li| 麻豆91精品| 亚洲一区视频在线观看视频| 欧美aa国产视频| 亚洲在线国产日韩欧美| 国产亚洲综合精品| 欧美激情国产精品| 亚洲欧美久久| 亚洲福利视频专区| 午夜在线a亚洲v天堂网2018| 在线播放国产一区中文字幕剧情欧美| 欧美日韩成人综合天天影院| 午夜精品久久久久久99热软件| 久久亚洲国产成人| 国产精品视频区| 蜜桃久久av一区| 欧美一区二区高清| 日韩一级精品| 欧美黄在线观看| 午夜精品久久久久久99热软件| 极品少妇一区二区三区精品视频| 欧美日韩成人一区二区三区| 久久xxxx精品视频| 99国产精品私拍| 欧美激情精品久久久久久久变态 | 国产综合久久| 欧美视频不卡| 欧美国产三区| 老司机一区二区| 亚洲欧美精品| 99视频精品在线| 亚洲日本成人网| 亚洲高清视频在线观看| 另类专区欧美制服同性| 午夜激情一区| 亚洲天堂av在线免费观看| 亚洲精品久久久久久一区二区| 一区二区三区在线观看国产| 国产又爽又黄的激情精品视频| 国产精品久久一卡二卡| 欧美日韩国产美女|