以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- 深入WINDOW字型 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=53478) |
-- 作者:一分之千 -- 发布时间:10/8/2007 9:21:00 AM -- 深入WINDOW字型 文档简介: 在探讨更深入的剧情处理以前,我们必须拥有输出文字的能力。这次我们的目标是撷取WINDOW系统资源,并且与DirectX全萤幕游戏相结合,你将会学习到如何有效快速地将字型套用到你的游戏上面。 目录: 文档内容: 记得我曾经说过,在DOS下开发游戏,与WINDOW下的差别是很大的,曾经令人苦恼的问题,在WINDOW下都有更方便的解决方式,最明显的例子就是音乐播放,以及我们这次讨论的字型使用。在很多较老旧的游戏书籍里面,都会教你如何秀中文,比较常见的方法就是利用倚天中文的字型档。并且还需要利用文字索引的技巧,以节省记忆体空间。这一切都过去了,等等你会看到我们在WINDOW下面的实作方式。这边所谓「正确的方向」是指有效率的解决方式,当然早期的作法仍然可以适用于WINDOW环境。只是要额外付出许多代价就是了。 □ WINDOW的字型 在中文的WINDOW里面,一安装完毕就会有许多字型可以套用,其中的中文字型至少有明体与标楷体,这些字型属于系统资源的一部份,任何应用程式皆可大方地使用。该如何使用呢?写过WIN32 APP的人大抵上都知道WINDOW API中,有一个字型对话方块,可以很方便的取的某个字型的资料,不过我不打算使用这个对话方块函示,实际上自己动手更有弹性,也不会浪费多少时间,以下开始实作。 □ 取得字型资讯 一个应用程式如果没有指定字型,则使用系统内定的字型。如果要使用标楷体字型的话,必须先取得此种字型的资讯,当然还可以指定字型的大小等等。打开字型对话方块,你会发觉字型的种类很多,但是我们需要的只有中文字型而已,所以我们忽略其他资讯,专注于我们需要的部份。这里我要强调的是,如果只强调秀出中文,你大可不必管他字型怎么来的(毕竟,我们的WINDOW环境本身就是中文的),使用内定字型即可,如果你还要强调系统字型的美观,就需要选择一种比较容易搭配的字型,更进一步如果要让使用者在游戏内自由选择系统任一种字型,则需要列举出所有可用的字型。并且将这些资讯收集起来,提供程式使用。 好的,我们先介绍如何列举出所有的系统字型,首先介绍这个函示: int EnumFontFamilies( 这个函示需要的参数共有四个,第一个参数是绘图使用的设备代码,在一般应用程式,我们会使用GetDC()来取得他的设备代码,而在DirectX里面,我们必须使用IDirectDrawSurface3::GetDC(),由这个函示取得的DC才能保证与GDI的函示相容。第二个参数设定为NULL则会取得所有的字型,包括固定宽度字型与向量字型。第三个参数是一个CALLBACK函示指标,他的原型固定,系统会自动呼叫这个函示,而实作此函示的我们,正好可以将系统字型撷取下来,第四个参数用不到,设为NULL即可。 至于FONTENUMPROC的原型是这样子的: int CALLBACK EnumFontFamProc( 这个函示的四个参数由系统传给我们,里面包含我们所需要的一切资讯,现在我们就看看实际上该如何使用,底下撷取自CFONT类别的实作内容: //此成员函示呼叫以后,会开始取得系统字型 void CFont::QueryFont() CFont::QueryFont()仅设定好初始的资料,真正接收字型资料的部份在CALLBACK函示,而CALLBACK函示我们应该怎么实作呢?底下便是: 首先我们配置三个结构以存放「细明体」「新细明体」与「标楷体」的字型资料, LOGFONT logfont[3]; 这个结构可以存放字型的细部资料,其内容相当繁杂,且不是所有的资料都派上用场,更详细的资料可以在VC线上说明取得。需要的话,你可以配置更多的空间以存放各式各样的字型资料,这边我只示范三种常用字型。接著我们看一下该怎么在CALLBACK函示里面接收这些资料: BOOL CALLBACK CFont::EnumFamCallBack(LPLOGFONT lplf,LPNEWTEXTMETRIC lpntm ,DWORD FontType,LPARAM aFontCount) return TRUE; 刚刚有说到这个CALLBACK函示的四个参数是系统传给我们的,其中的LPLOGFONT包含了一种字型的资料,我们藉由判断其名称来决定这个字型是不是我们需要的,如果是的话,将他拷贝到我们预先配置好的LOGFONT结构里面。这个函示事实上会持续呼叫,直到找完所有的字型为止,所以在这个过程中,我们只接收三种字型的资料,其他的都忽略不处理。当这个函示完成以后,我们配置的LOGFONT[3]这个阵列里面,已经包含我们所需要的资料了。大事已经完成一半了,接著我们应该做什么事情呢? □ 将字型设定给DC 取得字型资料以后,实际上什么事情也没发生,我们必须根据字型的资料,来产生一个字型代码给API函示使用,这个处理过程我将他包在成员函示CFont::SetFont(HDC hdc,int FontType,int width,int height)里面,实作内容如下: void CFont::SetFont(HDC hdc,int FontType,int width,int height) case NEWMINGLIU: case KAIU: SelectObject(hdc,hFont); 这个成员函示接收四个参数,第一个参数是欲设定字型的目的DC,第二个参数决定要设定何种字型,第三第四个参数决定字型的大小。多方便阿,连字型大小都可以自由设定,不过还是要适中才会好看。所以实际呼叫的时候,我们是这样做的: SetFont(hdc,NEWMINGLIU,10,15); //将前景DC与字型相结合 为了美观起见,我把三种字型另外定义其名称: #define KAIU 5 //标楷体 所以上面的SetFont()我们是选择了新细明体,并且决定其字型宽度10,高度15,当然,宽度高度是可以任意变化的,决定好宽度高度以后,接著使用CreateFontIndirect ();其传回值为字型代码,最后利用SelectObject(hdc,hFont);把他真正设定给DC就可以了。 感觉上这个过程绕来绕去的,都没有一枪毙命的感觉,唔~~~我也这样认为,所以我还是把到目前为止的过程整理一下吧: 1. 使用EnumFontFamilies()列举字型 □ 规画秀字的方式 好的,我用最简单的方式来秀字看看,要用什么函示呢?TextOut()是也,简单又大方,亲切又可爱,而且在任何地方,秀字总免不了使用这个函示,我们来看看他的样子: BOOL TextOut( 可以指定座标与字串,果然是为我们精心设计的API,不用怎么对得起别人呢?示范一下我要在座标(20,25)的地方秀出一段文字,我这么做: TextOut(hdc,20,25,"相当稳用",8); 果然没问题,不过呢,我们需要再包装一层,让这个函示更人性化一点,所以我又实作了一个成员函示void CFont::ShowFont(char* string),这个成员函示接收一个指向任意长度的字串指标,并且按照我们预先设计好的格式秀出来,这个格式需要讨论一下,我自己决定的方式是这样子的: 1. 在萤幕座标 (46,120)的地方开始秀字 一旦决定好以后,根据这些规则我们实作出来的内容是这样子的: void CFont::ShowFont(char* string) while(string[count]!=0) count++;//先取得这个字串的长度 if(count%2) //不足2 bytes则补足 count/=2; //COUNT除以二变成中文字个数 Line=count/14; // 每列14个中文字,所以我们计算共需要几列 Cycle=Line/4; //每个画面最多4列,所以我们计算需要几个画面 Cycle++; //至少一个画面 //可见页拷贝到隐藏页,文字秀出以后,恢复萤幕用 lpBackBuffer->Blt(NULL,lpFrontBuffer,NULL,DDBLT_WAIT,NULL); SetFont(hdc,NEWMINGLIU,10,15);//将前景DC与字型相结合 for(k=0;k<Cycle;k++)//四行字为一个回圈 for(j=0;j<14;j++)//一行字里面有14个中文字 //第二次左移一个像点秀出另一色,制造框线效果 if(ShowedFont==count)goto Finish;//秀完了吗?离开回圈 m++;//指向下一列给TextOut()使用 Finish: lpFrontBuffer->ReleaseDC(hdc); ReleaseFont(); Sleep(200); //延迟 while(GetAsyncKeyState(VK_SPACE)>=0);//按空白键继续 lpFrontBuffer->Blt(NULL,lpBackBuffer,NULL,DDBLT_WAIT,NULL); }//for k end //还原萤幕画面 } // function end 虽然加上注解了,我还是稍微说明一下,首先函示会收到一个不定长度的字串,第一个步骤我们先计算他的长度,此处的长度单位是byte,除以二以后就变成中文字的个数了,接著利用国小数学,我们计算出总共要几列,几个画面才得以把这个字串秀完。所以回圈里面就是在做这一件事情,最后跳出回圈的方式,我们判断已经秀出的字是否跟传进来的长度一样,如果一样,代表我们已经秀字完毕了,goto跳出来就好了,轻松。 另外保存萤幕画面是有必要的,不过这边我的作法相当直接。在秀字的时候,我们是直接秀到前景的绘图页(surface),所以TextOut()一呼叫完毕,萤幕上马上会出现这些字串。所以你知道了,在我们秀字的当时,背景的绘图页是闲置的,于是我们在秀字串以前,把萤幕画面Blt()拷贝到背景绘图页,等到秀字完毕以后,要恢复画面,则反向操作,从背景绘图页拷贝到前景绘图页即可。我的初步结论是,一切较静态的画面,你直接画在前景surface即可,不必担心会有闪烁的现象。 在秀字的过程中,每一个字我们都秀两次,这是为什么呢?达成文字边框的效果是也。同一套字型,我们利用不同颜色画上去,效果不错,其原理是这样子的,我随便举例说明: 第一次我在座标(46,120)的地方用黑色秀出一个「中」字,第二次我在座标(45,120)的地方用红色秀出一个「中」字,你可以看到这两个字的偏移只有一个x座标单位,所以红色的「中」字会覆盖掉黑色的「中」字,但是最右边的黑色部份则不会覆盖到(因为我们偏移一个x单位嘛),这简单的过程,我们好像获得了一套新的,更漂亮的字型一样,这技巧够酷。 各位也可以看到函示里面Sleep()的部份,主要是控制秀出字串的速度,你可以在字与字之间控制间隔的速度,如果没有稍微延迟的话,则一整面的字瞬间秀完。当然最好的方法是把Sleep()函示的参数(延迟时间)当成变数,让使用者决定他想要的速度。 最后我要说的是,在你使用IDirectDrawSurface3::GetDC()获得DC以后,记得要释放他,不这么做的话,其他绘图的动作都会失败,这跟我们平常使用GDI的GetDC()是有差别的。 □ 整合一下 到目前为止,初步完成99%的任务。我们学到了如何利用系统的字型资源,并应用在我们的程式上面,整个字型类别是这样子的: class CFont private: 我的目的是解释其过程,并非要写一个现成的东西给你用,所以还有很多事情需要靠你自己去完成。比方说,一般人系统里面的中文字型,并不仅止于明体与标准楷体,或许还有华康等其他的字型,我们或许应该列举出所有的中文字型,让程式更有弹性。在字型大小方面,是可以调整的,所以你也不必要担心在320x200的画面字型是否会过大,在800x600的画面下,字型是否太小。一切的操控权都在你手上。在秀字的同时,加上一个美丽的背景图框也是不错的选择。 □ 末语 看到这里,你大概已经知道WINDOW下的处理方式跟DOS下有相当相当的差别吧。如果你掌握到正确的处理方式,并节省下额外的时间,那么,看这篇文章就值回票价了。 以处理字型为目标,我们已经达成阶段性任务,以处理剧情为目标,我们只完成了开始的5%,剧情的表现上,大致上可以看成「说故事」一样,不断的把文字输入到脑海里面,就形成了剧情,当然也需要各种事件的参与。更有趣的是,架构单线剧情与多线剧情都是相当富挑战性的一件工作。我们是不是该开始规画了呢?规画前记得先洗把脸(可以不使用洗面皂),让思绪更加畅通。
|
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
62.500ms |