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

游戲中漢字顯示的實(shí)現(xiàn)與技巧

Posted on 2008-10-13 09:12 RichardHe 閱讀(2706) 評(píng)論(4)  編輯 收藏 引用 所屬分類(lèi): D3D

游戲中漢字顯示的實(shí)現(xiàn)與技巧[ZZ]

作者:炎龍工作室 千里馬肝

版本:v1.0

最后更新日期:2002-3-30

緒言

在游戲中,因?yàn)槲覀兪侵袊?guó)人麻,通常都需要顯示漢字,比方說(shuō)交待劇情。而對(duì)于文字的顯示,英文的顯示要較其簡(jiǎn)單得多,因?yàn)橹挥?/span>26個(gè)字母,就算再加一些標(biāo)點(diǎn)、符號(hào)什么的,用一張位圖,就可以足以顯示所有的單詞了,而相關(guān)實(shí)現(xiàn)技巧,也比較輕松。clip_image002

而中文的顯示方法,要復(fù)雜得許多。記得原來(lái)在DOS下,漢字的顯示都是讀的UCDOS的點(diǎn)陣字庫(kù),而點(diǎn)陣字庫(kù)的讀取方法,在UCDOS SDK中都有源代碼可以參考。但是自從Windows操作系統(tǒng)開(kāi)始,我們開(kāi)始了解到一種更好的字庫(kù),它就是TTF

注:以下我所指的開(kāi)發(fā)環(huán)境,除非明確說(shuō)明,默認(rèn)的平臺(tái)是VC6.0+DirectX8.1,使用D3D來(lái)加速2D。然后使用的STL是用的SGI實(shí)現(xiàn)的那一套STL

點(diǎn)陣字庫(kù)

包括現(xiàn)在,有很多游戲都還是使用的點(diǎn)陣字庫(kù)。因?yàn)椴僮髌饋?lái)比較方便,加上這方面的經(jīng)驗(yàn)已經(jīng)積累了好幾年了。通常如果只是一種字體就可以滿足需要的話,它會(huì)是一個(gè)比較好、快的解決辦法。但是它有3個(gè)缺點(diǎn):

1.   如果放大顯示,不做處理的話,顯示出來(lái)的漢字,是很難看的。

2.   像是UCDOS所提供的點(diǎn)陣字庫(kù),只有24點(diǎn)陣的有幾種字體,如:宋體、黑體、揩體…,而16點(diǎn)陣的好象就只有宋體一種。

3.   點(diǎn)陣字庫(kù),通常是有版權(quán)的,尤其是第三方制作的漢字庫(kù)(如:方正)。

在 這樣的情況下,當(dāng)我們寫(xiě)好這樣的一個(gè)顯示函數(shù),就算是解決了如:放大、快速顯示等問(wèn)題的話,可供選擇的字體還是太過(guò)于局限了。所以,在字體的要求比較強(qiáng)的 情況下,點(diǎn)陣字庫(kù)并不是一個(gè)好的解決方法,他不夠靈活。盡管我們對(duì)于它的操作是如此得熟練,可以寫(xiě)出優(yōu)美的代碼來(lái)展示我們的編程技巧。

TTF

TTFTrue Type Font的簡(jiǎn)稱(chēng)。在Windows\Fonts目錄下面,我們可以看到許多后綴為ttf的文件,它就是接下來(lái)我們接下來(lái)所要談到的。

TTF是一種矢量字庫(kù)。我們經(jīng)常可以聽(tīng)到矢量這個(gè)詞,像是FLASH中的矢量圖形,在100*100分辨率下制作的flash,就算它放大為全屏,顯示出的畫(huà)面也不會(huì)出現(xiàn)馬賽克。所謂矢量,其實(shí)說(shuō)白了就是用點(diǎn)和線來(lái)描述圖形,這樣,在圖形需要放大的時(shí)候,只要把所有這個(gè)圖形的點(diǎn)和線放大相應(yīng)的倍數(shù)就可以了。而且,在網(wǎng)站上有很多的TTF字庫(kù)可以下載,或者你可以去買(mǎi)一些專(zhuān)門(mén)的字庫(kù)光盤(pán)。然后在你發(fā)行你精心制作的游戲時(shí),可以順便捎上這些后綴為.ttf的文件就行了。包括Quake這樣的驚世之作,也都是用的TTF字庫(kù)。

這樣,我們就可以解決點(diǎn)陣漢字的一些問(wèn)題。通過(guò)TTF,我們?cè)谧煮w的質(zhì)量和字庫(kù)的數(shù)量上獲得了暫時(shí)性的勝利。

字庫(kù)的讀取和顯示

先前談到點(diǎn)陣字庫(kù),只需要很簡(jiǎn)單的一些操作,就可以顯示出想要的漢字。下面我給出一個(gè)讀取hzk16的函數(shù),它需要一個(gè)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);

}

其實(shí)原理很簡(jiǎn)單:

1.         打開(kāi)字庫(kù)

2.         計(jì)算字符串長(zhǎng)度(這個(gè)函數(shù)只支持中文),并且Lock Surface

3.         依次計(jì)算出每個(gè)漢字所對(duì)應(yīng)的區(qū)碼和位碼(漢字的第1個(gè)字節(jié)是區(qū)碼,第2個(gè)字節(jié)就是位碼),然后通過(guò)公式計(jì)算出這個(gè)漢字在字庫(kù)中的偏移量:
offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;

