新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → 深入WINDOW字型 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 2936 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 深入WINDOW字型 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 深入WINDOW字型

    文档简介:
      在探讨更深入的剧情处理以前,我们必须拥有输出文字的能力。这次我们的目标是撷取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%,剧情的表现上,大致上可以看成「说故事」一样,不断的把文字输入到脑海里面,就形成了剧情,当然也需要各种事件的参与。更有趣的是,架构单线剧情与多线剧情都是相当富挑战性的一件工作。我们是不是该开始规画了呢?规画前记得先洗把脸(可以不使用洗面皂),让思绪更加畅通。


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/8 9:21:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/28 15:47:18

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    78.125ms