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