4.         讀出一個(gè)32個(gè)字節(jié)的點(diǎn)陣

5.         繪制到Surface

以上只是1616點(diǎn)陣字庫(kù)的顯示方法,2424的讀取方法與之類(lèi)似,大家可以參照相關(guān)資料來(lái)書(shū)寫(xiě)出自己的代碼。

 

如何顯示TTF字庫(kù)呢,有很多種手段,下面我按從簡(jiǎn)單到復(fù)雜的的順序依次介紹:

1.   使用Windows API,也就是大家所熟悉的TextOut。通過(guò)它,還需要一個(gè)HDC(設(shè)備句柄),我們就可以隨意地在屏幕任何地方顯示出文字了。

2.   http://www.freetype.org,有一個(gè)FreeType的免費(fèi)庫(kù),而且是OpenSource的。它目前有2個(gè)版本:1.02.0。其區(qū)別在于,1.0只能讀取TTF格式的,而2.0支持更多的文件格式,在使用它之前請(qǐng)?jiān)敿?xì)閱讀所要遵循的Licence,以下是摘自FreeType2.0對(duì)字庫(kù)的支持列表:

    • TrueType fonts (and collections)
    • Type 1 fonts
    • CID-keyed Type 1 fonts
    • CFF fonts
    • OpenType fonts (both TrueType and CFF variants)
    • SFNT-based bitmap fonts
    • X11 PCF fonts
    • Windows FNT fonts

3.   自己研究TTF的格式,然后自己來(lái)操作。

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

 

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

 

DirectDraw時(shí)代,我們都不自覺(jué)地喜歡上了GetDC,因?yàn)?#8230;…多方便啊。可是現(xiàn)在已經(jīng)到了DirectX8.1時(shí)代了(我要使勁地?fù)u那些還沉醉于DirectX7中,為如何在使用alpha時(shí)提升那可憐的12個(gè)fps的朋友們:醒醒,該起床了!),HDC已經(jīng)被M$列為禁用品。怎么辦呢?是的,你可能已經(jīng)想到了,我們還一直保存著窗口的hWnd呢,可以通過(guò)它來(lái)得到hdc,從而調(diào)用那些需要hdcAPI,可是,這樣做是更為愚蠢的,這樣對(duì)你是沒(méi)有一點(diǎn)好處的,不信,你就試試吧。有一句話,請(qǐng)牢記:要想你的游戲有更快的速度的話,請(qǐng)不要再去碰HDC了。

我們非常清楚hdc是一個(gè)超慢的解決辦法,它無(wú)法在我們的高速游戲中滿60分及格。下面來(lái)看看FreeType,它更像是一個(gè)Service。它的解決方法是,先通過(guò)一系列的初始化和設(shè)置,告訴FreeType字體的名字和大小等,然后它會(huì)動(dòng)態(tài)地申請(qǐng)一個(gè)Graphic,再把我們要顯示的字畫(huà)到這個(gè)Graphic上,你還可以把它保存為tga格式。不過(guò)我們最終所想要的不是這個(gè),所以可能我們還需要從這個(gè)Graphic上逐點(diǎn)讀取或者用CopyRect,然后再畫(huà)到我們的畫(huà)面上。其實(shí)它已經(jīng)是很方便的了,可是需要你去學(xué)習(xí)如何配置和使用它,這是很花時(shí)間的一件事情,而且它最大的優(yōu)點(diǎn)是可以跨平臺(tái),我們需要它嗎?如果有一個(gè)更為簡(jiǎn)單的辦法,像是如果Textout不是那么慢的話,就好了……

在這里,順便談一下另2個(gè)字體顯示類(lèi):ID3DXFontCD3DFONT。可能早就有人會(huì)說(shuō)怎么在上面的列表中沒(méi)有它們?原因我會(huì)在下面慢慢地說(shuō)明:

ID3DXFont,它存在于D3DX庫(kù)中,一個(gè)現(xiàn)成的字體類(lèi),不過(guò)對(duì)于它的處理方法……我實(shí)在不敢恭維,就引用一位大師所說(shuō)的話來(lái)表達(dá)我的看法吧: 在內(nèi)部實(shí)現(xiàn)中, ID3DXFont::DrawText()函數(shù)確實(shí)做了我上面討論的工作,先建立一張GDI兼容的位圖,把文本繪制到位圖上,而后把位圖拷貝到紋理貼圖上去,最后把紋理渲染到屏幕上。這樣你就聚齊了所有的龜速的原始GDI函數(shù),還包括了一大堆的額外開(kāi)銷(xiāo) — 最終,這個(gè)函數(shù)比原來(lái)GDIDrawTextEx()函數(shù)要慢上超過(guò)六倍……

CD3DFONT,是由M$D3D的框架代碼中提供。不過(guò)它只能顯示英文,有很多朋友通過(guò)自己定制和修改這個(gè)類(lèi),來(lái)實(shí)現(xiàn)自己的中文顯示。不過(guò)效果都不是很好。其實(shí)原理,跟ID3DXFont的方法差不多,不過(guò)處理方法要聰明了一點(diǎn)。

分析與思考

