冒险解谜游戏中文网 ChinaAVG

标题: 【汉化资料】游戏中汉字显示的实现与技巧 [打印本页]

作者: shane007    时间: 2008-10-22 17:34
标题: 【汉化资料】游戏中汉字显示的实现与技巧
讲了一些TTF的知识,也许对汉化有用6 ^( T- k, J3 d6 f& T

! U, a: k8 ~; m; Y% ^0 [1 ~' |0 F& R% f- L5 ]/ e: M& n4 y
游戏中汉字显示的实现与技巧2 {% n. J% p: [- g. e0 S
作者:炎龙工作室 千里马肝2 E; G4 x2 Z# `$ X: Z# w! ^
版本:v1.0  r% ?0 l, _- F
最后更新日期:2002-3-30
2 j/ h' E; X% [- b* X. r1 e绪言/ S; H0 r+ X. e2 H: A
在游戏中,因为我们是中国人麻,通常都需要显示汉字,比方说交待剧情。而对于文字的显示,英文的显示要较其简单得多,因为只有26个字母,就算再加一些标点、符号什么的,用一张位图,就可以足以显示所有的单词了,而相关实现技巧,也比较轻松。
) R* V  p4 x2 C' k; @% O1 I1 K! d而中文的显示方法,要复杂得许多。记得原来在DOS下,汉字的显示都是读的UCDOS的点阵字库,而点阵字库的读取方法,在UCDOS SDK中都有源代码可以参考。但是自从Windows操作系统开始,我们开始了解到一种更好的字库,它就是TTF。9 j" ^* U6 M* V9 y
注:以下我所指的开发环境,除非明确说明,默认的平台是VC6.0+DirectX8.1,使用D3D来加速2D。然后使用的STL是用的SGI实现的那一套STL。
, i, n6 \( _& \/ k点阵字库
' m. z) T: `* }/ Q" W% W包括现在,有很多游戏都还是使用的点阵字库。因为操作起来比较方便,加上这方面的经验已经积累了好几年了。通常如果只是一种字体就可以满足需要的话,它会是一个比较好、快的解决办法。但是它有3个缺点:$ u4 g7 ?0 i5 L3 H5 o
1.    如果放大显示,不做处理的话,显示出来的汉字,是很难看的。0 e, R9 v3 ~% Q# \
2.    像是UCDOS所提供的点阵字库,只有24点阵的有几种字体,如:宋体、黑体、揩体…,而16点阵的好象就只有宋体一种。& o4 q  A7 u7 M
3.    点阵字库,通常是有版权的,尤其是第三方制作的汉字库(如:方正)。5 y2 ?( q$ e" x0 O( l0 {5 q
在这样的情况下,当我们写好这样的一个显示函数,就算是解决了如:放大、快速显示等问题的话,可供选择的字体还是太过于局限了。所以,在字体的要求比较强的情况下,点阵字库并不是一个好的解决方法,他不够灵活。尽管我们对于它的操作是如此得熟练,可以写出优美的代码来展示我们的编程技巧。
. b3 {( a# c  w: T0 STTF, U( E+ `" U+ ?. R
TTF是True Type Font的简称。在Windows\Fonts目录下面,我们可以看到许多后缀为ttf的文件,它就是接下来我们接下来所要谈到的。
/ @+ v) x' W, a$ q' tTTF是一种矢量字库。我们经常可以听到矢量这个词,像是FLASH中的矢量图形,在100*100分辨率下制作的flash,就算它放大为全屏,显示出的画面也不会出现马赛克。所谓矢量,其实说白了就是用点和线来描述图形,这样,在图形需要放大的时候,只要把所有这个图形的点和线放大相应的倍数就可以了。而且,在网站上有很多的TTF字库可以下载,或者你可以去买一些专门的字库光盘。然后在你发行你精心制作的游戏时,可以顺便捎上这些后缀为.ttf的文件就行了。包括Quake这样的惊世之作,也都是用的TTF字库。, j1 t( b/ G6 A) Z
这样,我们就可以解决点阵汉字的一些问题。通过TTF,我们在字体的质量和字库的数量上获得了暂时性的胜利。
( K( u7 G. F' G' R' }4 ]. y字库的读取和显示! u6 S4 I) i0 ^7 e4 J4 ?: N) F
先前谈到点阵字库,只需要很简单的一些操作,就可以显示出想要的汉字。下面我给出一个读取hzk16的函数,它需要一个Surface以供显示用:
- r2 l- e) Z+ F4 P; r#include <io.h>; a5 o8 _9 J6 N- V/ i  Z. _1 e
#include <stdio.h>
' {9 E& m, {  O$ G  ?% Z#include <conio.h>  D8 F! o+ G! F1 @( V& w- {+ N" `

2 b: Z& [4 W& A5 T- W1 D' }3 A2 ^1 [# k  w// 读取16x160 p+ |: K, V. F7 J/ o6 |( E0 p
void DispHZ16(int x, int y, BYTE *Str, LPDIRECTDRAWSURFACE surf)0 L( t8 B5 D# _* o1 D8 Q: o
{7 m9 l; H+ n, B3 S: w- A
    const int Mask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
0 M( Q; |! ^+ _+ L/ Z# S) k    FILE *HzkFp;) \# P/ M, o' n9 q! r2 Q
    WORD i, j, k=0, m;
$ ?/ U" U6 T2 G0 B" b' X# s4 ^    WORD HzNum;9 G6 h2 I, _5 f# O
    WORD QuHao;9 b: u: m& s& u9 j1 i
    WORD WeiHao;& \0 ^6 U% r& E' N' R  \
    long offset;, f  {" ]& W& i4 w0 A
    BYTE dotBuffer[32];   
- _4 I& h: m7 H* d) Q5 E1 d4 ]5 i: ]
) _, ]" x6 q+ `/ b! G9 F    HzkFp = fopen("HZK16", "rb");$ k3 x# u7 e4 x3 \$ Z$ u
   
  z2 ~& C$ N# A& C    HzNum = strlen((const char *)Str)/2;
- t3 b6 [2 ]+ e& D8 S* h' g0 w
. T( K  V) Z' n! D6 y+ F    DDSURFACEDESC       ddsd;/ D! m% x* _" N* K2 V
    LPWORD              lpSurface;
% g3 y, Y9 T  u: ^* ?8 {    HRESULT             ddrval;9 w# B; L, g- V
    - h6 q; C0 k. @0 `* Z4 q' t
    ddsd.dwSize = sizeof(ddsd);8 s5 i. |9 X4 ^+ H/ \& q" _! k; `' i
   
. n8 t, S8 `+ z, c/ K* d4 u6 J5 z; s    while((ddrval=surf->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING);
/ ?/ [: b" F+ ]+ \0 k1 K    if(ddrval == DD_OK)8 v7 ?* i9 N  V! B5 e
        lpSurface = (LPWORD)ddsd.lpSurface;
. Z/ A5 ?( i; Q! g, ]/ o- v
7 f: v# H$ x! C    for(i = 0; i<HzNum; i++)# D7 f# B; m3 E4 }0 Y/ [/ E4 z# b
    {    3 n- t& V4 P0 E! N! O5 I
        QuHao = Str[i*2]-160;
5 Z, R) f3 w) k3 C$ ~  @        WeiHao = Str[i*2+1]-160;% s2 }4 ?0 W  ?

1 s* s4 g( D4 P, Y% x        offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;" _; G* M" l7 r# e

9 D3 P) n7 y; h        fseek(HzkFp, offset, SEEK_SET);1 p/ ]+ @6 f# k
        fread(dotBuffer, 32, 1, HzkFp);
6 S" ]: a: f4 [& l3 O( f, H% g" t- ~, h. }; {  L7 L
        for(j=0;j<16;j++)
- J# Z7 C" @& Y  `; P0 p' z            for(k=0;k<2;k++)
3 i9 x' s# y' w: C. r' a                for(m=0;m<8;m++)
, F& z9 E( ?9 ]                    if(dotBuffer[j*2+k] & Mask[m])9 m2 G! ?  k' [, p9 b
                    {
- r' J$ n4 i/ S* Q' i) L                        lpSurface[ddsd.lPitch*(y+j+1) + x+k*8+m] = 0x000000;) q4 u6 ~7 N* c; V, X
                    }
# p5 j6 U7 h4 s+ ]  p6 ^1 W        x+=16;4 B2 T4 r* f0 h! J, x; l8 e
    }. J( b' ^* `* p9 G0 j
) g- {8 H' R7 \8 r' O5 H
    surf->Unlock(NULL);0 X" v( @8 d/ \) h7 [

: u) i" I' f9 O! D5 Y    fclose(HzkFp);
; N& q6 j2 l; v; c$ s9 Z" K4 V1 c) D}
- H2 V1 A5 M1 P其实原理很简单:
6 P0 i9 S1 `" f5 X! H1.    打开字库: [& o. b6 k$ ?' [; K; i. j
2.    计算字符串长度(这个函数只支持中文),并且Lock Surface- r% S* O. O8 W( s
3.    依次计算出每个汉字所对应的区码和位码(汉字的第1个字节是区码,第2个字节就是位码),然后通过公式计算出这个汉字在字库中的偏移量:
* k- R3 G" o; n' Ooffset = ((QuHao - 1) * 94 + (WeiHao-1))*32;4 H; {' [- [; C% C
4.    读出一个32个字节的点阵
2 y4 R: \8 ?3 @9 P7 v8 w5.    绘制到Surface上
0 O( B. _  \  `. C9 N以上只是16*16点阵字库的显示方法,24*24的读取方法与之类似,大家可以参照相关资料来书写出自己的代码。
! p0 [/ k6 h% \) D0 F' o: J
9 ?) Z! l) ~5 U  ?如何显示TTF字库呢,有很多种手段,下面我按从简单到复杂的的顺序依次介绍:
4 F# H# Q8 ~! \1 j9 r1.    使用Windows API,也就是大家所熟悉的TextOut。通过它,还需要一个HDC(设备句柄),我们就可以随意地在屏幕任何地方显示出文字了。
! I. W3 b2 D; B  j8 _1 V1 s6 N0 I0 X4 t2.    在http://www.freetype.org,有一个FreeType的免费库,而且是OpenSource的。它目前有2个版本:1.0和2.0。其区别在于,1.0只能读取TTF格式的,而2.0支持更多的文件格式,在使用它之前请详细阅读所要遵循的Licence,以下是摘自FreeType2.0对字库的支持列表:+ w$ l4 Q& \$ L9 ?; R1 c" I/ l& c
o    TrueType fonts (and collections)
/ F  Q6 Y* j4 |: ro    Type 1 fonts " r5 s1 Y; G; F8 y
o    CID-keyed Type 1 fonts : R4 a% ?! p8 n  g- P
o    CFF fonts
; L' R; z" p' e! ~" j4 C' eo    OpenType fonts (both TrueType and CFF variants) 6 @# [% K4 \( ?, s  {* ~( K6 n$ @
o    SFNT-based bitmap fonts
, P: g/ a7 }7 l% do    X11 PCF fonts
. V1 ?$ A+ z9 B$ n& }# co    Windows FNT fonts
) P. E+ o- H% s' R! h3.    自己研究TTF的格式,然后自己来操作。2 M0 }6 P; c: J; N* N
2 s) u  o: s: C- k
....... ╮╮ 6 h8 z  b+ R. s0 ~/ m; K6 D1 o
      \█/倒!
  Q6 X( A8 D8 s/ q( J      ●7 G$ R0 Q: E! |- C
虽然我们想要把每一件事情都做好,但是也不是每一件事情都要亲历亲为。如果你非要这样,也行^____^,但是过不了多久,你就会陷入泥沼,到时候你会发现自己的热情正在慢慢被磨灭,什么叫做抓狂,相信你很快就会知道^_^。* |; x, C& H/ D; {, }7 {( p
9 @9 J8 o2 q' d( c
在有多种选择可以取舍的情况下,我们需要考虑一下,对比一下各种解决方法的优劣。
* \4 F% `6 _4 j7 }  }; Z$ U8 C( Z! u, n3 u# L. h: _9 @
在DirectDraw时代,我们都不自觉地喜欢上了GetDC,因为……多方便啊。可是现在已经到了DirectX8.1时代了(我要使劲地摇那些还沉醉于DirectX7中,为如何在使用alpha时提升那可怜的1、2个fps的朋友们:醒醒,该起床了!),HDC已经被M$列为禁用品。怎么办呢?是的,你可能已经想到了,我们还一直保存着窗口的hWnd呢,可以通过它来得到hdc,从而调用那些需要hdc的API,可是,这样做是更为愚蠢的,这样对你是没有一点好处的,不信,你就试试吧。有一句话,请牢记:要想你的游戏有更快的速度的话,请不要再去碰HDC了。9 `% v! e* R6 K  }2 u
我们非常清楚hdc是一个超慢的解决办法,它无法在我们的高速游戏中满60分及格。下面来看看FreeType,它更像是一个Service。它的解决方法是,先通过一系列的初始化和设置,告诉FreeType字体的名字和大小等,然后它会动态地申请一个Graphic,再把我们要显示的字画到这个Graphic上,你还可以把它保存为tga格式。不过我们最终所想要的不是这个,所以可能我们还需要从这个Graphic上逐点读取或者用CopyRect,然后再画到我们的画面上。其实它已经是很方便的了,可是需要你去学习如何配置和使用它,这是很花时间的一件事情,而且它最大的优点是可以跨平台,我们需要它吗?如果有一个更为简单的办法,像是如果Textout不是那么慢的话,就好了……" [9 i4 s3 a$ ^" P: R
在这里,顺便谈一下另2个字体显示类:ID3DXFont和CD3DFONT。可能早就有人会说怎么在上面的列表中没有它们?原因我会在下面慢慢地说明:
$ }. ^: F, P8 o& yID3DXFont,它存在于D3DX库中,一个现成的字体类,不过对于它的处理方法……我实在不敢恭维,就引用一位大师所说的话来表达我的看法吧: 在内部实现中, ID3DXFont::DrawText()函数确实做了我上面讨论的工作,先建立一张GDI兼容的位图,把文本绘制到位图上,而后把位图拷贝到纹理贴图上去,最后把纹理渲染到屏幕上。这样你就聚齐了所有的龟速的原始GDI函数,还包括了一大堆的额外开销 — 最终,这个函数比原来GDI的DrawTextEx()函数要慢上超过六倍……
& q  z! m6 s9 m7 eCD3DFONT,是由M$在D3D的框架代码中提供。不过它只能显示英文,有很多朋友通过自己定制和修改这个类,来实现自己的中文显示。不过效果都不是很好。其实原理,跟ID3DXFont的方法差不多,不过处理方法要聪明了一点。
# B+ M# n: C: I# r5 M分析与思考  ^  v3 i1 s5 v, l) M9 k, z
那么我们应该怎么办呢?通常我们会幻想,如果可以像处理英文那样,把所有的汉字都保存在一张位图里,该有多好。这样,显示的速度就不是问题了,直接可以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。
/ t! U  y$ D5 z% b! P; g汉字虽然很多,但是常用的汉字,其实也就只有那么几百个。像这样的字:鬯、鞴,你一辈子会看到多少次呢?如果可以做一个类似于Cache的东西,保存着常用的那些个汉字,在需要显示的的时候,先在Cache中查找,如果有的话,就马上画上去;如果没有,就从字库中提取到Cache中。这样的话,在使用Texture来保存汉字的位图信息的同时,对于每个汉字,我们还要定义一个结构,然后用一个东西把它串起来,综合它们2个,也就实现了我们所要的Cache了。刚开始,我所定义的结构是这样的:1 x0 Z( b6 A: \! U5 c4 [2 I) C
struct Char{* }! c" W5 a% d6 z( x' k
  char hz[3];   // 保存汉字" x6 O8 C( d" D
  int frequency;// 使用频率$ M/ F, Y! }2 _+ P1 Y1 J+ M
  RECT rect;    // 这个字对应位图的区域" P9 Y5 u7 }7 Y, F& b$ _9 |
  Bool isUsing; // 是否使用
  [5 c. S6 R6 F% y4 p  `}. y  o7 H5 E" @% J" J0 E* r
对于汉字和英文,我在这里大概地讲一下原理:汉字是由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’。" g7 ~2 _5 o. ~7 d& u0 X7 n
接下来,对于使用char[3]来保存汉字,是否真的很合适呢?因为如果把它当作一个字符串来看的话,在查找时就需要使用 strcmp 来比较字符串了,这样一定是会影响速度的。如果不把它看作字符串(字符串的最后一个字节需要以’\0’结尾),只用char[2]的话,我们可以只是简单地调用宏MAKEWORD,把2个byte压成1个WORD。当把文字作为一个WORD来看的时候,这样查找比较时可以用WORD内建的==操作,这样要比调用strcmp函数要快得多。
, q: J' P8 q6 yint frequency用来标志每个WORD的使用频率。设想,如果一个字已经存在于Cache中,以后每对它调用一次,就让frequency++。这样做还有一个用意是,是否可以在一个合适的时候,以frequency为参照来对这整个Cache排个序,把常用的字放在前面。那么在显示时,可以先在Cache中查找所要显示的字是否已经存在于Cache中,如果有则直接显示,没有的话才需要采取某种手段将字加入到Cache中。一些常用的字(像:我、的、着、了、过……),使得显示的速度将会大大提高。) M0 Z0 l8 k& D$ n
其实上面说了半天的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上的区域位置。
6 Q; N3 ]3 n5 b: n) i使用什么东西来把这n个Char串起来呢,一般会想到的是链表,原因无非有2个:1 随时有新的字加进来,而内存是不连续的 2 它几乎没有容量的限制(除非是内存用完了)。不过链表的访问速度是很慢的,如果使用像数组这样的东西就好了。仔细想想,在这里,我们用来存储的Cache,最大也就是256*256(理由上面说了),所以大小应该会是固定的。我们只需要在数组中的给每一个汉字加上一个标志,说明这个位置的使用情况。那么就使用数组吧,这样的话,访问的速度要更快一些,直接首地址+偏移量就够了,不必像链表,在查找时需要逐node访问。当然,我绝不会想到用new  Char来申请这个数组。因为这样做实在没有必要,请不要过于迷信自己的能力,在STL中已经有vector了,为什么还要自己写呢?^_^最后的一个bool成员变量isUsing,也就是上面所说,用来标志使用情况的。
4 v, M+ I) R6 U, j/ d实际的操作- p& r# w+ \- r  b! u
上面考虑了那么多,我认为都是实际操作之前所应该有的。先谈谈如何显示吧,因为在DirectX8.1中已经将DirectDraw和Direct3D融合为DirectGraphics了。所以无法像原来那样了…………哦,实在有太多东西要讲了,我还是推荐几篇文章给你吧^_^:
4 X  D: n# V9 M: vhttp://vip.6to23.com/mays/develop/directx/200201/Geczy3Din2D.htm: d  _! ^* G. E( B4 C
http://vip.6to23.com/mays/develop/directx/200201/GESurface.htm) H5 A- z2 T$ H- g6 g8 G$ _
http://vip.6to23.com/mays/develop/directx/200112/2DGtoDX8.htm
# |6 r2 z7 D: L7 Rhttp://vip.6to23.com/mays/develop/directx/200201/DX8adv2D.htm
* V$ a; T7 r- K接下来,我会假设你已经具备了在DirectX8.1中绘图的基本概念了,所以在你继续往下阅读之前,请务必先仔细阅读以上推荐的文章。
  t! V/ J: n& R) M% t5 l- Y前面提到,需要一个vector来对应Texture上各个位置文字的信息,上面已经创建了一个结构Char,则这个vector的定义为:* E/ h* ?9 [6 @' j4 i! p
    vector <Char> _vBuf;                    // 记录缓冲中现有的文字情况2 @) ^& o; U' }3 F% p
首先,由于可以利用硬件的放大缩小机能,所以字体的大小精度要求不是很高,只需要支持16*16和24*24大小的字体就可以了。我们需要一个这样的初始化函数:
6 [9 C1 T1 Q7 y' h  Pbool CFont::
7 j" G# s8 |! Z% b' n/*-------------------------------------------------------------. H# b4 S& b7 U
LPDIRECT3DDEVICE8 pd3dDevice  ---  D3DDevice设备2 T$ F! s' h! Q& p7 R/ j
char szFontName[]              ---  字体名(如: 宋体)
6 Q- g* ^" G) k5 }int nSize                      ---  字体大小, 只支持16和24
" M* E" ?) R. h& f4 R: Kint nLevel                      ---  纹理的大小级别
4 N( u# A/ [, c+ S1 c& J-------------------------------------------------------------*/' C9 ]& J4 o. J4 T
Init( LPDIRECT3DDEVICE8 pd3dDevice, char szFontName[], int nSize, int nLevel )。
: O* I1 B! t* f# y" P, ~5 y" n; d  ^( C
在DirectX8.1中,由SetTexture(…)所贴的图的大小,也就是Texture的大小,是有大小限制的,长和宽都必须是2^n,而且位图越大,所花费的显存越大,这样留给其他显示用的显存就少了。所以,必须根据需求的不同,来自定Texture(也就是Cache)的大小。因为汉字点阵大小的原因,所以从实用角度而言(比方说只是显示fps或是短小的标题),开辟一个64*64大小的Texture,才能满足最低情况下的需要(这时如果选择16点阵的话可以存放16个汉字,24点阵可以存放7个,依次类推……)。" A7 E$ k0 u0 b  W1 c% O8 k
根据设置,创建Texture:
3 c4 A3 F4 K/ P2 ~- e( E    _TextureSize = 32 << nLevel;        // 纹理大小
: @6 U3 N( r; v: k( o# F    _TextSize     = nSize;                // 文字大小4 t. @. h/ s5 Q+ Q9 L# ~9 n
    _TextureSize = 32 << nLevel;        // 纹理大小% ]) o8 o1 ~/ Q! e
    ! X2 Z- G* c& _8 D
    _RowNum = _TextureSize / _TextSize;    // 计算一行可以容纳多少个文字
9 Z3 `1 Y9 i0 H( ]$ L    _Max = _RowNum * _RowNum;            // 计算缓冲最大值) C) r8 a  O. V2 V
7 s+ \3 a0 X8 |; H, L; Q
创建字体,还是需要使用Win32 API。也就是先创建一个HDC:' Z; s6 m" v, w% N
    _hDc = CreateCompatibleDC(NULL);! G& |9 b0 D9 |0 f, T
- E" L- P. [% p) J* `9 ~8 `, y0 a4 b
然后创建一个BITMAP和一个FONT,将它们与HDC关联起来。0 y- z) O/ R5 w" P$ |5 k* [* R9 G
    LOGFONT LogFont;$ h+ l+ a8 i4 q6 A# l
    ZeroMemory( &LogFont, sizeof(LogFont) );
( i% z; r( m5 D5 `5 P5 h' T4 q/ e    LogFont.lfHeight            = -_TextSize;
, R) j: V4 r+ _' T) C. ^# x, v4 \    LogFont.lfWidth                = 0;
! N& A. U, k: W! b3 {4 ~* a, d$ u    LogFont.lfEscapement        = 0;
. P: h& t* K3 ~1 N    LogFont.lfOrientation        = 0;
( a& F1 D: X3 y3 w" {" {    LogFont.lfWeight            = FW_BOLD;
8 i- x" t+ ]5 Y5 @" w7 f* v    LogFont.lfItalic            = FALSE;+ f+ C" s8 |8 \) E+ d" n" ^( E
    LogFont.lfUnderline            = FALSE;
7 x/ t) D% E. p/ U" a3 ^* o    LogFont.lfStrikeOut            = FALSE;& r% R6 k3 M9 b) }8 z1 n6 @
    LogFont.lfCharSet            = DEFAULT_CHARSET;1 j# T% e6 K6 r5 ~
    LogFont.lfOutPrecision        = OUT_DEFAULT_PRECIS; . B4 d2 a! K' Y* ~2 h( n6 ^
    LogFont.lfClipPrecision        = CLIP_DEFAULT_PRECIS; $ W$ q- J, S* M  n1 g2 T% H
    LogFont.lfQuality            = DEFAULT_QUALITY;
; w# q7 i3 C* r8 M" K7 X1 u8 c+ ]    LogFont.lfPitchAndFamily    = DEFAULT_PITCH;
( T5 c. g6 s" i5 l) y# m; m; J    lstrcpy( LogFont.lfFaceName, szFontName );
6 t  M' J  V  _  F! A    / v3 f4 T) |' A/ l- i
    _hFont = CreateFontIndirect( &LogFont );; m5 O% d7 k; m. y1 }. t5 |8 l; [
    if ( NULL == _hFont )8 Q2 |; I% h5 m
    {
0 ]9 r3 q/ m( U  s% D6 p6 |1 p        DeleteDC( _hDc );
: ]0 K; I4 u* f% B        return false;
- L0 L+ I9 y9 z2 Z    }: ^. [5 `+ u; I$ A9 p
    : b( f& t% {# Q7 q: U* P- g
(只需要创建一个字体大小的BITMAP即可)# c2 q6 p5 N* S% P- T
    BITMAPINFO bmi;, Y4 F$ F: z& M' ]3 v. {
    ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));, g3 [% z6 e6 W4 w" A
    bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);. {# _/ d2 l+ j3 E8 T  _# G- A
    bmi.bmiHeader.biWidth        = _TextSize;+ \) I1 O, G; r' o6 C, }
    bmi.bmiHeader.biHeight        = -_TextSize;7 G6 m" \! K+ ?3 X- w0 E$ J- {
    bmi.bmiHeader.biPlanes        = 1;
) @8 c% r* P' a3 {0 w7 I* ~    bmi.bmiHeader.biBitCount    = 32;
" L3 N7 V' \/ Y6 I    bmi.bmiHeader.biCompression = BI_RGB;+ p* I1 U  Q5 {& q5 {9 O4 n
    # o; ^! V9 A5 D5 V, h8 B: g
(这里需要定义一个指针指向位图的数据:
% u5 ]' Z4 ~, v+ l: c. X    DWORD *        _pBits;            // 位图的数据指针)3 Z( m, P' H0 L& R- K. Y6 X

5 s: o6 t% S5 e! k" F% @8 q1 v- p    _hBmp = CreateDIBSection( _hDc, &bmi, DIB_RGB_COLORS,0 C* K& }- p* w7 S- h
        (void **) &_pBits, NULL, 0 );
* L; P6 ?5 J# ?) l* Y3 S    if ( NULL == _hBmp || NULL == _pBits )
& {7 x4 I2 Z, Z! A# }& v4 D    {# m3 p: ^5 v; c% H8 ]
        DeleteObject( _hFont );
/ i5 c$ `; u) [6 v( I        DeleteDC( _hDc );( t8 a* l1 C$ G: q: ^
        return false;
4 e2 V8 v' t8 L    }
) c" }+ `2 v4 s2 i; p. i  |   
- B( C, n7 }% e4 A0 h3 y    // 将hBmp和hFont加入到hDc$ N9 t# X; M2 j4 z1 s% T" H
    SelectObject( _hDc, _hBmp );% a- V  A3 z. i( ~3 h
    SelectObject( _hDc, _hFont );
  h/ S9 e/ H% l
+ F7 N6 ~7 ]9 @/ z# _4 U& }" R# I接着设置背景色和文字色:5 a; ]' H% Y. N/ J! @
    SetTextColor( _hDc, RGB(255,255,255) );
& z' B- @& o0 b1 x: l8 \' B. H- A    SetBkColor( _hDc, 0 );
* m  A$ E$ x/ P/ u# y/ H% C; ?6 x& Z7 @; T( P% y
设置文字为上对齐:- z5 \: W4 p0 T+ N3 ~
    SetTextAlign( _hDc, TA_TOP );* |) }& t8 K7 _

* h5 m7 q6 Y# V- Y" G5 e创建Texture所需要的顶点缓冲:4 x/ C) I/ A# F8 a: e" b
    if ( FAILED( _pd3dDevice->CreateVertexBuffer( _Max * 6 * sizeof(FONT2DVERTEX),0 }& F- N- y9 `; O6 D1 d' v* [' W
        D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,, ], J! g; p4 z2 r1 R+ ?  Z" K; V& y
        D3DPOOL_DEFAULT, &_pVB ) ) ). Z8 M2 ?8 ?, O9 Q: B: s4 p
    {
0 B# S1 e2 \7 e% ~        DeleteObject( _hFont );. g# p8 _6 J4 `! v( E& O: f
        DeleteObject( _hBmp );
* N- Q* `0 W% V. n- r        DeleteDC( _hDc );* i9 q+ }6 s! Q+ M. ]6 X
        return false;
2 W& s3 S% N& Z8 {8 p: k* ?6 R% a, j    }, b2 P9 D' b# K  r
   
# t5 y' k* U6 ~+ B9 X创建Texture
2 W" f* r5 g. W    if ( FAILED( _pd3dDevice->CreateTexture( _TextureSize, _TextureSize, 1, 0, ( I$ c: i$ Y2 R3 H+ j
        D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &_pTexture ) ) )( m3 s' s" \* I+ E
    {; e- `- q2 s1 P& {+ M
        DeleteObject( _hFont );
. K0 ~  a, _+ J        DeleteObject( _hBmp );1 U2 o' o2 }# o8 O7 U, k) j
        DeleteDC( _hDc );
8 {- }+ ]) q  {8 W( ^5 n        SAFE_RELEASE(_pVB);
5 v7 u8 G  D1 `- Y5 m' [         return false;
: H5 H2 ?1 H4 {# |3 V3 V' ]    }
6 T9 w3 Z& D& Y  K( @6 O+ E5 y% ~
; w% }& w, f& ?, K8 d# g# y设置渲染设备的渲染属性:" S8 Q' P+ ?4 U2 Y# u; z
    _pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE,   TRUE );! }; ?" }) c2 h
    _pd3dDevice->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA );* }* n$ ^+ [- q7 R' a/ @  E
    _pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
9 M' C% A5 L# s    _pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE,        TRUE );
. r: t8 b7 K# |; T8 k% g    _pd3dDevice->SetRenderState( D3DRS_ALPHAREF,            0x08 );6 s8 N/ n$ p; N6 P
    _pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC,            D3DCMP_GREATEREQUAL );+ }+ z6 O* Q* H( p2 ^/ j' s  r; y& n
    _pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,    D3DTOP_MODULATE );
8 ~# U9 o' Z8 e3 Y        
" X8 N" E; s5 Z0 T: H7 b    _pd3dDevice->SetTexture( 0, _pTexture );
" O; K: T$ }9 b2 l    _pd3dDevice->SetVertexShader( D3DFVF_FONT2DVERTEX );; z6 @8 ?( g% t% q, j
    _pd3dDevice->SetStreamSource( 0, _pVB, sizeof(FONT2DVERTEX) );
  U0 J7 o1 V8 n- B( x0 b, T, U$ u5 `: c
设置缓冲的最大容量
: {2 _& e* J' }    _vBuf.resize( _Max );
/ `/ j& O4 Y8 v7 B! N$ |# u) U1 S
这样,初始化完成了。接下来是如何把一个汉字写到Texture中,以及如何进行管理。定义函数:
0 P" @% r/ }9 K, e% m# }// 得到文字在纹理中的位置, t: N( F3 h8 J5 \$ U3 R2 z0 G( G4 |
void CFont::$ p8 C; U9 O; _+ {' E- d# `
/*-------------------------------------------------------------' a/ h# W+ I; w7 s
char c1   ---  文字的第1个字节# l* j8 x+ R# c( I% j9 d# R: v
char c2   ---  文字的第2个字节) r/ [9 z5 v( j* L, F
int & tX  ---  写入纹理中的坐标x
& Q0 f8 t, d6 m3 ]/ I2 {$ E, lint & tY  ---  写入纹理中的坐标y
- A# d! S  A' K: s& a6 {  `2 q5 m. g-------------------------------------------------------------*/2 C9 w6 G! V; _! y
Char2Texture( char c1, char c2, int & tX, int & tY )
" \( {( s% N, N# S# Z{- r8 X( E; j  ~5 _) o9 ]) v8 I$ g
    WORD w = MAKEWORD(c1, c2);        // 把此字变为WORD
: m( p) @/ T5 U6 d  k% j2 I    vector<Char>::iterator it = find( _vBuf.begin(), _vBuf.end(), w );% V) e# P/ f- B1 z0 h% X) R7 N! k
    if ( it == _vBuf.end() )        // 如果没找到) j6 ^8 {+ B: y, j# e4 ~5 a) J" I9 |
    {2 M3 N) u+ G, K; r$ [
        it = find( _vBuf.begin(), _vBuf.end(), 0 ); // 查找空闲位置
/ Q  f! V# A6 q$ r9 T% ~* F' h        if ( it == _vBuf.end() )    // 缓冲已满
' u/ c. x0 _4 S; u1 ]* Q7 r( A        {- L" i# j: t) _$ u5 y
            for(; it!=_vBuf.begin(); it-- )* _" W, f4 y, B' \+ D
            {
# d3 i8 A# R4 Y; g* u                it->hz = 0;
- a9 |5 C2 ?: q3 T$ \            }
% w( y7 }, k: q" v//            Log.Output( "字体缓冲已满, 清空!" );& U* Z% M9 W! m9 l; w. e# Q
        }2 P+ d& R% w3 u& ?2 d. \+ B

# F8 i2 R3 @5 K7 G# z) ?' ?        // 计算当前空闲的Char在缓冲中是第几个
6 R+ g! V, R& [" {) Z        int at = it-_vBuf.begin();
( @! |% w! c5 I$ e; H! f9 N5 `6 G9 C# k& I5 p  j
        // 得到空闲位置的坐标
  O3 m3 q, }( N- c        tX = (at % _RowNum) * _TextSize;
9 b2 H% H2 \2 w/ f9 y+ x        tY = (at / _RowNum) * _TextSize;
4 r3 R' K$ E1 n, S' E" W4 `; D+ ?
6 K% E3 w" [8 D: h: Y/ ~& [; n0 B1 x        // 设置这个Char为使用中1 Q. N. p. N. _1 }: {3 o; [# M0 v4 F
        (*it).hz = w;. y( X( K; T! K5 V) T
# H. V; i- b* }
        RECT rect = {0, 0, _TextSize, _TextSize};5 {4 {2 p  e: }+ }$ }: [, v- \
        char sz[3] = {c1, c2, '\0'};
0 J) D, Y# c+ w1 z) y        // 填充背景为黑色(透明色)) {; l7 c# Q0 N7 w
        FillRect( _hDc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );$ r8 V" f  X3 F+ i
        // 往hBitmap上写字8 U4 }* S3 v3 [* g. v. E
        ::TextOut( _hDc, 0, 0, sz, c1 & 0x80 ? 2 : 1 );8 z' j6 V- ~, v$ h4 P# f
        
1 E7 N+ Y" ]- s; m) T        // 锁定表面, 把汉字写入纹理, 白色的是字(可见), 黑色为背景(透明)2 X8 Y, N: Y0 K% t" {: E
        D3DLOCKED_RECT d3dlr;
: h, O9 e1 r- Q1 Z/ \% }* r2 ~        _pTexture->LockRect(0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
" B3 t1 o/ Y* O+ {        BYTE * pDstRow = (BYTE*)( (WORD *)d3dlr.pBits + tY * _TextureSize + tX );
1 Z' E5 ]" H# x        + [) m8 L' M* H
        for (DWORD y=0; y<_TextSize; y++)
) m& e4 n" p: l$ R9 @7 }+ _% W( L        {
* B8 C4 [& B( Y! E1 _7 N            WORD * pDst16 = (WORD*)pDstRow;
' C5 H% X! u6 v4 a; P$ A' j            for (DWORD x=0; x<_TextSize; x++)+ J8 D8 P$ Z0 p* m
            {
; b3 t( ^( j9 R( F8 k; \- [                BYTE bAlpha = (BYTE)((_pBits[_TextSize * y + x] & 0xff) >> 4);, l% T. ~9 m1 j0 Y
                if (bAlpha > 0)
3 J  S4 R" F8 T# C$ V                    *pDst16++ = (bAlpha << 12) | 0x0fff;  w9 ]4 l; }2 S/ ]: Y2 B
                else8 A2 _: V. Y9 {
                    *pDst16++ = 0x0000;
0 T! B  H% {2 z2 z+ a$ N( y            }: T: o: V+ k9 S$ V  r
            pDstRow += d3dlr.Pitch;
( H3 D! m# m0 k3 ^, r8 l& z        }
: Y6 Z+ g0 D2 O, n( v" x! W        _pTexture->UnlockRect( NULL );
$ G( m* ~, ?% a9 n/ m3 E: h# |. j    }9 v/ [3 D  p0 _, \/ ~# E: D
    else& C# S( u4 B9 D6 v
    {
! K3 @1 {, E" B- Z" u3 }        // 计算当前空闲的Char在缓冲中是第几个
* s: v' x/ g4 ^, S# C        int at = it-_vBuf.begin();  g0 F5 @# Y: P% ^
6 X5 w. R' R  J1 x) y
        // 得到这个字的坐标( e7 ?) n! Y; X0 x6 C$ D* k
        tX = (at % _RowNum) * _TextSize;8 R" Z7 x! u; l4 a1 r
        tY = (at / _RowNum) * _TextSize;7 M4 Y8 N/ E% P6 O
    }
- ~. H8 F* e4 x6 Q* B- F}5 A3 C7 {+ g* }6 q; ]; G( T: c
以上代码中的注释已经很清楚了,相信无须我多言。这里唯一需要声明的是:原来所定义的Char结构是这样的5 a& q& _5 ]  P& @3 F- R
struct Char{0 ?$ W! Z0 Z8 a
  char hz[3];   // 保存汉字
2 W3 u9 Q/ i! {+ }  }, V  int frequency;// 使用频率
9 {4 ^5 y9 y, |- K+ N: {  RECT rect;    // 这个字对应位图的区域
- d0 \: z3 w, p6 S# f" L+ i8 q) o  Bool isUsing; // 是否使用
* t) {7 f: e5 L( B- Z1 X7 X2 g2 S}
* v9 X( N5 m0 p5 `8 Z5 b" F后来因为将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。
8 @4 h, X( ?( N3 p  [7 p然后对于RECT rect,因为没有了int frequency,意味着一旦将汉字写入到Texture,其位置就不会变动了。所以,很容易根据find函数操作后的iterator,直接计算出这个汉字所在Texture的位置。这样,RECT rect也不再必须。) y( ~$ o1 G7 b; j8 E3 W5 T
而bool isUsing,它本身就是个鸡肋,要也可以,这样结构更加清晰。不过,直接通过观察WORD hz为0或非0,即可实现isUsing的作用了。
5 J, W5 n, s/ |) e为什么要对结构Char这么精雕细琢呢?
0 t3 [) V" A" m7 Y1 p' b1.    既然没有必要的东西,就应该删除
2 p. B& e4 [) G1 S9 |2.    Char结构的大小越大,vector所要求的内存越大
8 w# [- b- V8 _9 X2 c3.    小的结构,find可以更快地查找出所结果; C$ l9 I) @' r8 X6 {  W
为什么find会正常工作呢?这里我要大概地讲一下find是如何查找出所需的位置的:它只是简单地使用while从vector的begin一直遍历到end,逐个判断,直到找到为止。find要求必须实现自己的operator ==(),进一步跟踪到find的源码中,发现也是这样。于是前面的结构Char变成了现在这样:$ F9 r. h4 J7 [: R; r
    struct Char{
8 Z, c$ U% Z1 ~1 t        WORD    hz;                // 文字
. U" f- d* ]: ?8 U9 Q, z4 N* N( e$ v8 I5 v5 Q9 a" ^2 I
        Char() : hz(0) {}( B: I, ?2 Q" [' l
" o* N/ w( k3 _" [+ p0 ~- Z1 ~$ s
        // 用作查找文字6 x% r, w( O- k6 u- x
        inline bool operator == ( WORD h ) const  d% T. U2 a: V' Z/ p
        {# I2 f) f# a$ g- E
            return hz==h ? true : false;6 b& S5 l, r, h5 s0 d
        }: Y5 G% W/ r# s& i+ p9 c4 Q
    };- B& |4 ?8 @6 N* b! i/ g: X
是不是很简单?^___^3 }, z) J" ~9 r5 U+ m; T
% H  ]! M/ z4 x5 L6 n
终于到了显示的函数了:
- n( `" _4 B8 B0 p" `$ r2 h/ V// 得到文字在纹理中的位置
6 G' k8 g. V' Hbool CFont::
: Y3 C9 Y4 o+ L6 H# v/*-------------------------------------------------------------
% F) a$ L4 S% t9 dchar szText[]  ---  显示的字符串( Y* `: |2 x- j- g( |* |+ k1 b
int x           ---  屏幕坐标x
; ]2 w5 ]/ q2 U$ H+ kint y           ---  屏幕坐标y5 V. v6 q- ]# K, w, A8 f7 |, ?; a% k
D3DCOLOR       ---  颜色及alpha值
0 c- e% n. I! T  _" dint nLen       ---  字符串长度5 \0 b; `" B" j% I' r' v8 y0 s2 k' i
float fScale   ---  放大比例
( O6 u/ i3 ~; S+ o  E-------------------------------------------------------------*/
% g3 b, S9 I, B$ t+ UTextOut( char szText[], int x, int y, D3DCOLOR color, int nLen, float fScale )
# x# D# h% Z9 G{- A2 q/ m( J9 o7 v- [" b8 Y
    Assert( szText!=NULL );
) c" D+ L. w( X# g% y. {8 Y1 O* ?7 x3 j6 \) L' Z
    float sx = x, sy = y,9 @# f* d" ]/ ?6 B6 R5 u7 ]
          offset=0, w=0, h=0, tx1=0, ty1=0, tx2=0, ty2=0;8 M, y5 ^& R" G0 t8 r0 }
    w = h = (float)_TextSize * fScale;
% f- w# j  p) O: Y6 c# W7 o4 h( O
    char ch[3] = {0,0,0};
- P% ^+ M9 M6 [$ r2 C6 t' \- A& ~    FONT2DVERTEX * pVertices = NULL;
7 A  G/ B. u7 |4 D3 @$ l7 ~    UINT wNumTriangles = 0;
/ o1 q/ L/ E% H' R    _pVB->Lock(0, 0, (BYTE**)&pVertices, D3DLOCK_DISCARD);" w! C# C6 }* M% W) G: @3 r

1 `# s) Z2 i" U& d% D* c% t    if ( -1 == nLen ||                // 默认值-1+ b1 `' R9 W8 g
         nLen > lstrlen( szText ) ) // 如果nLen大于字符串实际长度, 则nLen=实际长度
. F; P8 O& ~# i4 _& z. Y        nLen = lstrlen( szText );
* [1 [' D( A5 i) u    for (int n=0; n<nLen; n++ )7 V9 a1 {" @4 u$ U+ ]5 z8 e1 `
    {
# ^+ D4 A. ]5 O( G        ch[0] = szText[n];& a  b, O  s& Z; v$ z: o

) T- H, ?4 d/ A6 Q. v* n        if ( ch[0]=='\n' )
$ B  j- \# a0 p4 Y0 C9 B8 M/ ]        {2 o* n. ]2 o9 r& \; V0 `
            sy+=h;! q- b  ?9 d# z8 M* A
            sx=x;& |6 g1 D* I* J9 d2 i  E
            continue;
/ N% T; B0 B3 n' I' a  J% U        }
' p6 e0 y- h' G5 @3 k1 o* J
$ g1 f8 |0 w- d* Y8 F& {$ A6 Y        if ( ch[0] & 0x80 )
$ U" S$ A- ]: F- H) k  O        {4 _" Q2 J+ i$ @; M' e
            n++;
3 k7 k; H  B7 `$ D1 z" m; n            ch[1] = szText[n];
0 y- [$ {  X, C7 ~! M            offset = w;8 x5 G% C$ ^% C% p( U$ x
        }) M: C; o3 I1 l- E2 @! h3 l) H9 p
        else# F/ P% r! Y* Z  X$ T
        {
, I% B. l" x, n$ z2 o4 l            ch[1] = '\0';
; c+ I) j. ?3 _1 d8 B. r' X6 ]            offset = w / 2 ;2 Z/ y: c5 q3 n6 h- z( r
        }
: L! V, V- ^& s& e
) E  V+ a% h% @" {4 d        int a, b;
0 }6 A% D; U9 x- A! q        Char2Texture( ch[0], ch[1], a, b );
4 Y' x0 h. b/ r7 @3 ]$ Z2 G4 d) L    $ b+ v6 c3 a- x% I
        // 计算纹理左上角 0.0-1.0
7 N( O1 j2 M1 h# n( M9 v; c        tx1 = (float)(a) / _TextureSize;1 ^8 l3 T8 _) U
        ty1 = (float)(b) / _TextureSize;
- ~. j# v- W& c( R% I& `6 J        // 计算纹理右上角 0.0-1.07 p; u) e  i+ ~/ w; v3 A2 `+ |
        tx2 = tx1 + (float)_TextSize / _TextureSize;
+ U8 ?" O# k7 L7 c' Q& d, Q        ty2 = ty1 + (float)_TextSize / _TextureSize;% E  X* x: T, ]) Z% X
, e# ~' D8 ?( P3 _
        // 填充顶点缓冲区
" \2 G6 [  G% V3 C3 k1 q! H9 a, `        *pVertices++ = FONT2DVERTEX(sx,        sy + h, 0.9f, color, tx1, ty2);
7 z+ M. j9 g; q  A' t        *pVertices++ = FONT2DVERTEX(sx,        sy,        0.9f, color, tx1, ty1);
% N4 l, q% k" P$ h- B4 w$ q        *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f, color, tx2, ty2);
& h; `2 |% y$ V* O        *pVertices++ = FONT2DVERTEX(sx + w, sy,        0.9f, color, tx2, ty1);
8 J5 d0 a. X4 x! Z. Z. P! m( m        *pVertices++ = FONT2DVERTEX(sx + w, sy + h,    0.9f, color, tx2, ty2);
) t- P5 i8 S; L" S) A5 A        *pVertices++ = FONT2DVERTEX(sx,        sy,        0.9f, color, tx1, ty1);2 E3 U' ]; ]. I* S7 J/ D

. {- E+ c, o# c. s. C        wNumTriangles+=2;
, ?* L: s9 r& \: \" Z5 O9 O# H
        sx+=offset;    // 坐标x增量
( w: d! M8 C2 P6 f6 d# _( D    }: `& G/ w' M3 m+ _
    _pVB->Unlock();
* L( V  K1 s+ L0 b- P" s    _pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, wNumTriangles );7 t7 t0 D3 k3 m, E

' \& h. f2 |0 y/ y2 d6 n    return true;
' z/ d3 E* s0 O. _6 o  C8 a- g}
  H) D5 N8 i( y& Y3 Q7 h结束语
3 w  R& y9 U4 W8 h. X7 a* \/ U6 g- G7 W记得有一句名言: Keep it simple and stupid.在实现功能的同时,保持代码简单、清晰是非常重要的一件事。相信在往后的日子里,在不论是别人阅读或是你自己回顾的时候,你都会发现一如既往地遵守这个守则,是多么得重要!
. n, Q' }% |( y# h8 X相信通过上面我那无数的废话,加上代码中还算足够的注释,聪明的你一定能够明白这其中的原理了吧。如果以上的内容还不足以让你完全搞清楚的话,你可以登录我的主页:
: [* P, i; K$ D  D$ m, i炎龙工作室
& P8 R% @1 [% a6 w6 e( U上面不仅包括了上面所写的程序代码,还有一个用来演示效果的一个很简单的demo。
: z% y9 {+ d. S7 r$ k) K8 |说明,以上所实现的CFont是包含在我的游戏引擎中的一个部件,而目前已经实现的部件包括有:
' U: @  S. u; ]. F* u1.    CGameFrame(游戏框架类)  -----  封装了窗口及D3D设备的建立,需要派生出自己的子类
8 O( C  G- q3 t! d. E2.    CAudio和CSound(声音类) -----  支持wav/mid/mp3的播放$ {/ A6 F7 e; F" n
3.    CDirectInput(控制类)    -----  键盘、鼠标操作3 \  h% i8 _8 U
4.    CDirectShow(视频类)     -----  支持avi/mpg/mov等的播放/ w5 v% U8 g/ m& @) B' c$ Z) l
5.    CSpriteX(精灵类)        -----  方便游戏中对精灵的控制0 X8 q  W) k% Q- B. k  C8 f: {
6.    CFont(字体类)           -----  中英文字体的显示
: L; v2 l- j% C% R: d" }7.    CTimer(时间类)          -----  高精度时间的控制( u9 v" F  v  E; o) v% G+ w
8.    FPS(fps 类)             -----  fps的计算
( ~4 l/ Y- x4 @2 T& O9.    LOG(日志类)             -----  游戏中的错误反应以及状态记录0 ~- K5 ^, E9 [, Q2 F6 q
最重要的是,这个Game Engine完全是开放源代码的。关于更新的情况、版本说明以及源码下载,请随时关注我的主页!& B* x, Q2 c/ y+ D! ?
接下来,我将会继续完善这个Engine,可能加入的有:高效粒子系统、斜45度角地图……
作者: mtvc349    时间: 2008-10-25 10:59
原来汉化这么复杂啊,真的由衷感谢这些游戏汉化爱好者,你们辛苦了




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2