网页功能: 加入收藏 设为首页 网站搜索  
深入WINDOW字型
发表日期:2007-01-16作者:[转贴] 出处:  


文档简介:
  在探讨更深入的剧情处理以前,我们必须拥有输出文字的能力。这次我们的目标是撷取
WINDOW系统资源,并且与DirectX全萤幕游戏相结合,你将会学习到如何有效快速地将字型套用到你的游戏上面。

目录:
  掌握正确方向
  WINDOW的字型
  取得字型资讯
  将字型设定给DC
  规画秀字的方式
  整合一下
  末语

文档内容:

掌握正确方向

  记得我曾经说过,在DOS下开发游戏,与WINDOW下的差别是很大的,曾经令人苦恼的问题,在WINDOW下都有更方便的解决方式,最明显的例子就是音乐播放,以及我们这次讨论的字型使用。在很多较老旧的游戏书籍里面,都会教你如何秀中文,比较常见的方法就是利用倚天中文的字型档。并且还需要利用文字索引的技巧,以节省记忆体空间。这一切都过去了,等等你会看到我们在WINDOW下面的实作方式。这边所谓「正确的方向」是指有效率的解决方式,当然早期的作法仍然可以适用于WINDOW环境。只是要额外付出许多代价就是了。

WINDOW的字型

  在中文的WINDOW里面,一安装完毕就会有许多字型可以套用,其中的中文字型至少有明体与标楷体,这些字型属于系统资源的一部份,任何应用程式皆可大方地使用。该如何使用呢?写过WIN32 APP的人大抵上都知道WINDOW API中,有一个字型对话方块,可以很方便的取的某个字型的资料,不过我不打算使用这个对话方块函示,实际上自己动手更有弹性,也不会浪费多少时间,以下开始实作。

取得字型资讯

  一个应用程式如果没有指定字型,则使用系统内定的字型。如果要使用标楷体字型的话,必须先取得此种字型的资讯,当然还可以指定字型的大小等等。打开字型对话方块,你会发觉字型的种类很多,但是我们需要的只有中文字型而已,所以我们忽略其他资讯,专注于我们需要的部份。这里我要强调的是,如果只强调秀出中文,你大可不必管他字型怎么来的(毕竟,我们的WINDOW环境本身就是中文的),使用内定字型即可,如果你还要强调系统字型的美观,就需要选择一种比较容易搭配的字型,更进一步如果要让使用者在游戏内自由选择系统任一种字型,则需要列举出所有可用的字型。并且将这些资讯收集起来,提供程式使用。

好的,我们先介绍如何列举出所有的系统字型,首先介绍这个函示:

int EnumFontFamilies(
    HDC
hdc,                        // handle to device control
    LPCTSTR lpszFamily,
             // pointer to family-name string
    FONTENUMPROC lpEnumFontFamProc,
// pointer to callback function
    LPARAM lParam                   // address of application-supplied data
); //
取自VC线上说明

  这个函示需要的参数共有四个,第一个参数是绘图使用的设备代码,在一般应用程式,我们会使用GetDC()来取得他的设备代码,而在DirectX里面,我们必须使用IDirectDrawSurface3::GetDC(),由这个函示取得的DC才能保证与GDI的函示相容。第二个参数设定为NULL则会取得所有的字型,包括固定宽度字型与向量字型。第三个参数是一个CALLBACK函示指标,他的原型固定,系统会自动呼叫这个函示,而实作此函示的我们,正好可以将系统字型撷取下来,第四个参数用不到,设为NULL即可。

至于FONTENUMPROC的原型是这样子的:

int CALLBACK EnumFontFamProc(
    ENUMLOGFONT FAR* lpelf,
         // pointer to logical-font data
    NEWTEXTMETRIC FAR* lpntm,     
 // pointer to physical-font data
    int FontType,
                  // type of font
    LPARAM lParam                   // address of application-defined data
); // 取字VC 线上说明