那么我們應(yīng)該怎么辦呢?通常我們會(huì)幻想,如果可以像處理英文那樣,把所有的漢字都保存在一張位圖里,該有多好。這樣,顯示的速度就不是問(wèn)題了,直接可以CopyRect上去。可是,這樣可能嗎?首先,必須每一種字體都要生成這樣的一個(gè)巨型位圖。而且據(jù)說(shuō)在GB2312中,一共有6000多個(gè)漢字,就算是用16*16oh my god,這個(gè)位圖該有多大啊(據(jù)說(shuō)會(huì)有2.5M^__^)!!!而且在DirectX8.1中,對(duì)于Texture(顯示的最小單位,就好象是原來(lái)DirectSurface的概念一樣。說(shuō)過(guò)多少遍了,不要再用DirectX8以前的東西了。不要試著去回憶那些美好的過(guò)去,我很明白,要你一下子放棄原來(lái)多年所獲得的成就,是一件很痛苦的事情,但是包袱太重,是會(huì)影響進(jìn)步的。就像是我們的國(guó)家……扯遠(yuǎn)了),不同的顯卡,支持的最大容量也是不同的。比方說(shuō)早期的Voodoo,只支持256*256大小的Texture。而在我的顯卡(Geforce2 MX 200)上測(cè)試,支持最大2048*2048大小的Texture。對(duì)于這樣的硬件不確定性,我們只能取其最小值,也就是256*256

漢字雖然很多,但是常用的漢字,其實(shí)也就只有那么幾百個(gè)。像這樣的字:鬯、鞴,你一輩子會(huì)看到多少次呢?如果可以做一個(gè)類(lèi)似于Cache的東西,保存著常用的那些個(gè)漢字,在需要顯示的的時(shí)候,先在Cache中查找,如果有的話,就馬上畫(huà)上去;如果沒(méi)有,就從字庫(kù)中提取到Cache中。這樣的話,在使用Texture來(lái)保存漢字的位圖信息的同時(shí),對(duì)于每個(gè)漢字,我們還要定義一個(gè)結(jié)構(gòu),然后用一個(gè)東西把它串起來(lái),綜合它們2個(gè),也就實(shí)現(xiàn)了我們所要的Cache了。剛開(kāi)始,我所定義的結(jié)構(gòu)是這樣的:

struct Char{

  char hz[3];   // 保存漢字

  int frequency;// 使用頻率

  RECT rect;    // 這個(gè)字對(duì)應(yīng)位圖的區(qū)域

  Bool isUsing; // 是否使用

}

對(duì)于漢字和英文,我在這里大概地講一下原理:漢字是由2個(gè)字節(jié)保存,而英文只需要1個(gè)。而判斷一個(gè)字是否是漢字,只需判斷第1個(gè)byte是否>128(在原來(lái)的GB2312中,漢字的2個(gè)字節(jié)都是>128的。而新的GBK字庫(kù),漢字的第2個(gè)字節(jié)不一定>128,我想這是擴(kuò)大了字庫(kù)容量的原因。我的意思是說(shuō),如果給一個(gè)字符串你,隨機(jī)給其中一個(gè)位置,然后我問(wèn)你這個(gè)位置是什么?你的回答只能是:1 英文 2 漢字的首字節(jié) 3 漢字的尾字節(jié)。而這個(gè)問(wèn)題的解法,為了穩(wěn)妥起見(jiàn),你必須從字符串的開(kāi)始判斷起)。也就是說(shuō)在char[3]中,如果保存的是漢字,則char[0]保存漢字第1個(gè)字節(jié),char[1]保存漢字第2個(gè)字節(jié),第3個(gè)存放’\0’;如果是英文的話,則只用到char[0],其它的全部為’\0’

接下來(lái),對(duì)于使用char[3]來(lái)保存漢字,是否真的很合適呢?因?yàn)槿绻阉?dāng)作一個(gè)字符串來(lái)看的話,在查找時(shí)就需要使用 strcmp 來(lái)比較字符串了,這樣一定是會(huì)影響速度的。如果不把它看作字符串(字符串的最后一個(gè)字節(jié)需要以’\0’結(jié)尾),只用char[2]的話,我們可以只是簡(jiǎn)單地調(diào)用宏MAKEWORD,把2個(gè)byte壓成1個(gè)WORD。當(dāng)把文字作為一個(gè)WORD來(lái)看的時(shí)候,這樣查找比較時(shí)可以用WORD內(nèi)建的==操作,這樣要比調(diào)用strcmp函數(shù)要快得多。

int frequency用來(lái)標(biāo)志每個(gè)WORD的使用頻率。設(shè)想,如果一個(gè)字已經(jīng)存在于Cache中,以后每對(duì)它調(diào)用一次,就讓frequency++。這樣做還有一個(gè)用意是,是否可以在一個(gè)合適的時(shí)候,以frequency為參照來(lái)對(duì)這整個(gè)Cache排個(gè)序,把常用的字放在前面。那么在顯示時(shí),可以先在Cache中查找所要顯示的字是否已經(jīng)存在于Cache中,如果有則直接顯示,沒(méi)有的話才需要采取某種手段將字加入到Cache中。一些常用的字(像:我、的、著、了、過(guò)……),使得顯示的速度將會(huì)大大提高。

