讲了一些TTF的知识,也许对汉化有用! i! b" k: Z$ l* m
. Q) }& L m4 K7 B: I6 Q
0 v2 w) w& S) W4 x9 {3 J. H+ ?- ^
游戏中汉字显示的实现与技巧
1 ~, O" C9 c6 g. A8 S3 E, V作者:炎龙工作室 千里马肝/ m% N( e) @, K8 V- I
版本:v1.0! a @# [0 @4 u' r8 [! P: K
最后更新日期:2002-3-30
+ R/ _9 c9 x+ [' P4 j绪言8 I+ F. ?" c4 A Y
在游戏中,因为我们是中国人麻,通常都需要显示汉字,比方说交待剧情。而对于文字的显示,英文的显示要较其简单得多,因为只有26个字母,就算再加一些标点、符号什么的,用一张位图,就可以足以显示所有的单词了,而相关实现技巧,也比较轻松。 * v3 u) m: ^7 _
而中文的显示方法,要复杂得许多。记得原来在DOS下,汉字的显示都是读的UCDOS的点阵字库,而点阵字库的读取方法,在UCDOS SDK中都有源代码可以参考。但是自从Windows操作系统开始,我们开始了解到一种更好的字库,它就是TTF。
/ c4 m+ W4 ]6 d, P* A注:以下我所指的开发环境,除非明确说明,默认的平台是VC6.0+DirectX8.1,使用D3D来加速2D。然后使用的STL是用的SGI实现的那一套STL。
, a2 T0 {$ V: R0 V8 Z1 L% c3 w" y0 u* H点阵字库4 c9 c2 G& V4 Y/ u* C6 S+ N0 K
包括现在,有很多游戏都还是使用的点阵字库。因为操作起来比较方便,加上这方面的经验已经积累了好几年了。通常如果只是一种字体就可以满足需要的话,它会是一个比较好、快的解决办法。但是它有3个缺点:4 Q7 f5 u% x& f2 u
1. 如果放大显示,不做处理的话,显示出来的汉字,是很难看的。
1 B3 ?0 m" K, `; }, Y1 G2. 像是UCDOS所提供的点阵字库,只有24点阵的有几种字体,如:宋体、黑体、揩体…,而16点阵的好象就只有宋体一种。; e6 j0 H( h+ B
3. 点阵字库,通常是有版权的,尤其是第三方制作的汉字库(如:方正)。
3 H( Z- q$ y$ x1 k' d在这样的情况下,当我们写好这样的一个显示函数,就算是解决了如:放大、快速显示等问题的话,可供选择的字体还是太过于局限了。所以,在字体的要求比较强的情况下,点阵字库并不是一个好的解决方法,他不够灵活。尽管我们对于它的操作是如此得熟练,可以写出优美的代码来展示我们的编程技巧。
4 d! A" i- d5 P8 d1 M$ K2 CTTF
4 `4 q( E8 }) l% f4 JTTF是True Type Font的简称。在Windows\Fonts目录下面,我们可以看到许多后缀为ttf的文件,它就是接下来我们接下来所要谈到的。
* k/ Y3 N& X- U, ]. H$ jTTF是一种矢量字库。我们经常可以听到矢量这个词,像是FLASH中的矢量图形,在100*100分辨率下制作的flash,就算它放大为全屏,显示出的画面也不会出现马赛克。所谓矢量,其实说白了就是用点和线来描述图形,这样,在图形需要放大的时候,只要把所有这个图形的点和线放大相应的倍数就可以了。而且,在网站上有很多的TTF字库可以下载,或者你可以去买一些专门的字库光盘。然后在你发行你精心制作的游戏时,可以顺便捎上这些后缀为.ttf的文件就行了。包括Quake这样的惊世之作,也都是用的TTF字库。
" v- ?) l9 x, J这样,我们就可以解决点阵汉字的一些问题。通过TTF,我们在字体的质量和字库的数量上获得了暂时性的胜利。
) j0 X3 Y$ ?% q5 k0 v+ ^字库的读取和显示0 c9 T$ B3 y m/ O
先前谈到点阵字库,只需要很简单的一些操作,就可以显示出想要的汉字。下面我给出一个读取hzk16的函数,它需要一个Surface以供显示用:1 l# s* S0 s' K
#include <io.h># D( J+ ?# U4 e, I
#include <stdio.h>3 u9 ^4 C( I7 z7 f) o7 z
#include <conio.h>2 c' F; z, h. G0 ]7 E: _
/ Y8 Z3 S+ {& `7 x- u4 \// 读取16x16
! p H; F+ B! jvoid DispHZ16(int x, int y, BYTE *Str, LPDIRECTDRAWSURFACE surf)% l! O- z* Y2 g$ T
{
9 q' n, s2 {* r# m8 E7 m const int Mask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
% d1 V: h. H! O6 Y' H D1 W) c FILE *HzkFp;- l0 V3 S; Y- e# g( v$ d0 h
WORD i, j, k=0, m;4 S, f4 Z; t `" }9 e5 i
WORD HzNum;- ]* W6 U8 \" b" ~$ Y; i( a
WORD QuHao;
; G$ W- C$ k5 [" r* o" n) j. a* ^8 o WORD WeiHao;
3 D A2 y* @+ v" X' e: g long offset; D1 G1 ^0 l# S4 F/ f2 i% e
BYTE dotBuffer[32];
# C4 h; X3 N; u. m0 S* R/ Q
5 e8 G1 A. w* j HzkFp = fopen("HZK16", "rb");1 U3 i+ A4 z6 F' n2 `: O
/ k; l/ P& v8 \( ^6 C5 {( q* @ HzNum = strlen((const char *)Str)/2;2 @8 j* \ x' u: X
4 c3 I' D; Y$ w
DDSURFACEDESC ddsd;
$ O5 P( v9 A# L LPWORD lpSurface;, m ?, s% |# H
HRESULT ddrval;+ l+ _, c( E* c3 b( R5 d
9 m: z. \7 j) g" f; Z7 `
ddsd.dwSize = sizeof(ddsd);4 Q5 H; j% `0 W- J
) d# r+ w+ S2 A% v1 w* E' u while((ddrval=surf->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING);
4 a- G5 y% e# p: u if(ddrval == DD_OK)
( d L) `8 o4 h9 O lpSurface = (LPWORD)ddsd.lpSurface;
3 k- u; \6 H8 [
6 L: c- I/ E( i' M- Z: a7 y# P. R for(i = 0; i<HzNum; i++)0 _7 _( t. e! U5 L' M9 C, U' I: z
{ / x% n/ d0 @& r5 l; c2 j+ a, c+ z
QuHao = Str[i*2]-160;# j. M& g- N+ W
WeiHao = Str[i*2+1]-160;& N6 [: F) H, ?. j& i' K$ G% }
& L* W* o/ i- F& c9 t5 u* `7 u offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;
2 r& C- U. A) ]' t7 T) [
- r7 I u* b- ?2 B& `9 |4 U. T fseek(HzkFp, offset, SEEK_SET);% g/ l2 e% [" U- }
fread(dotBuffer, 32, 1, HzkFp);
; P& G4 s. y* M7 R" `2 t' e. J6 q1 w$ W5 e4 `0 ~( S& X5 L- }
for(j=0;j<16;j++)
0 x4 Q& X2 `. j for(k=0;k<2;k++)
) C* a3 b/ Q: j- K9 U2 k& Q- T; D for(m=0;m<8;m++) 7 Z; l; _0 ?+ b! @" T7 t
if(dotBuffer[j*2+k] & Mask[m]). O/ J1 T9 f- T
{
3 L- w& W4 t- ?2 ^3 S" i lpSurface[ddsd.lPitch*(y+j+1) + x+k*8+m] = 0x000000;
$ f/ R' A; k' U# ^2 h# c ~2 p }
: }% _7 _. N% ^; ?! K x+=16;/ l% }) A( i+ |0 G: n) ~- o7 p# q. }
}+ U* r+ y0 {- V
) j" L# t) e) W$ K; M. E. U
surf->Unlock(NULL);/ M" ]* i0 Q* d5 A% a# t
1 p/ I) c7 X; u fclose(HzkFp);
& U* n) E) e0 C, n}
7 c$ `4 M7 x9 @( c7 U# i! q其实原理很简单:
# f3 [; j# R/ H v1. 打开字库
4 R; L2 N7 @' {% I) `2. 计算字符串长度(这个函数只支持中文),并且Lock Surface" d, B) T( N/ K6 G7 P1 x4 S
3. 依次计算出每个汉字所对应的区码和位码(汉字的第1个字节是区码,第2个字节就是位码),然后通过公式计算出这个汉字在字库中的偏移量:
3 V. D+ H9 {* x; G' x2 Goffset = ((QuHao - 1) * 94 + (WeiHao-1))*32;
) w5 C- @1 l! `4. 读出一个32个字节的点阵
5 ]9 ~' o _% k# s9 V8 Y5. 绘制到Surface上. n1 i9 e0 C& ^/ J: X
以上只是16*16点阵字库的显示方法,24*24的读取方法与之类似,大家可以参照相关资料来书写出自己的代码。
- \# j( v. ^( s9 ?) r# i! N8 z$ c. x2 ^% ]: q
如何显示TTF字库呢,有很多种手段,下面我按从简单到复杂的的顺序依次介绍:: X! X& E y a, U
1. 使用Windows API,也就是大家所熟悉的TextOut。通过它,还需要一个HDC(设备句柄),我们就可以随意地在屏幕任何地方显示出文字了。
% ?% t) O- D! n5 N. L. Q2. 在http://www.freetype.org,有一个FreeType的免费库,而且是OpenSource的。它目前有2个版本:1.0和2.0。其区别在于,1.0只能读取TTF格式的,而2.0支持更多的文件格式,在使用它之前请详细阅读所要遵循的Licence,以下是摘自FreeType2.0对字库的支持列表:% [( G* ^( I% j C8 h
o TrueType fonts (and collections) . d7 j3 x( @+ l
o Type 1 fonts
6 H. x' Z% v/ ^8 |' ?$ m, O: Po CID-keyed Type 1 fonts $ ?. j1 y. n/ E" f7 v( m8 q
o CFF fonts 2 e- s/ J9 ~/ S4 M$ q* F! c
o OpenType fonts (both TrueType and CFF variants) " x, x. q- O4 G
o SFNT-based bitmap fonts
4 [$ C X" {( ?; f* u) c9 Io X11 PCF fonts + L# O! S4 r! \8 n# ~+ x6 i: }
o Windows FNT fonts
5 A; Z4 [4 S: V2 i1 z3. 自己研究TTF的格式,然后自己来操作。
@1 ] m2 h# u1 I2 m+ h晕
& ~( b; f" `! E5 A, i9 J" n....... ╮╮ 7 _! u$ B$ c. z/ i0 R( r6 a/ k
\█/倒!
/ h- Z9 t& o4 g9 m( u, @* V; p ●
! m+ ?8 ]1 O6 u虽然我们想要把每一件事情都做好,但是也不是每一件事情都要亲历亲为。如果你非要这样,也行^____^,但是过不了多久,你就会陷入泥沼,到时候你会发现自己的热情正在慢慢被磨灭,什么叫做抓狂,相信你很快就会知道^_^。
3 R, x7 E. H+ R* Z) U; R. }( d& l. B+ g4 F. g; J- K, y
在有多种选择可以取舍的情况下,我们需要考虑一下,对比一下各种解决方法的优劣。
, h% O+ q8 W$ x/ i2 j1 H: ?" `9 p' Z9 y% W# h& T" \
在DirectDraw时代,我们都不自觉地喜欢上了GetDC,因为……多方便啊。可是现在已经到了DirectX8.1时代了(我要使劲地摇那些还沉醉于DirectX7中,为如何在使用alpha时提升那可怜的1、2个fps的朋友们:醒醒,该起床了!),HDC已经被M$列为禁用品。怎么办呢?是的,你可能已经想到了,我们还一直保存着窗口的hWnd呢,可以通过它来得到hdc,从而调用那些需要hdc的API,可是,这样做是更为愚蠢的,这样对你是没有一点好处的,不信,你就试试吧。有一句话,请牢记:要想你的游戏有更快的速度的话,请不要再去碰HDC了。) e4 c% D" ~, b$ f* q+ _0 Z
我们非常清楚hdc是一个超慢的解决办法,它无法在我们的高速游戏中满60分及格。下面来看看FreeType,它更像是一个Service。它的解决方法是,先通过一系列的初始化和设置,告诉FreeType字体的名字和大小等,然后它会动态地申请一个Graphic,再把我们要显示的字画到这个Graphic上,你还可以把它保存为tga格式。不过我们最终所想要的不是这个,所以可能我们还需要从这个Graphic上逐点读取或者用CopyRect,然后再画到我们的画面上。其实它已经是很方便的了,可是需要你去学习如何配置和使用它,这是很花时间的一件事情,而且它最大的优点是可以跨平台,我们需要它吗?如果有一个更为简单的办法,像是如果Textout不是那么慢的话,就好了……! U9 I* e/ E+ B% [$ j, `' K4 D0 A: i5 c
在这里,顺便谈一下另2个字体显示类:ID3DXFont和CD3DFONT。可能早就有人会说怎么在上面的列表中没有它们?原因我会在下面慢慢地说明:0 Q, S2 L7 k( X; R
ID3DXFont,它存在于D3DX库中,一个现成的字体类,不过对于它的处理方法……我实在不敢恭维,就引用一位大师所说的话来表达我的看法吧: 在内部实现中, ID3DXFont::DrawText()函数确实做了我上面讨论的工作,先建立一张GDI兼容的位图,把文本绘制到位图上,而后把位图拷贝到纹理贴图上去,最后把纹理渲染到屏幕上。这样你就聚齐了所有的龟速的原始GDI函数,还包括了一大堆的额外开销 — 最终,这个函数比原来GDI的DrawTextEx()函数要慢上超过六倍……
& `$ o L% N+ U) t6 `5 r6 e( F6 zCD3DFONT,是由M$在D3D的框架代码中提供。不过它只能显示英文,有很多朋友通过自己定制和修改这个类,来实现自己的中文显示。不过效果都不是很好。其实原理,跟ID3DXFont的方法差不多,不过处理方法要聪明了一点。
+ M, {1 |2 r7 b: {, Z' e分析与思考
: A) I# N/ i% Y, a2 z$ F: y$ K那么我们应该怎么办呢?通常我们会幻想,如果可以像处理英文那样,把所有的汉字都保存在一张位图里,该有多好。这样,显示的速度就不是问题了,直接可以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。
5 s& m1 |+ x2 u+ i& X P汉字虽然很多,但是常用的汉字,其实也就只有那么几百个。像这样的字:鬯、鞴,你一辈子会看到多少次呢?如果可以做一个类似于Cache的东西,保存着常用的那些个汉字,在需要显示的的时候,先在Cache中查找,如果有的话,就马上画上去;如果没有,就从字库中提取到Cache中。这样的话,在使用Texture来保存汉字的位图信息的同时,对于每个汉字,我们还要定义一个结构,然后用一个东西把它串起来,综合它们2个,也就实现了我们所要的Cache了。刚开始,我所定义的结构是这样的:4 B* K2 A; L9 B# J1 t# B0 O
struct Char{3 ?% g2 ? R* @' J) I
char hz[3]; // 保存汉字: H# [- K: H" F( ~& o$ L- v
int frequency;// 使用频率
8 | p9 B: k8 o5 _1 Y' r' I; B RECT rect; // 这个字对应位图的区域8 x. j9 Q6 n1 M1 N) o
Bool isUsing; // 是否使用4 x( g, N) t% s$ a3 k
}! [: h/ w9 t- l* z) Y6 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’。& W, Z, j- ]6 Y- K1 C- J1 n
接下来,对于使用char[3]来保存汉字,是否真的很合适呢?因为如果把它当作一个字符串来看的话,在查找时就需要使用 strcmp 来比较字符串了,这样一定是会影响速度的。如果不把它看作字符串(字符串的最后一个字节需要以’\0’结尾),只用char[2]的话,我们可以只是简单地调用宏MAKEWORD,把2个byte压成1个WORD。当把文字作为一个WORD来看的时候,这样查找比较时可以用WORD内建的==操作,这样要比调用strcmp函数要快得多。7 N- q& y5 U' [/ O. i1 S( C
int frequency用来标志每个WORD的使用频率。设想,如果一个字已经存在于Cache中,以后每对它调用一次,就让frequency++。这样做还有一个用意是,是否可以在一个合适的时候,以frequency为参照来对这整个Cache排个序,把常用的字放在前面。那么在显示时,可以先在Cache中查找所要显示的字是否已经存在于Cache中,如果有则直接显示,没有的话才需要采取某种手段将字加入到Cache中。一些常用的字(像:我、的、着、了、过……),使得显示的速度将会大大提高。9 E8 ~+ c2 `7 X9 |" @: @3 K
其实上面说了半天的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上的区域位置。
0 E, `" ^) B3 ~6 o! R使用什么东西来把这n个Char串起来呢,一般会想到的是链表,原因无非有2个:1 随时有新的字加进来,而内存是不连续的 2 它几乎没有容量的限制(除非是内存用完了)。不过链表的访问速度是很慢的,如果使用像数组这样的东西就好了。仔细想想,在这里,我们用来存储的Cache,最大也就是256*256(理由上面说了),所以大小应该会是固定的。我们只需要在数组中的给每一个汉字加上一个标志,说明这个位置的使用情况。那么就使用数组吧,这样的话,访问的速度要更快一些,直接首地址+偏移量就够了,不必像链表,在查找时需要逐node访问。当然,我绝不会想到用new Char来申请这个数组。因为这样做实在没有必要,请不要过于迷信自己的能力,在STL中已经有vector了,为什么还要自己写呢?^_^最后的一个bool成员变量isUsing,也就是上面所说,用来标志使用情况的。" y7 `1 P' @7 `/ y3 h1 ?
实际的操作: P5 l: e" }& X# A7 Y; M) @
上面考虑了那么多,我认为都是实际操作之前所应该有的。先谈谈如何显示吧,因为在DirectX8.1中已经将DirectDraw和Direct3D融合为DirectGraphics了。所以无法像原来那样了…………哦,实在有太多东西要讲了,我还是推荐几篇文章给你吧^_^:
5 o- Y G" d+ \http://vip.6to23.com/mays/develop/directx/200201/Geczy3Din2D.htm
. x' \, _) o( V A6 x, Rhttp://vip.6to23.com/mays/develop/directx/200201/GESurface.htm6 Z4 p4 N% o# q( \- n
http://vip.6to23.com/mays/develop/directx/200112/2DGtoDX8.htm" B: y. U' r- A% v4 X: p0 ^8 t3 T
http://vip.6to23.com/mays/develop/directx/200201/DX8adv2D.htm
/ }& D1 j# O5 m/ Y接下来,我会假设你已经具备了在DirectX8.1中绘图的基本概念了,所以在你继续往下阅读之前,请务必先仔细阅读以上推荐的文章。, ?$ R& }# D8 J$ U% H' O8 M, v$ {
前面提到,需要一个vector来对应Texture上各个位置文字的信息,上面已经创建了一个结构Char,则这个vector的定义为:
) {7 ` s# i6 l/ x' Z; e( t vector <Char> _vBuf; // 记录缓冲中现有的文字情况
# q! N% G( B8 ?$ o6 }: g) o4 d首先,由于可以利用硬件的放大缩小机能,所以字体的大小精度要求不是很高,只需要支持16*16和24*24大小的字体就可以了。我们需要一个这样的初始化函数:- Z# V8 v( K+ ?# f4 ^1 K
bool CFont::
: _2 }0 N2 T# s. {/*-------------------------------------------------------------. l9 {5 D* J/ I4 ?+ B. l
LPDIRECT3DDEVICE8 pd3dDevice --- D3DDevice设备8 o" B; W2 t; L
char szFontName[] --- 字体名(如: 宋体)
/ O: z8 V q+ a& B) | y3 Cint nSize --- 字体大小, 只支持16和24
* t' e# ^ l o; h, cint nLevel --- 纹理的大小级别6 d5 O$ P; [4 z5 u7 X! D
-------------------------------------------------------------*/. n" i6 U3 B+ [! v6 h% M
Init( LPDIRECT3DDEVICE8 pd3dDevice, char szFontName[], int nSize, int nLevel )。. d8 S4 m0 R. t8 W/ T
6 {$ v/ \" |' J; Y) y
在DirectX8.1中,由SetTexture(…)所贴的图的大小,也就是Texture的大小,是有大小限制的,长和宽都必须是2^n,而且位图越大,所花费的显存越大,这样留给其他显示用的显存就少了。所以,必须根据需求的不同,来自定Texture(也就是Cache)的大小。因为汉字点阵大小的原因,所以从实用角度而言(比方说只是显示fps或是短小的标题),开辟一个64*64大小的Texture,才能满足最低情况下的需要(这时如果选择16点阵的话可以存放16个汉字,24点阵可以存放7个,依次类推……)。
. `( `; @+ o- D3 O. N& G% }! c根据设置,创建Texture:
2 M- q8 U; `* I0 u6 Z# Z _TextureSize = 32 << nLevel; // 纹理大小
2 s' J! H; _$ r! J2 U _TextSize = nSize; // 文字大小# U! w8 |, B0 z8 Q/ Y( U7 Q6 e
_TextureSize = 32 << nLevel; // 纹理大小$ R L1 |. L$ I; Q. B7 y
5 Y+ W" I& z2 J
_RowNum = _TextureSize / _TextSize; // 计算一行可以容纳多少个文字
1 y) H% |6 O4 e8 X# g+ d _Max = _RowNum * _RowNum; // 计算缓冲最大值
5 h. G/ `( i4 M: P2 N; W9 i. B0 K$ }7 H+ m& N( S5 h* X8 i
创建字体,还是需要使用Win32 API。也就是先创建一个HDC:
2 Z% Y8 M1 Y7 _4 Q- a _hDc = CreateCompatibleDC(NULL);5 O* j/ Z' @6 v, n, D# f% {
* y: B+ K0 X' H" \/ `然后创建一个BITMAP和一个FONT,将它们与HDC关联起来。
- E9 ]1 h; ?2 a S# \ LOGFONT LogFont;1 O9 R3 a- ]' A
ZeroMemory( &LogFont, sizeof(LogFont) );; V& M6 V1 M. U& u, ~
LogFont.lfHeight = -_TextSize;* O; t8 c' A$ g# b; V: r
LogFont.lfWidth = 0;8 c {# m: \9 K& A- @
LogFont.lfEscapement = 0;& C2 ?' c+ j7 v3 `) e! P
LogFont.lfOrientation = 0;
$ r# A" c/ {" Q' t4 [ LogFont.lfWeight = FW_BOLD;
, c. H/ ]8 ^8 v* N# n1 i2 X( y( R LogFont.lfItalic = FALSE;% Z. I; G. a8 T
LogFont.lfUnderline = FALSE;
6 v" J7 Y& ^' t% q" r3 n LogFont.lfStrikeOut = FALSE;- N; {6 J+ U& d* z D; Y
LogFont.lfCharSet = DEFAULT_CHARSET;$ Z: X8 s. s+ R- o( V
LogFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
8 k j+ C7 w `3 H2 ] LogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; , l+ b* C7 j. |, w& o Z8 {
LogFont.lfQuality = DEFAULT_QUALITY;
4 I- e7 u* d# x R/ k" A8 w! M LogFont.lfPitchAndFamily = DEFAULT_PITCH;
& |: c/ }5 r* L lstrcpy( LogFont.lfFaceName, szFontName );
4 f: C0 \: @( K! t* s % \/ `( c. j/ |
_hFont = CreateFontIndirect( &LogFont );
0 |; e9 Y4 T2 N8 H( h% Z if ( NULL == _hFont )
% Q" t7 r0 E, I5 G& P {4 T7 z; y$ z* u8 F$ }7 ^
DeleteDC( _hDc );
2 H3 i E( ~4 s* ]7 @ return false;
* v: ~$ o0 s& S" e# A6 A }
4 R; c& P5 n% K2 o7 z
/ d0 q' v6 s) J1 t8 h(只需要创建一个字体大小的BITMAP即可)4 [$ ?: {* T9 i0 |+ b
BITMAPINFO bmi;" H% g0 v6 |9 y, P, z( V1 b
ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
" }$ s6 m G7 F; @+ [8 ^5 C# {# L bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);9 B* S) h7 ?1 m4 a, A! P
bmi.bmiHeader.biWidth = _TextSize;
6 s+ P( P! }$ d1 V! Z6 \, V9 v; y bmi.bmiHeader.biHeight = -_TextSize;
& W! o7 `; S4 y3 o+ R bmi.bmiHeader.biPlanes = 1;
- A- D3 _& y# ]! Y: l, S, _8 B bmi.bmiHeader.biBitCount = 32;" C6 W1 Q7 C- C
bmi.bmiHeader.biCompression = BI_RGB;
# g3 y8 Q2 k) \# |" P7 _+ R 4 H3 g- Q/ T) ~# O* ~' w V4 ?
(这里需要定义一个指针指向位图的数据:1 X0 W4 z* }- k8 f0 [+ ~
DWORD * _pBits; // 位图的数据指针)- ?8 H( j. D5 p$ ^% S
. F5 ]4 f7 a3 p/ } _hBmp = CreateDIBSection( _hDc, &bmi, DIB_RGB_COLORS,
o& k* I9 L- K' F0 y5 ^ (void **) &_pBits, NULL, 0 );5 Y a. z& c! w7 s# ~9 W$ b2 p
if ( NULL == _hBmp || NULL == _pBits )! J! P7 G6 P$ S/ X% T' i
{8 \$ c3 ?: H5 N* ^& V( y
DeleteObject( _hFont );$ `2 U5 ?* m. w$ Z- D
DeleteDC( _hDc );
) ]. ~/ w, O4 K return false;
4 d/ m' ?0 n" G3 c0 } }8 _, o5 v; j4 l' v; V6 c- B
* b3 n$ |$ H5 | g) E
// 将hBmp和hFont加入到hDc2 O0 [9 F$ w" j& t \- @
SelectObject( _hDc, _hBmp );) [0 b6 S+ g" ^* O6 C' }( `
SelectObject( _hDc, _hFont );. i) q6 f; }* T& s, ]% F% [
: y. h% I+ M3 \: `$ s- [接着设置背景色和文字色:8 j9 ~9 m3 g+ p5 F! Y+ v3 E
SetTextColor( _hDc, RGB(255,255,255) );8 D' P& h( v! g* U; P8 D
SetBkColor( _hDc, 0 );
3 H- G4 {' Q( l4 @& o5 W
& v; {/ U4 k. M设置文字为上对齐:% @" v& g0 o2 M& s
SetTextAlign( _hDc, TA_TOP );
0 R, G. T+ N; ]0 b' w0 e
& c: _& e/ e: J创建Texture所需要的顶点缓冲:- @& B' m- W% }4 u
if ( FAILED( _pd3dDevice->CreateVertexBuffer( _Max * 6 * sizeof(FONT2DVERTEX),/ C$ Q# @/ J9 |. J2 J
D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,
) m: W W# c; o; i% J1 F# H8 k D3DPOOL_DEFAULT, &_pVB ) ) )
$ q( K3 f" r4 U9 H/ Y' \. ^ {. d f8 Q: s' V
DeleteObject( _hFont );7 W z0 l9 w, r' z9 k! ~* d k- g
DeleteObject( _hBmp );
- g" U9 U' j t9 Y5 v3 s DeleteDC( _hDc );
4 b( v* A1 X {/ b return false;
{' D ~& @/ z( l j; v. v }
! {+ ]. J/ ^+ n/ q9 I, X8 h+ F
- Y7 O3 c; _% p- P, d T创建Texture! F4 C3 {; c% {$ f% @
if ( FAILED( _pd3dDevice->CreateTexture( _TextureSize, _TextureSize, 1, 0,
! w9 O7 W( q6 P; D/ O$ | D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &_pTexture ) ) )* `8 x A- h! e4 w( i" X1 R2 T7 W
{
( E0 p# t9 _/ g* U/ l. u DeleteObject( _hFont );
6 I% R) n4 _/ d+ d) y/ Z DeleteObject( _hBmp );! {, k* L) i4 l, }3 l! C- N
DeleteDC( _hDc );7 s- n8 `1 B$ n# I0 V
SAFE_RELEASE(_pVB);7 y, ]. k# T/ [* f$ o" J& e% |
return false;0 N0 Y/ [& S1 |
}9 h! i, m$ B# |( |! e* N
; R: l+ x8 n( A0 b# |. X
设置渲染设备的渲染属性:% ?! a N% h$ {
_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
9 I! P: b. S0 f) \6 r+ M _pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );* P0 B0 u: a( z* \2 d6 Q
_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );; a! K8 S& \! k7 V, G% o
_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE );
& m2 r4 h: d0 |( Q% |% `4 D1 Y! f _pd3dDevice->SetRenderState( D3DRS_ALPHAREF, 0x08 );
0 g& c {: G3 ?: }$ | _pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL );
, g& a. K- p2 _0 V) ~ _pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );! p. W) {0 W( f! W
+ f1 O u2 \4 K C: ]6 ^7 q _pd3dDevice->SetTexture( 0, _pTexture );& `. c. ]" F, e8 q+ `
_pd3dDevice->SetVertexShader( D3DFVF_FONT2DVERTEX );
, c3 S6 `; v o _pd3dDevice->SetStreamSource( 0, _pVB, sizeof(FONT2DVERTEX) );
6 z! r& L1 K/ I8 K# `3 `. |: Z5 e0 U6 d! Z% v# S' n
设置缓冲的最大容量
3 H9 X) n# m6 m- T/ A; d _vBuf.resize( _Max );; U* B% f! _0 R$ n& Q) o
, a& B7 m) v. t# e. E! s. S这样,初始化完成了。接下来是如何把一个汉字写到Texture中,以及如何进行管理。定义函数:5 W# J& V( L* z5 \ s( R
// 得到文字在纹理中的位置3 s6 v# V1 m8 O4 D. R
void CFont::$ @9 x( P3 ]8 i, N; z
/*-------------------------------------------------------------
) V$ P3 u" I) R9 r$ Lchar c1 --- 文字的第1个字节" L1 I/ h% @; F0 L. ~& h; H
char c2 --- 文字的第2个字节2 n' Y* O1 o8 H q( U4 W3 j
int & tX --- 写入纹理中的坐标x, ^# ^. Y# A: j( [# ^5 M3 G
int & tY --- 写入纹理中的坐标y! d. N3 ]% K1 q1 ^1 t3 D7 r* j. n0 F
-------------------------------------------------------------*/
$ {; n: k( N3 v7 AChar2Texture( char c1, char c2, int & tX, int & tY )
1 i' G0 V/ \% V. B7 }5 I3 o$ _{) F0 b, ~& r3 W, H) I# o+ E
WORD w = MAKEWORD(c1, c2); // 把此字变为WORD
) g3 m0 O+ Z7 s& r7 d9 \1 ? vector<Char>::iterator it = find( _vBuf.begin(), _vBuf.end(), w );4 A2 Q' e2 b' ^* Q% V
if ( it == _vBuf.end() ) // 如果没找到
' t/ V0 J3 M) N# y5 A6 c3 c7 R7 C {7 i! z( f5 i4 x' E2 t9 F
it = find( _vBuf.begin(), _vBuf.end(), 0 ); // 查找空闲位置
9 a% a U6 O2 }% W0 R if ( it == _vBuf.end() ) // 缓冲已满6 C. {# S" ^ _
{ U6 q1 Y$ n0 g \; {
for(; it!=_vBuf.begin(); it-- ) N; P8 t2 p: s- v7 p
{
@: v9 x3 N0 E' I it->hz = 0;
1 n4 @5 y3 g) D* s" j4 l/ J }
" y7 x$ `7 d4 E# n8 n, z- _// Log.Output( "字体缓冲已满, 清空!" );
; v9 V; T" {& U! B+ Q6 V" L }
# G% _( @" G# ~4 N# |8 v+ p7 t; |9 O: P) {8 K2 y- C
// 计算当前空闲的Char在缓冲中是第几个
' ?0 U% e: H. P' L1 \) g; \. P( s" S int at = it-_vBuf.begin();$ {* ?8 l7 n8 C$ ]! `
; U5 o/ A( e4 k0 }5 g/ D$ A: Y U // 得到空闲位置的坐标
8 R7 \8 J% g: P( v! Z) v tX = (at % _RowNum) * _TextSize;9 n# H5 @4 E! F6 W7 ]/ F
tY = (at / _RowNum) * _TextSize;: |- ~3 g4 E) ]0 ]9 J
8 t: f. {7 m- }4 w // 设置这个Char为使用中- c+ C0 v* f, P. l* H
(*it).hz = w;+ f! a0 Z& o3 {8 @" a6 ^ K
6 c4 V( B: N5 i; w; O2 D+ w& |1 d% E& O
RECT rect = {0, 0, _TextSize, _TextSize};
/ A% `1 X/ |; |. v! h: Z# L char sz[3] = {c1, c2, '\0'};; u0 @* c$ @9 V+ x
// 填充背景为黑色(透明色)
$ |. c% {* \" [( T1 A- i FillRect( _hDc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );! {( e4 v: r5 T( V s
// 往hBitmap上写字4 v4 N- O7 I- h V8 |) E: W d( J
::TextOut( _hDc, 0, 0, sz, c1 & 0x80 ? 2 : 1 );
. x# o. i( a1 i ; p1 T7 C l2 u% F( Z( b% d; l0 S/ F
// 锁定表面, 把汉字写入纹理, 白色的是字(可见), 黑色为背景(透明)
. l( q6 p3 ]% Y2 s0 U$ q" o D3DLOCKED_RECT d3dlr;
$ d) F+ y# y4 i _5 V9 P- h" c _pTexture->LockRect(0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
0 }$ H! i! Y- b0 H0 R BYTE * pDstRow = (BYTE*)( (WORD *)d3dlr.pBits + tY * _TextureSize + tX );0 t3 l k* o; o. c! M/ g
0 a5 ~( z: G& ^. ?9 w* P
for (DWORD y=0; y<_TextSize; y++). |- ]9 v- B7 D F. B+ K
{* t+ A: ^2 H3 U: j) e* P
WORD * pDst16 = (WORD*)pDstRow;
# M1 o# U" c( M for (DWORD x=0; x<_TextSize; x++)/ ^8 Y9 m+ F3 T. d2 t
{
% o8 @! N5 x/ D* U) B BYTE bAlpha = (BYTE)((_pBits[_TextSize * y + x] & 0xff) >> 4);
- Q" B; j* I# U& [8 ]( } if (bAlpha > 0)
, }/ y' A/ \$ W `) K8 ?, i *pDst16++ = (bAlpha << 12) | 0x0fff;
3 [; s/ `/ P6 J else
- ~% n& S" v5 f3 o2 P1 W# b# E *pDst16++ = 0x0000;# D% M" h# W& P0 ]: y
} j* K, h& q5 p" s
pDstRow += d3dlr.Pitch;( c* l& I" K w+ b% D+ q
}
3 x9 U0 D/ c4 v _pTexture->UnlockRect( NULL );
; E+ A |' v: P5 G. c* Z% f( F }% V; {, f! w5 E/ D) Q/ [5 q1 i
else0 E. r- y6 `$ |7 i- }
{% x: \( W1 F" r+ P6 E1 J
// 计算当前空闲的Char在缓冲中是第几个% S; ?# j1 C5 n! U. [
int at = it-_vBuf.begin();
' O4 w0 |2 w* a! J9 ]2 }/ D4 r7 w/ R8 c& c7 P; j4 k h) f
// 得到这个字的坐标4 t: z0 f, c: u. }/ n
tX = (at % _RowNum) * _TextSize;
- ]8 i- {0 v) l tY = (at / _RowNum) * _TextSize;
$ k: ?9 ?$ m2 M9 N }
# | l4 ?8 ]1 V7 `1 ]}
2 R1 J. {! W. {以上代码中的注释已经很清楚了,相信无须我多言。这里唯一需要声明的是:原来所定义的Char结构是这样的; x5 M! R8 R8 U/ {) Q6 O4 }
struct Char{7 J5 K# L5 G. W) y9 r
char hz[3]; // 保存汉字
, M8 U" t D% P int frequency;// 使用频率
5 U' f3 h; r- J7 J: n RECT rect; // 这个字对应位图的区域
+ T9 p8 b0 `- W! |- g Bool isUsing; // 是否使用
7 [5 a; g& a( M6 w( q: Z; a}
3 z2 p" G. Y7 M( f6 V7 m) d- y* u后来因为将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。) F2 a' d, v5 r) A7 n
然后对于RECT rect,因为没有了int frequency,意味着一旦将汉字写入到Texture,其位置就不会变动了。所以,很容易根据find函数操作后的iterator,直接计算出这个汉字所在Texture的位置。这样,RECT rect也不再必须。
; H) V$ ~! I4 F) a- k而bool isUsing,它本身就是个鸡肋,要也可以,这样结构更加清晰。不过,直接通过观察WORD hz为0或非0,即可实现isUsing的作用了。
+ O3 [8 A% d) K+ K为什么要对结构Char这么精雕细琢呢?* j7 S ^) f; S/ ~, a* |6 _ ^
1. 既然没有必要的东西,就应该删除) Q% n* l0 k+ b! |3 J
2. Char结构的大小越大,vector所要求的内存越大; j; \; z$ S& D3 j: q3 |! ]
3. 小的结构,find可以更快地查找出所结果
9 m* U. Y# C, @" g2 O' J为什么find会正常工作呢?这里我要大概地讲一下find是如何查找出所需的位置的:它只是简单地使用while从vector的begin一直遍历到end,逐个判断,直到找到为止。find要求必须实现自己的operator ==(),进一步跟踪到find的源码中,发现也是这样。于是前面的结构Char变成了现在这样:5 b! \" {/ A5 U5 S% |* u: f) z3 @
struct Char{
3 V* j& z0 d2 _. `8 U R% Q WORD hz; // 文字
0 O u' ~3 U9 e6 m3 K) g- p* p- q: G0 T! M! i. l! I8 T' \
Char() : hz(0) {}
+ z j/ V- S# ?& `) L; r2 r5 `& p0 [" {
// 用作查找文字7 w4 ~$ i7 `! G9 \, t8 R7 q
inline bool operator == ( WORD h ) const0 S1 Z. j8 e8 k- L$ \
{
/ n! X+ C# ^8 n return hz==h ? true : false;
" [% Z, p- v: \0 D3 f3 f; J+ j! p2 j }
9 X( M7 d* O0 c- X' p% _ };" k* `$ w9 `1 J/ y
是不是很简单?^___^4 @7 N* q* [$ |& O2 p# @
! f7 ]4 Y4 u# `0 \7 l5 \4 F
终于到了显示的函数了:! `9 M0 L1 j ~; O. x% \- t0 o6 E
// 得到文字在纹理中的位置5 G' \ I/ Z- m
bool CFont::
, A/ M& L- {2 R. L/ q; ] t/*-------------------------------------------------------------- ~5 l5 [% M' a8 x8 {
char szText[] --- 显示的字符串
% E, O/ i# U% K& p$ yint x --- 屏幕坐标x
0 w$ i9 P2 k- Y5 bint y --- 屏幕坐标y
; j- c$ F2 j8 X2 G$ N% RD3DCOLOR --- 颜色及alpha值
% g( B: c( `& h( E; b& j& K$ v* zint nLen --- 字符串长度. H0 a" c2 ~! j8 K
float fScale --- 放大比例
3 U; G1 i, B8 A5 ^# J; X( o-------------------------------------------------------------*/. E, W" h& n; B. y
TextOut( char szText[], int x, int y, D3DCOLOR color, int nLen, float fScale )1 [- c; w; j q5 q7 N) W
{
4 ?! H$ U4 ~5 `' ^1 R; D* u Assert( szText!=NULL );
$ b6 N0 y3 z5 Q( Z
' ~" H( y- |' ?2 Y9 n9 a% A) L0 L) A float sx = x, sy = y,
, E0 G) [7 y( s; Y% ` ` offset=0, w=0, h=0, tx1=0, ty1=0, tx2=0, ty2=0;
5 U0 U7 z& O v9 i8 W6 o w = h = (float)_TextSize * fScale; b5 s% | {! O+ _+ [. f: U
$ r T8 ?/ W* ]. J$ V9 r/ P char ch[3] = {0,0,0};- C; W, B" z* { g7 h
FONT2DVERTEX * pVertices = NULL;# d: C9 m9 l" n) L
UINT wNumTriangles = 0;; x1 b" { g$ h; t3 G8 p% o, z
_pVB->Lock(0, 0, (BYTE**)&pVertices, D3DLOCK_DISCARD);( k' ~8 u! M r2 O7 X3 z
# O4 b$ J% M! K5 X3 p7 p' \& g0 S if ( -1 == nLen || // 默认值-1
& r$ z+ L0 L1 v* V, O8 s, w8 ~% _ nLen > lstrlen( szText ) ) // 如果nLen大于字符串实际长度, 则nLen=实际长度, k) y' N" Q& X, ~* z" y! H
nLen = lstrlen( szText );
- }6 }% y5 s {% m4 R2 T9 e for (int n=0; n<nLen; n++ )8 i0 v1 a7 P L& C
{1 s8 l: {. \8 R! z9 }
ch[0] = szText[n];. [ ~+ o' f1 X* r8 P9 y
6 @; g! {6 i3 F) Z$ B; d9 k
if ( ch[0]=='\n' )
) E' d; m& W% E& r9 H {
8 e, e. [, P% @) N1 x sy+=h;5 N9 w* V* {5 E) [1 h. j
sx=x;
6 P) f; H! Q1 y G+ I, Z continue; L4 Q$ X2 f. F1 c. L* _
}
. y6 F3 k& t( K6 ^: k1 C- o2 G j+ H. I
if ( ch[0] & 0x80 )
" @/ D# R7 O' [ {
/ s" Y5 C3 R: Q+ {- Z" v n++;" I) E5 D# d& `% G$ G. S
ch[1] = szText[n];: p. z# V8 g6 M2 x
offset = w;
7 {- W- Z! F3 F: v0 S& L } M6 j! K8 d2 H% F0 R3 E4 t% v" B
else1 M. }- S+ i; c% D
{. b: b3 n- @3 ]! z! E/ P9 N& ]
ch[1] = '\0';! I% G/ d) \& z+ E
offset = w / 2 ; x9 Q6 n3 W9 N/ A$ `* g
}/ ?1 K. r. g! t; ]+ g- n7 p
% P$ V5 k/ v1 ^. G6 P- V int a, b;
) z+ o- v2 `9 ?+ Z( d5 ]* B0 w k+ D Char2Texture( ch[0], ch[1], a, b );
9 U6 T9 P- \- Z0 y; I ! h W, l6 K2 l2 q
// 计算纹理左上角 0.0-1.0
* i @1 C6 z X, @. `/ u1 V tx1 = (float)(a) / _TextureSize;
( y8 q# ^' O7 |0 @ ty1 = (float)(b) / _TextureSize;, G% [" x. N" s: o( K H
// 计算纹理右上角 0.0-1.00 P& C4 d5 _" w Y* O& q/ {2 V: v
tx2 = tx1 + (float)_TextSize / _TextureSize;
& P) W0 u$ T: _3 D5 I+ W ty2 = ty1 + (float)_TextSize / _TextureSize;
1 R# Z) Y; |( S2 A5 d. p; X; m; F$ h2 U" b0 W
// 填充顶点缓冲区
. j7 Q3 a/ L# Z; s+ r/ w% A7 _ *pVertices++ = FONT2DVERTEX(sx, sy + h, 0.9f, color, tx1, ty2);
1 I ]: L' h8 o( u2 Z; M! ]- u *pVertices++ = FONT2DVERTEX(sx, sy, 0.9f, color, tx1, ty1);+ F# l. z! J P- U' S1 |8 E9 r
*pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f, color, tx2, ty2);8 C! ]6 U' Q4 B7 Y: h N- s9 T
*pVertices++ = FONT2DVERTEX(sx + w, sy, 0.9f, color, tx2, ty1);
7 B1 r, G( s' S *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f, color, tx2, ty2);3 r1 t# r9 }8 j2 v3 i# ^2 H( A' y
*pVertices++ = FONT2DVERTEX(sx, sy, 0.9f, color, tx1, ty1);0 _" j' P6 N a) ~: ]2 ~
6 ]; {5 z- S: t
wNumTriangles+=2;
0 I! }9 p. Z# j( w) M* j* d0 @- B' t) g ]# I) N& ?: C
sx+=offset; // 坐标x增量
) G q! j. `, X! s9 I( D }
3 \6 @$ B) I% x _pVB->Unlock();
`7 g1 T6 H0 A5 ], A _pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, wNumTriangles );
! Y: |; [* @% P( k( z+ O% W9 B" q
return true;* K, j% R/ \% Q# y$ p! k/ o) F! I
}
' X2 e6 }6 g! t$ L, H4 G5 |结束语
T+ ~5 ?$ Q% k" V- ?) W( I记得有一句名言: Keep it simple and stupid.在实现功能的同时,保持代码简单、清晰是非常重要的一件事。相信在往后的日子里,在不论是别人阅读或是你自己回顾的时候,你都会发现一如既往地遵守这个守则,是多么得重要!- a" f, T: e- f5 F. l4 I
相信通过上面我那无数的废话,加上代码中还算足够的注释,聪明的你一定能够明白这其中的原理了吧。如果以上的内容还不足以让你完全搞清楚的话,你可以登录我的主页:
1 m/ I5 s4 z$ h* ^. U炎龙工作室9 Y8 t2 m. w# u I
上面不仅包括了上面所写的程序代码,还有一个用来演示效果的一个很简单的demo。
; S$ E3 G/ o8 O( {说明,以上所实现的CFont是包含在我的游戏引擎中的一个部件,而目前已经实现的部件包括有:
# l- L! a6 r. f* H( ?3 L1. CGameFrame(游戏框架类) ----- 封装了窗口及D3D设备的建立,需要派生出自己的子类
, |8 `' B) n1 p* j( \2. CAudio和CSound(声音类) ----- 支持wav/mid/mp3的播放
) Q# C: _+ I7 Q1 x1 B3. CDirectInput(控制类) ----- 键盘、鼠标操作
9 [, g: f; w; U. {/ X3 [5 S% X4. CDirectShow(视频类) ----- 支持avi/mpg/mov等的播放0 L: L9 K0 C8 h" z7 R
5. CSpriteX(精灵类) ----- 方便游戏中对精灵的控制
* z0 D9 w/ @, |$ J x8 [+ S6. CFont(字体类) ----- 中英文字体的显示* m4 b- r$ p* z
7. CTimer(时间类) ----- 高精度时间的控制2 s, ~; u3 [0 c: _6 {! c
8. FPS(fps 类) ----- fps的计算
, U% b" R+ _ H/ [4 ? G+ T9. LOG(日志类) ----- 游戏中的错误反应以及状态记录
) F7 V! N$ \2 W# Q( ~最重要的是,这个Game Engine完全是开放源代码的。关于更新的情况、版本说明以及源码下载,请随时关注我的主页!
: [8 g5 O! r+ [6 s8 L# h* `6 B5 A接下来,我将会继续完善这个Engine,可能加入的有:高效粒子系统、斜45度角地图…… |