这个函示的四个参数由系统传给我们,里面包含我们所需要的一切资讯,现在我们就看看实际上该如何使用,底下撷取自CFONT类别的实作内容:

//此成员函示呼叫以后,会开始取得系统字型

void CFont::QueryFont()
{
   
lpFrontBuffer->GetDC(&hdc);     //取得前景DC
    EnumFontFamilies(hdc, (LPCTSTR)NULL,(FONTENUMPROC) EnumFamCallBack,(LPARAM)NULL);
    lpFrontBuffer->ReleaseDC(hdc);  //释放DC
}

CFont::QueryFont()仅设定好初始的资料,真正接收字型资料的部份在CALLBACK函示,而CALLBACK函示我们应该怎么实作呢?底下便是:

首先我们配置三个结构以存放「细明体」「新细明体」与「标楷体」的字型资料, LOGFONT logfont[3];

这个结构可以存放字型的细部资料,其内容相当繁杂,且不是所有的资料都派上用场,更详细的资料可以在VC线上说明取得。需要的话,你可以配置更多的空间以存放各式各样的字型资料,这边我只示范三种常用字型。接著我们看一下该怎么在CALLBACK函示里面接收这些资料:

BOOL CALLBACK CFont::EnumFamCallBack(LPLOGFONT lplf,LPNEWTEXTMETRIC lpntm ,DWORD FontType,LPARAM aFontCount)
{
   
if(strcmp(lplf->lfFaceName,"细明体")==0)         //仅找明体与标楷体
        memcpy(&logfont[0],lplf,sizeof(LOGFONT));    //资料存放到logfont[]阵列
    if(strcmp(lplf->lfFaceName,"新细明体")==0)
        memcpy(&logfont[1],lplf,sizeof(LOGFONT));
    if(strcmp(lplf->lfFaceName,"标楷体")==0)
        memcpy(&logfont[2],lplf,sizeof(LOGFONT));

    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)
{
    switch(FontType)
    {
        case MINGLIU:
            logfont[0].lfHeight=height;
            logfont[0].lfWidth=width;
            hFont = CreateFontIndirect (&logfont[0]);
            break;

        case NEWMINGLIU:
            logfont[1].lfHeight=height;
            logfont[1].lfWidth=width;
            hFont = CreateFontIndirect (&logfont[1]);
            break;

        case KAIU:
            logfont[2].lfHeight=height;
            logfont[2].lfWidth=width;
            hFont = CreateFontIndirect (&logfont[2]);
            break;
    }

    SelectObject(hdc,hFont);
}

这个成员函示接收四个参数,第一个参数是欲设定字型的目的DC,第二个参数决定要设定何种字型,第三第四个参数决定字型的大小。多方便阿,连字型大小都可以自由设定,不过还是要适中才会好看。所以实际呼叫的时候,我们是这样做的:

SetFont(hdc,NEWMINGLIU,10,15); //将前景DC与字型相结合

为了美观起见,我把三种字型另外定义其名称:

#define KAIU 5                 //标楷体
#define MINGLIU 6              //
细明体
#define NEWMINGLIU 7           //
新细明体

所以上面的SetFont()我们是选择了新细明体,并且决定其字型宽度10,高度15,当然,宽度高度是可以任意变化的,决定好宽度高度以后,接著使用CreateFontIndirect ();其传回值为字型代码,最后利用SelectObject(hdc,hFont);把他真正设定给DC就可以了。

感觉上这个过程绕来绕去的,都没有一枪毙命的感觉,唔~~~我也这样认为,所以我还是把到目前为止的过程整理一下吧:

1. 使用EnumFontFamilies()列举字型
2. 在CALLBACK函示里面,接收字型资讯
3. 使用字型的时候,将字型与DC结合
4. 目前为止,大事完成2/3

规画秀字的方式

好的,我用最简单的方式来秀字看看,要用什么函示呢?TextOut()是也,简单又大方,亲切又可爱,而且在任何地方,秀字总免不了使用这个函示,我们来看看他的样子:

BOOL TextOut(
    HDC hdc,          // handle of device context
    int nXStart,      // x-coordinate of starting position
    int nYStart,      // y-coordinate of starting position
    LPCTSTR lpString, // address of string
    int cbString      // number of characters in string
); //
取自VC++4.0线上说明

可以指定座标与字串,果然是为我们精心设计的API,不用怎么对得起别人呢?示范一下我要在座标(20,25)的地方秀出一段文字,我这么做:

TextOut(hdc,20,25,"相当稳用",8);

果然没问题,不过呢,我们需要再包装一层,让这个函示更人性化一点,所以我又实作了一个成员函示void CFont::ShowFont(char* string),这个成员函示接收一个指向任意长度的字串指标,并且按照我们预先设计好的格式秀出来,这个格式需要讨论一下,我自己决定的方式是这样子的:  

1. 在萤幕座标 (46,120)的地方开始秀字
2. 一列以十四个中文字为最长的长度,超过换列。
3. 萤幕最多同时容纳四列,超过的话清除字串,从第一列输出。
4. .........(依照喜好,自己定格式)

一旦决定好以后,根据这些规则我们实作出来的内容是这样子的:

void CFont::ShowFont(char* string)
{
    int Line,Cycle;
    int i,j,k,m=0;
    int count=0,ShowedFont=0;//
累计秀出的字

    while(string[count]!=0)

    count++;//先取得这个字串的长度

    if(count%2) //不足2 bytes则补足
       count++;

    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++)//四行字为一个回圈
    {
        lpFrontBuffer->GetDC(&hdc);//取得前景DC
        SetBkMode(hdc,TRANSPARENT);//
设定秀字背景为透明色
        for(i=0;i<4;i++)//一行字为一个回圈,共四行
        {

            for(j=0;j<14;j++)//一行字里面有14个中文字
            {
                //第一次秀黑色
                SetTextColor(hdc,RGB(0,0,0));
                TextOut(hdc,46+j*17,120+i*15,&string[m*28+j*2],2);

                //第二次左移一个像点秀出另一色,制造框线效果
                SetTextColor(hdc,RGB(255,0,0));
                TextOut(hdc,45+j*17,120+i*15,&string[m*28+j*2],2);

                Sleep(20);//
稍微延迟,控制速度
                ShowedFont++;

                if(ShowedFont==count)goto Finish;//秀完了吗?离开回圈
            }//for j end

            m++;//指向下一列给TextOut()使用
        }// for i end

    Finish:

    lpFrontBuffer->ReleaseDC(hdc);

    ReleaseFont();

    Sleep(200); //延迟

    while(GetAsyncKeyState(VK_SPACE)>=0);//按空白键继续

    lpFrontBuffer->Blt(NULL,lpBackBuffer,NULL,DDBLT_WAIT,NULL);         

    }//for k end

    //还原萤幕画面
    lpFrontBuffer->Blt(NULL,lpBackBuffer,NULL,DDBLT_WAIT,NULL);

    Sleep(300);
    return;

} // function end

虽然加上注解了,我还是稍微说明一下,首先函示会收到一个不定长度的字串,第一个步骤我们先计算他的长度,此处的长度单位是byte,除以二以后就变成中文字的个数了,接著利用国小数学,我们计算出总共要几列,几个画面才得以把这个字串秀完。所以回圈里面就是在做这一件事情,最后跳出回圈的方式,我们判断已经秀出的字是否跟传进来的长度一样,如果一样,代表我们已经秀字完毕了,goto跳出来就好了,轻松。

另外保存萤幕画面是有必要的,不过这边我的作法相当直接。在秀字的时候,我们是直接秀到前景的绘图页(surface),所以TextOut()一呼叫完毕,萤幕上马上会出现这些字串。所以你知道了,在我们秀字的当时,背景的绘图页是闲置的,于是我们在秀字串以前,把萤幕画面Blt()拷贝到背景绘图页,等到秀字完毕以后,要恢复画面,则反向操作,从背景绘图页拷贝到前景绘图页即可。我的初步结论是,一切较静态的画面,你直接画在前景surface即可,不必担心会有闪烁的现象。