其實(shí)上面說(shuō)了半天的Cache,它具體是什么呢?其實(shí)就是指的最小繪制單位,在DirectX7里是Surface,而在DirectX8中就是Texture。使用它來(lái)存放顯示過(guò)的漢字,這樣,就不用每次都從字庫(kù)中讀取或是調(diào)用如TextOut這類(lèi)GDI超慢的函數(shù)了。因?yàn)槊看卧诶L制一個(gè)文字之前,都會(huì)先在這個(gè)Cache中找,有的話就直接畫(huà)上去,沒(méi)有才會(huì)調(diào)用TextOut操作。而這樣做的原因,我們先設(shè)想一下:游戲一般會(huì)控制為30fps或是60fps的速度不停地刷新,如果在GameLoop中有任何的代碼是龜速級(jí)的話,這樣就會(huì)導(dǎo)致fps的最大數(shù)的降低,也就意味著在保證30fps60fps的同時(shí),能繪制到屏幕上的物體的數(shù)量減少了。這就是我們?yōu)槭裁匆褂?span lang="EN-US">Texture來(lái)作為Cache的實(shí)現(xiàn)的原因。再一個(gè),文字在屏幕上顯示時(shí)一般會(huì)保持一段時(shí)間,這個(gè)時(shí)間可能是1-3秒,我們的游戲也就會(huì)相應(yīng)地更新60fps180fps,這是因?yàn)槿藗冃枰喿x它們。或者是一些如標(biāo)題這樣的文字,它們總是不會(huì)更新的,或是更新得很慢。我們完全可以在第一時(shí)間,比方說(shuō)我們的畫(huà)面有60fps,在第1個(gè)fps時(shí),我們得知要顯示文字,然后先在Cache中找,結(jié)果很糟:沒(méi)有找到!這時(shí)馬上用TextOut寫(xiě)到Texture上(現(xiàn)在還是屬于第1個(gè)fps的時(shí)間范圍內(nèi)),而接下來(lái)的59個(gè)fps(甚至更多),都不用再調(diào)TextOut了,而是直接從我們的CacheTextureCopy到屏幕上,速度得到了保障。談到GDI的函數(shù),為了實(shí)現(xiàn)設(shè)備無(wú)關(guān)性,它們的速度都很慢。其實(shí)它們也不像說(shuō)得那么慢,如果不是每一幀都要調(diào)用它們,也算是蠻快的^_^。那么這個(gè)RECT rect,就代表著這個(gè)文字所對(duì)應(yīng)在Texture上的區(qū)域位置。

使用什么東西來(lái)把這n個(gè)Char串起來(lái)呢,一般會(huì)想到的是鏈表,原因無(wú)非有2個(gè):1 隨時(shí)有新的字加進(jìn)來(lái),而內(nèi)存是不連續(xù)的 2 它幾乎沒(méi)有容量的限制(除非是內(nèi)存用完了)。不過(guò)鏈表的訪問(wèn)速度是很慢的,如果使用像數(shù)組這樣的東西就好了。仔細(xì)想想,在這里,我們用來(lái)存儲(chǔ)的Cache,最大也就是256*256(理由上面說(shuō)了),所以大小應(yīng)該會(huì)是固定的。我們只需要在數(shù)組中的給每一個(gè)漢字加上一個(gè)標(biāo)志,說(shuō)明這個(gè)位置的使用情況。那么就使用數(shù)組吧,這樣的話,訪問(wèn)的速度要更快一些,直接首地址+偏移量就夠了,不必像鏈表,在查找時(shí)需要逐node訪問(wèn)。當(dāng)然,我絕不會(huì)想到用new  Char來(lái)申請(qǐng)這個(gè)數(shù)組。因?yàn)檫@樣做實(shí)在沒(méi)有必要,請(qǐng)不要過(guò)于迷信自己的能力,在STL中已經(jīng)有vector了,為什么還要自己寫(xiě)呢?^_^最后的一個(gè)bool成員變量isUsing,也就是上面所說(shuō),用來(lái)標(biāo)志使用情況的。

實(shí)際的操作

上面考慮了那么多,我認(rèn)為都是實(shí)際操作之前所應(yīng)該有的。先談?wù)勅绾物@示吧,因?yàn)樵?span lang="EN-US">DirectX8.1中已經(jīng)將DirectDrawDirect3D融合為DirectGraphics了。所以無(wú)法像原來(lái)那樣了…………哦,實(shí)在有太多東西要講了,我還是推薦幾篇文章給你吧^_^

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

接下來(lái),我會(huì)假設(shè)你已經(jīng)具備了在DirectX8.1中繪圖的基本概念了,所以在你繼續(xù)往下閱讀之前,請(qǐng)務(wù)必先仔細(xì)閱讀以上推薦的文章。

前面提到,需要一個(gè)vector來(lái)對(duì)應(yīng)Texture上各個(gè)位置文字的信息,上面已經(jīng)創(chuàng)建了一個(gè)結(jié)構(gòu)Char,則這個(gè)vector的定義為:

    vector <Char> _vBuf;               // 記錄緩沖中現(xiàn)有的文字情況

首先,由于可以利用硬件的放大縮小機(jī)能,所以字體的大小精度要求不是很高,只需要支持16*1624*24大小的字體就可以了。我們需要一個(gè)這樣的初始化函數(shù):

