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