在秀字的过程中,每一个字我们都秀两次,这是为什么呢?达成文字边框的效果是也。同一套字型,我们利用不同颜色画上去,效果不错,其原理是这样子的,我随便举例说明:

第一次我在座标(46,120)的地方用黑色秀出一个「中」字,第二次我在座标(45,120)的地方用红色秀出一个「中」字,你可以看到这两个字的偏移只有一个x座标单位,所以红色的「中」字会覆盖掉黑色的「中」字,但是最右边的黑色部份则不会覆盖到(因为我们偏移一个x单位嘛),这简单的过程,我们好像获得了一套新的,更漂亮的字型一样,这技巧够酷。

各位也可以看到函示里面Sleep()的部份,主要是控制秀出字串的速度,你可以在字与字之间控制间隔的速度,如果没有稍微延迟的话,则一整面的字瞬间秀完。当然最好的方法是把Sleep()函示的参数(延迟时间)当成变数,让使用者决定他想要的速度。

最后我要说的是,在你使用IDirectDrawSurface3::GetDC()获得DC以后,记得要释放他,不这么做的话,其他绘图的动作都会失败,这跟我们平常使用GDI的GetDC()是有差别的。

整合一下

到目前为止,初步完成99%的任务。我们学到了如何利用系统的字型资源,并应用在我们的程式上面,整个字型类别是这样子的:

class CFont
{
public:
    void QueryFont(); //找中文字型存入 LOGFONT[]
    void SetFont(HDC,int,int,int);//设定字型以及大小
    void ReleaseFont();//释放字型资源
    void ShowFont(char *);//秀出格式化字串

private:
    static BOOL CALLBACK EnumFamCallBack(LPLOGFONT,
    LPNEWTEXTMETRIC,DWORD,LPARAM);
    HFONT hFont;//字型代码
};

我的目的是解释其过程,并非要写一个现成的东西给你用,所以还有很多事情需要靠你自己去完成。比方说,一般人系统里面的中文字型,并不仅止于明体与标准楷体,或许还有华康等其他的字型,我们或许应该列举出所有的中文字型,让程式更有弹性。在字型大小方面,是可以调整的,所以你也不必要担心在320x200的画面字型是否会过大,在800x600的画面下,字型是否太小。一切的操控权都在你手上。在秀字的同时,加上一个美丽的背景图框也是不错的选择。

末语

看到这里,你大概已经知道WINDOW下的处理方式跟DOS下有相当相当的差别吧。如果你掌握到正确的处理方式,并节省下额外的时间,那么,看这篇文章就值回票价了。

以处理字型为目标,我们已经达成阶段性任务,以处理剧情为目标,我们只完成了开始的5%,剧情的表现上,大致上可以看成「说故事」一样,不断的把文字输入到脑海里面,就形成了剧情,当然也需要各种事件的参与。更有趣的是,架构单线剧情与多线剧情都是相当富挑战性的一件工作。我们是不是该开始规画了呢?规画前记得先洗把脸(可以不使用洗面皂),让思绪更加畅通。

 

我来说两句】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 深入WINDOW字型
本类热点文章
  如何用D3D/OpenGL在制作2D游戏中高效地..
  自己绘制True type font字体
  使用CPU时间戳进行高精度计时
  阵汉字显示
  程序员的超强外挂——Visual Assist .N..
  用VC++实现console程序显示彩色文本 wx..
  制做自己的点阵字库的方法和程序
  “变速齿轮”再研究
  一个简单的线程管理机制
  VC编程中常用快捷键
  Delpih中的Windows API编程初步
  BIG5到GB的转换技术
最新分类信息我要发布 
最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放  
Copyright ©2003-2024 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00358