bool CFont::

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

LPDIRECT3DDEVICE8 pd3dDevice  ---  D3DDevice設(shè)備

char szFontName[]          ---  字體名(: 宋體)

int nSize                  ---  字體大小, 只支持1624

int nLevel                 ---  紋理的大小級(jí)別

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

Init( LPDIRECT3DDEVICE8 pd3dDevice, char szFontName[], int nSize, int nLevel )

 

DirectX8.1中,由SetTexture(…)所貼的圖的大小,也就是Texture的大小,是有大小限制的,長(zhǎng)和寬都必須是2^n,而且位圖越大,所花費(fèi)的顯存越大,這樣留給其他顯示用的顯存就少了。所以,必須根據(jù)需求的不同,來(lái)自定Texture(也就是Cache)的大小。因?yàn)闈h字點(diǎn)陣大小的原因,所以從實(shí)用角度而言(比方說(shuō)只是顯示fps或是短小的標(biāo)題),開(kāi)辟一個(gè)64*64大小的Texture,才能滿足最低情況下的需要(這時(shí)如果選擇16點(diǎn)陣的話可以存放16個(gè)漢字,24點(diǎn)陣可以存放7個(gè),依次類(lèi)推……)。

根據(jù)設(shè)置,創(chuàng)建Texture

    _TextureSize = 32 << nLevel;       // 紋理大小

    _TextSize  = nSize;            // 文字大小

    _TextureSize = 32 << nLevel;       // 紋理大小

   

    _RowNum = _TextureSize / _TextSize;    // 計(jì)算一行可以容納多少個(gè)文字

    _Max = _RowNum * _RowNum;          // 計(jì)算緩沖最大值

 

創(chuàng)建字體,還是需要使用Win32 API。也就是先創(chuàng)建一個(gè)HDC

    _hDc = CreateCompatibleDC(NULL);

 

然后創(chuàng)建一個(gè)BITMAP和一個(gè)FONT,將它們與HDC關(guān)聯(lián)起來(lái)。

    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;

    }

   

(只需要?jiǎng)?chuàng)建一個(gè)字體大小的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;

   

(這里需要定義一個(gè)指針指向位圖的數(shù)據(jù):

    DWORD *       _pBits;           // 位圖的數(shù)據(jù)指針)

 

    _hBmp = CreateDIBSection( _hDc, &bmi, DIB_RGB_COLORS,

       (void **) &_pBits, NULL, 0 );

    if ( NULL == _hBmp || NULL == _pBits )

    {

       DeleteObject( _hFont );

       DeleteDC( _hDc );

       return false;

    }

   

    // hBmphFont加入到hDc

    SelectObject( _hDc, _hBmp );

    SelectObject( _hDc, _hFont );

 

接著設(shè)置背景色和文字色:

    SetTextColor( _hDc, #ffffff );

    SetBkColor( _hDc, 0 );

 

設(shè)置文字為上對(duì)齊:

    SetTextAlign( _hDc, TA_TOP );

 

創(chuàng)建Texture所需要的頂點(diǎn)緩沖:

    if ( FAILED( _pd3dDevice->CreateVertexBuffer( _Max * 6 * sizeof(FONT2DVERTEX),

       D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,

       D3DPOOL_DEFAULT, &_pVB ) ) )

    {

       DeleteObject( _hFont );

       DeleteObject( _hBmp );

       DeleteDC( _hDc );

       return false;

    }

   

創(chuàng)建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;

    }

 

設(shè)置渲染設(shè)備的渲染屬性:

    _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) );

 

設(shè)置緩沖的最大容量

    _vBuf.resize( _Max );

 

這樣,初始化完成了。接下來(lái)是如何把一個(gè)漢字寫(xiě)到Texture中,以及如何進(jìn)行管理。定義函數(shù):

// 得到文字在紋理中的位置

void CFont::

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

char c1   ---  文字的第1個(gè)字節(jié)

char c2   ---  文字的第2個(gè)字節(jié)

int & tX  ---  寫(xiě)入紋理中的坐標(biāo)x

int & tY  ---  寫(xiě)入紋理中的坐標(biāo)y

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

Char2Texture( char c1, char c2, int & tX, int & tY )

