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