{

    WORD w = MAKEWORD(c1, c2);      // 把此字變?yōu)?span lang="EN-US">WORD

    vector<Char>::iterator it = find( _vBuf.begin(), _vBuf.end(), w );

    if ( it == _vBuf.end() )    // 如果沒(méi)找到

    {

       it = find( _vBuf.begin(), _vBuf.end(), 0 ); // 查找空閑位置

       if ( it == _vBuf.end() ) // 緩沖已滿

       {

           for(; it!=_vBuf.begin(); it-- )

           {

              it->hz = 0;

           }

//         Log.Output( "字體緩沖已滿, 清空!" );

       }

 

       // 計(jì)算當(dāng)前空閑的Char在緩沖中是第幾個(gè)

       int at = it-_vBuf.begin();

 

       // 得到空閑位置的坐標(biāo)

       tX = (at % _RowNum) * _TextSize;

       tY = (at / _RowNum) * _TextSize;

 

       // 設(shè)置這個(gè)Char為使用中

       (*it).hz = w;

 

       RECT rect = {0, 0, _TextSize, _TextSize};

       char sz[3] = {c1, c2, '\0'};

       // 填充背景為黑色(透明色)

       FillRect( _hDc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );

       // hBitmap上寫(xiě)字

       ::TextOut( _hDc, 0, 0, sz, c1 & 0x80 ? 2 : 1 );

      

       // 鎖定表面, 把漢字寫(xiě)入紋理, 白色的是字(可見(jiàn)), 黑色為背景(透明)

       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

    {

       // 計(jì)算當(dāng)前空閑的Char在緩沖中是第幾個(gè)

       int at = it-_vBuf.begin();

 

       // 得到這個(gè)字的坐標(biāo)

       tX = (at % _RowNum) * _TextSize;

       tY = (at / _RowNum) * _TextSize;

    }

}

以上代碼中的注釋已經(jīng)很清楚了,相信無(wú)須我多言。這里唯一需要聲明的是:原來(lái)所定義的Char結(jié)構(gòu)是這樣的

struct Char{

  char hz[3];   // 保存漢字

  int frequency;// 使用頻率

  RECT rect;    // 這個(gè)字對(duì)應(yīng)位圖的區(qū)域

  Bool isUsing; // 是否使用

}

后來(lái)因?yàn)閷?span lang="EN-US">char hz[3]合成為WORD,所以改為WORD hz。然后對(duì)于int frequency,這個(gè)詞頻應(yīng)該如何表現(xiàn),我一直沒(méi)有想到很好的方法。frequency應(yīng)該在何時(shí)++呢?是在每次被使用的時(shí)候嗎?但是這樣的話,上面說(shuō)過(guò),游戲是以60fps的速度在刷新,如果停上1分鐘的話,變量很快就會(huì)溢出了。就算是使用像是DWORD__int64這樣的巨型變量保存,也是不安全的。除非能在某個(gè)合適的時(shí)候?qū)?span lang="EN-US">frequency清零,但是這個(gè)“時(shí)候”是什么時(shí)候呢?或者設(shè)置一個(gè)最大值,如65535,但是這樣也基本上沒(méi)什么用途,很快,所有在vector中的Char中的frequency都會(huì)++65535的。回憶一下最初,是因?yàn)橄氚殉S米址诺?span lang="EN-US">vector的前面,以便每次find操作可以最快返回結(jié)果的。而經(jīng)過(guò)我的測(cè)試,即使不做這樣的優(yōu)化操作,速度也是很快的,畢竟Cache不是很大,加上vector是連續(xù)內(nèi)存空間。所以可以放棄使用int frequency

然后對(duì)于RECT rect,因?yàn)闆](méi)有了int frequency,意味著一旦將漢字寫(xiě)入到Texture,其位置就不會(huì)變動(dòng)了。所以,很容易根據(jù)find函數(shù)操作后的iterator,直接計(jì)算出這個(gè)漢字所在Texture的位置。這樣,RECT rect也不再必須。

bool isUsing,它本身就是個(gè)雞肋,要也可以,這樣結(jié)構(gòu)更加清晰。不過(guò),直接通過(guò)觀察WORD hz0或非0,即可實(shí)現(xiàn)isUsing的作用了。

為什么要對(duì)結(jié)構(gòu)Char這么精雕細(xì)琢呢?

1.  既然沒(méi)有必要的東西,就應(yīng)該刪除

2.  Char結(jié)構(gòu)的大小越大,vector所要求的內(nèi)存越大

3.  小的結(jié)構(gòu),find可以更快地查找出所結(jié)果

為什么find會(huì)正常工作呢?這里我要大概地講一下find是如何查找出所需的位置的:它只是簡(jiǎn)單地使用whilevectorbegin一直遍歷到end,逐個(gè)判斷,直到找到為止。find要求必須實(shí)現(xiàn)自己的operator ==(),進(jìn)一步跟蹤到find的源碼中,發(fā)現(xiàn)也是這樣。于是前面的結(jié)構(gòu)Char變成了現(xiàn)在這樣:

   struct Char{

       WORD   hz;           // 文字

 

       Char() : hz(0) {}

 

       // 用作查找文字

       inline bool operator == ( WORD h ) const

       {

          return hz==h ? true : false;

       }

   };

是不是很簡(jiǎn)單?^___^

 

終于到了顯示的函數(shù)了:

// 得到文字在紋理中的位置

bool CFont::

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

char szText[]  ---  顯示的字符串

int x        ---  屏幕坐標(biāo)x

int y        ---  屏幕坐標(biāo)y

D3DCOLOR     ---  顏色及alpha

int nLen     ---  字符串長(zhǎng)度

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 ||              // 默認(rèn)值-1

        nLen > lstrlen( szText ) ) // 如果nLen大于字符串實(shí)際長(zhǎng)度, nLen=實(shí)際長(zhǎng)度

       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 );

  

       // 計(jì)算紋理左上角 0.0-1.0

       tx1 = (float)(a) / _TextureSize;

       ty1 = (float)(b) / _TextureSize;

       // 計(jì)算紋理右上角 0.0-1.0

       tx2 = tx1 + (float)_TextSize / _TextureSize;

       ty2 = ty1 + (float)_TextSize / _TextureSize;

 

       // 填充頂點(diǎn)緩沖區(qū)

       *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;   // 坐標(biāo)x增量

   }

   _pVB->Unlock();

   _pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, wNumTriangles );

 

   return true;

}

結(jié)束語(yǔ)

記得有一句名言: Keep it simple and stupid.在實(shí)現(xiàn)功能的同時(shí),保持代碼簡(jiǎn)單、清晰是非常重要的一件事。相信在往后的日子里,在不論是別人閱讀或是你自己回顧的時(shí)候,你都會(huì)發(fā)現(xiàn)一如既往地遵守這個(gè)守則,是多么得重要!

相信通過(guò)上面我那無(wú)數(shù)的廢話,加上代碼中還算足夠的注釋?zhuān)斆鞯哪阋欢軌蛎靼走@其中的原理了吧。如果以上的內(nèi)容還不足以讓你完全搞清楚的話,你可以登錄我的主頁(yè):

炎龍工作室

上面不僅包括了上面所寫(xiě)的程序代碼,還有一個(gè)用來(lái)演示效果的一個(gè)很簡(jiǎn)單的demo

說(shuō)明,以上所實(shí)現(xiàn)的CFont是包含在我的游戲引擎中的一個(gè)部件,而目前已經(jīng)實(shí)現(xiàn)的部件包括有:

1.  CGameFrame(游戲框架類(lèi))  -----  封裝了窗口及D3D設(shè)備的建立,需要派生出自己的子類(lèi)

2.  CAudioCSound(聲音類(lèi)) -----  支持wav/mid/mp3的播放

3.  CDirectInput(控制類(lèi))    -----  鍵盤(pán)、鼠標(biāo)操作

4.  CDirectShow(視頻類(lèi))     -----  支持avi/mpg/mov等的播放

5.  CSpriteX(精靈類(lèi))        -----  方便游戲中對(duì)精靈的控制

6.  CFont(字體類(lèi))           -----  中英文字體的顯示

7.  CTimer(時(shí)間類(lèi))          -----  高精度時(shí)間的控制

8.  FPSfps 類(lèi))             -----  fps的計(jì)算

9.  LOG(日志類(lèi))             -----  游戲中的錯(cuò)誤反應(yīng)以及狀態(tài)記錄

最重要的是,這個(gè)Game Engine完全是開(kāi)放源代碼的。關(guān)于更新的情況、版本說(shuō)明以及源碼下載,請(qǐng)隨時(shí)關(guān)注我的主頁(yè)!

接下來(lái),我將會(huì)繼續(xù)完善這個(gè)Engine,可能加入的有:高效粒子系統(tǒng)、斜45度角地圖……

Feedback

# re: 游戲中漢字顯示的實(shí)現(xiàn)與技巧[未登錄](méi)  回復(fù)  更多評(píng)論   

2009-01-04 09:51 by fred
DX8,我沒(méi)用過(guò)
但是 DX9 中 ID3DXFont 可以顯示中文。。。

# re: 游戲中漢字顯示的實(shí)現(xiàn)與技巧  回復(fù)  更多評(píng)論   

2011-04-07 16:19 by Crazyfrog
樓上那位仁兄,你不用去回答了,那已是n年前的貼了,其實(shí)我也沒(méi)必要回的,感慨啊!!!

# re: 游戲中漢字顯示的實(shí)現(xiàn)與技巧  回復(fù)  更多評(píng)論   

2011-05-14 18:35 by 忽悠有沒(méi)有罪,那要看誰(shuí)忽悠了
兄弟,歲月如梭啊,匆匆一場(chǎng)俺一無(wú)所有,中國(guó)教育害人啊

# re: 游戲中漢字顯示的實(shí)現(xiàn)與技巧  回復(fù)  更多評(píng)論   

2012-05-20 13:56 by ruiner
又過(guò)一年啊,有幸看到此貼,雖然是過(guò)時(shí)的東西了,但學(xué)習(xí),學(xué)習(xí)。

只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


posts - 94, comments - 138, trackbacks - 0, articles - 94

Copyright © RichardHe

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美日韩aaaaa| 亚洲图片在线观看| 免费的成人av| 免费在线看一区| 久久久欧美一区二区| 久久免费视频这里只有精品| 久久久久久亚洲精品中文字幕| 久热精品在线视频| 欧美日韩国产色综合一二三四| 欧美日韩直播| 国产视频一区二区在线观看| 在线免费观看视频一区| 日韩亚洲成人av在线| 9色国产精品| 亚洲欧美日韩国产精品| 午夜精品短视频| 久久久精品午夜少妇| 欧美成人亚洲| 国产精品99久久久久久白浆小说| 亚洲综合成人在线| 久久亚洲欧美| 国产精品网站一区| 91久久精品网| 久久精品国产久精国产一老狼| 亚洲第一成人在线| 亚洲欧洲三级| 午夜精品久久久久久久久久久久| 久久香蕉国产线看观看网| 欧美freesex8一10精品| 国产久一道中文一区| 亚洲精品国久久99热| 欧美伊人久久| 日韩视频一区二区三区在线播放免费观看| 亚洲综合清纯丝袜自拍| 欧美激情精品久久久六区热门 | 美女露胸一区二区三区| 欧美午夜精品久久久久久浪潮 | 欧美午夜精品久久久久久浪潮 | 久久精品视频免费观看| 亚洲黄色成人| 美女国产一区| 狠狠色丁香久久综合频道| 亚洲一区二区三区四区中文 | 一区二区毛片| 欧美激情精品久久久| 性色av一区二区三区红粉影视| 欧美伦理a级免费电影| 亚洲激情另类| 蜜月aⅴ免费一区二区三区| 亚洲一区二区三区中文字幕在线| 欧美精品二区| 99re这里只有精品6| 亚洲国产精品va| 欧美.日韩.国产.一区.二区| 在线欧美小视频| 女女同性精品视频| 久久久久国产精品厨房| 欧美一区二区视频在线观看| 亚洲精品在线二区| 欧美理论在线| 中文精品视频| 一区二区三区免费看| 欧美日韩亚洲天堂| 亚洲一区二区三区四区五区黄| 亚洲国产视频直播| 欧美激情自拍| 一区二区三区视频在线观看| 亚洲美女视频| 国产精品久久网| 亚洲免费视频网站| 亚洲尤物在线视频观看| 国产日韩成人精品| 久久全球大尺度高清视频| 久久福利精品| 亚洲电影天堂av| 亚洲精品日产精品乱码不卡| 欧美三级第一页| 欧美亚洲视频在线看网址| 亚洲欧美日韩国产成人| 国产伦一区二区三区色一情| 久久天天躁狠狠躁夜夜av| 老巨人导航500精品| 在线视频精品一| 亚洲欧美日韩综合| 亚洲国产99精品国自产| 亚洲伦理中文字幕| 国产视频亚洲| 亚洲区一区二| 国产欧美日韩三区| 亚洲电影视频在线| 国产精品成人观看视频免费| 久久蜜臀精品av| 欧美精品综合| 久久在线免费| 欧美日本一区二区三区| 久久成人18免费网站| 嫩草影视亚洲| 久久xxxx精品视频| 欧美日韩高清免费| 久久久久久亚洲精品杨幂换脸| 欧美大色视频| 老司机亚洲精品| 国产精品久在线观看| 欧美寡妇偷汉性猛交| 国产伦精品一区二区三区四区免费| 免费视频久久| 国产伦精品一区二区三区高清版| 亚洲国产毛片完整版| 国产专区一区| 亚洲一区日韩在线| 99在线精品免费视频九九视| 久久国产精彩视频| 亚洲欧美日韩精品久久亚洲区| 能在线观看的日韩av| 久久综合国产精品台湾中文娱乐网| 欧美日韩国产综合视频在线观看 | 亚洲性色视频| 免费亚洲电影在线| 国产精品毛片va一区二区三区 | 亚洲性视频网站| 欧美xxx在线观看| 久久综合九色综合欧美狠狠| 国产精品日韩久久久| 亚洲三级影院| 亚洲日本中文字幕区| 老司机67194精品线观看| 久久久国产午夜精品| 国产精品一区视频| 午夜电影亚洲| 久久精品99| 国产一区二区黄色| 西西裸体人体做爰大胆久久久| 亚洲免费网站| 国产免费亚洲高清| 欧美在线播放一区| 久久亚洲电影| 激情婷婷久久| 久久综合久久美利坚合众国| 美女啪啪无遮挡免费久久网站| 狠狠色丁香婷婷综合影院| 欧美中文字幕在线| 蜜桃av一区二区| 亚洲青色在线| 欧美三区在线视频| 亚洲视频网在线直播| 香蕉久久夜色| 国产综合精品一区| 另类亚洲自拍| 亚洲美女精品成人在线视频| 亚洲一区不卡| 国产亚洲欧美aaaa| 欧美在线啊v| 亚洲高清不卡在线| 一区二区三区精品视频| 国产精品日本一区二区 | 亚洲黄网站黄| 亚洲性感美女99在线| 国产欧美日韩麻豆91| 久久久久在线观看| 亚洲精品一级| 久久精品欧美日韩| 亚洲黑丝一区二区| 欧美日韩综合不卡| 久久成人av少妇免费| 亚洲国产成人久久综合一区| 亚洲色图制服丝袜| 国产欧美日韩视频一区二区| 免费高清在线一区| 一区二区三区欧美在线| 久久精品国产清高在天天线| 亚洲福利视频一区| 国产精品久久久久久模特| 久久久精品国产一区二区三区 | 亚洲视频免费看| 裸体歌舞表演一区二区| 日韩小视频在线观看| 国产精品卡一卡二| 免费成人性网站| 午夜精品福利在线观看| 亚洲国产欧美在线| 久久精品中文| 久久精品国产精品亚洲| 亚洲一区二区三区在线视频| 国产亚洲视频在线| 欧美性久久久| 免费视频亚洲| 欧美伊人久久大香线蕉综合69| 亚洲精品久久久久久久久久久| 久久视频国产精品免费视频在线| 日韩系列在线| 精品91免费| 国产日产精品一区二区三区四区的观看方式 | 欧美国产专区| 欧美 日韩 国产在线| 在线亚洲免费| 在线免费观看视频一区| 国产欧美在线看| 欧美性猛片xxxx免费看久爱| 久久婷婷丁香| 久久国产精品久久久久久久久久 |