正文

游戏编程起源(初学者)Ⅳ2006-01-26 12:35:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/mroske/9889.html

分享到:

字符串表

字符串表是我最喜欢的资源类型。正象你所想的:一个充满字符串的庞大表格。字符串表有很多用处。你可以用它存储你的文件名称,游戏中的人物对话,消息框中的文本,菜单中的文本等等。在资源脚本里建立一个字符串表很容易,就像这样:

STRINGTABLE
{
    // entries go here
}

一个字符串表由几部分组成:一个标识字符串的数字;紧跟着一个逗号;然后是加了双引号的字符串本身。字符串表里的字符串被允许使用溢出符号,如
。注意,字符串表本身并没有标识符,所以每个程序只能有一个字符串表。一个简单的字符串表可能象下面这个样子:

// program information
STRINGTABLE
{
    1, "3D Space Game v1.0"
    2, "Written by The Masked Coder"
    3, "(C) 2000 WienerDog Software"
}

从程序的字符串表里调用字符串,将使用——你可能猜到了——LoadString()函数。这是它的原形:

int LoadString(
    HINSTANCE hInstance, // handle to module containing string resource
    UINT uID,            // resource identifier
    LPTSTR lpBuffer,     // pointer to buffer for resource
    int nBufferMax       // size of buffer
);


函数返回的实数是字符的数量,不包括空字符,它将被赋值到程序数据段的缓冲区中去,相当于字符串的长度。如果你调用了一个空字符串或者调用失败,都将返回0。下面来看看具体参数:
HINSTANCE hInstance:同以前的一样,你所有操纵项目的句柄。
UINT uID:你想要调用的字符串的数码标识符。
LPTSTR lpBuffer:指向接收字符串的字符数组的指针。
int nBufferMax:缓冲区的字节长度。如果被调用的字符串的长度大于缓冲区的长度,字符串将被按照缓冲区的大小缩减。
例如,调用“WienerDog Software’s copyright”的信息,代码应该如下:

char buffer[80];
LoadString(hinstance, 3, buffer, sizeof(buffer));


尽管在资源脚本中字符串使用数字声明,而不是标识符,但我通常在使用字符串表时,习惯于在头文件中用#define定义一下字符串的声明数字。针对上面的代码,我可能加一行:

#define ST_WIENERDOGCOPYRIGHT 3

这样一来,用LoadString()函数时,你的程序代码更容易读懂,也使你的思路更加清晰。但也并不是意味着你必须为字符串表里的每一个字符串都定义一个常量标识符。当字符串表相当大时,或者你感觉记不清时,就应该定义常量标识符。我通常在每个常量标识符的前面加上一个前缀ST_。具体的说,ST_FILENAMES作为存储文件名称字符串的索引,ST_DIALOGUE作为人物对话字符串的索引,等等。

菜单
这是我们要讲的最后一个Windows资源,当然,不是为了凑数才讲的哦。窗口的菜单条紧接在标题栏的下发显示,这个菜单有时被称为主菜单顶层菜单。菜单通常在建立窗口类时被调用。还记得吗?上一章中窗口类建立过程中,有这样一行:
sampleClass.lpszMenuName = NULL;
如果你正在建立一个窗口程序,并希望有菜单,你就得需要用到菜单资源。它的脚本文件可能要复杂一点儿,但下面是一个最基本的框架:

[identifier] MENU
{
    POPUP [menu name]
    {
        MENUITEM [item name], [identifier]
    }
}

[identifier]
标识符是你知道的:一个字符串或一个数字常量。在MENU的大括号中,可以有一个或者几个POPUP(弹出式)菜单,每一个都有一个下拉菜单,[menu name]中填入菜单名称。在POPUP的大括号中,可以有一个或者多个菜单条,[item name]中填入菜单条名称,后面必须跟着一个数字常量的标识符。(所谓数字常量的标识符,就是用#define定义过的标识符。如:#define MENUID_NEW 101)如果你还想在菜单里建立热键,就要用(&)符号。在你想成为热键的字符前加上&,例如,你想用Alt+F代替用鼠标点击File按钮,你就应该写成 &File ,菜单的名称都要用双引号引上。看看下面的例子就更清楚了:

MAIN_MENU MENU
{
    POPUP "&File"
    {
        MENUITEM "&New", MENUID_NEW
        MENUITEM "&Open...", MENUID_OPEN
        MENUITEM "&Save", MENUID_SAVE
        MENUITEM "Save &As...", MENUID_SAVEAS
        MENUITEM "E&xit", MENUID_EXIT
    }

    POPUP "&Help"
    {
        MENUITEM "&Contents", MENUID_CONTENTS
        MENUITEM "&Index...", MENUID_INDEX
        MENUITEM "&About", MENUID_ABOUT
    }
}


你还可以在POPUP下建立子菜单,你自己琢磨吧,我就不讲了,我们还得往下进行。获得菜单资源的句柄,我们需要用LoadMenu()函数,它的原形如下:

HMENU LoadMenu(
    HINSTANCE hInstance, // handle to application instance
    LPCTSTR lpMenuName   // menu name string or menu-resource identifier
);

现在你应该已经熟悉这些参数了。第一个参数是你的程序实例的句柄,第二个是你的菜单资源的标识符。如果你使用了数字常量作为标识符,别忘了使用MAKEINTRESOURCE()这个宏转换一下哦!现在,你有两个方法为窗口创建菜单。第一个方法是在创建窗口类时直接设置:

sampleClass.lpszMenuName = LoadMenu(hinstance, MAKEINTRESOURCE(MAIN_MENU));

第二个方法是在设置窗口类时,让lpszMenuName等于NULL,以后再加入菜单。当你要建立两个独立的菜单,而又不想定义不同的窗口类时,这个选择是很有意义的,你需要用SetMenu()函数:

BOOL SetMenu(
    HWND hWnd,   // handle to window
    HMENU hMenu, // handle to menu
);


如果创建菜单的功能实现了,将返回TRUE,否则返回FALSE。它的参数是很容易理解的:
HWND hWnd:是你所要创建菜单的那个窗口的句柄。也就是你在调用CreateWindowEx()时产生的那个窗口句柄。
HMENU hMenu:识别菜单,使用它的形式是:hMenu=LoadMenu(hInstance,菜单标识符),所以它得到的是LoadMenu()函数返回的菜单句柄。如果给它赋值NULL,指定窗口的菜单将被移除。
资源是个好东西,因为它使我们很容易就生成了菜单。但是当我们点击了菜单上的选项,将会发生什么呢?答案是Windows将会发出一个WM_COMMAND的消息给程序,程序再让Windows作出相应的反应。让我们具体看一看吧!

控制菜单事件
你可能记得,Windows的消息都是通过CALLBACK函数控制的,通常它是这个样子:WindowProc()或类似的样子。我们在上一章中用到的是这个样子:MsgHandler()。它的原形如下:

LRESULT CALLBACK MsgHandler(
    HWND hwnd,     // window handle
    UINT msg,      // the message identifier
    WPARAM wparam, // message parameters
    LPARAM lparam, // more message parameters
};


当一个菜单消息被送到,msg将等于WM_COMMAND,所选择的菜单项目将被包含进wparam。这就是为什么菜单的标识符不能是字符串的原因,它需要适合wparam参数。更特别的是,菜单标识符只占用wparam的低位字。WPARAMLPARAMint等都是32位,分高、低位字的变量。Windows提供了宏LOWORD()和HIWORD()分别来提取变量中的低位字和高位字,原形如下:

#define LOWORD(l) ((WORD) (l))
#define HIWORD(l) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))

LOWORD()
宏的实际情况是,由于简单的定义为WORD,就自然的取得了低端的16位。HIWORD()函数把高端的16位向右移,然后同0xFFFF之间调用了逻辑AND),确保把高于16位的字节变为0。可能你不太熟悉>><<操作符号,它们是位移操作符。“<<”操作符把变量中的每一个字节中的数字向左移动,“>>”就是向右移动。例如,我们有一个16位的变量x,它的值是224,二进制表示为0000 0000 1111 0100。下面是一个关于位移的例子:

short int x = 244, y;
y = x << 4;

Contents(内容) of x: 0000 0000 1111 0100
Contents
(内容)of y: 0000 1111 0100 0000


总之,使用LOWORD()宏你得到了wparam的低端字,也就是说你得到了被选择菜单的ID(标识符)。所以,在你的MsgHandler()函数中,你应该这样做:

// handle menu selections
if (msg == WM_COMMAND)
{
    switch (LOWORD(wparam))
    {
    case MENUID_NEW:
        // code to handle File->New goes here
        break;
    case MENUID_OPEN:
        // code to handle File->Open goes here
        break;

        // the rest of the option handlers go here

    }

    // tell Windows you took care of it
    return(0);
}

当然,还有一些其它的资源类型,如加速表(快捷键)、HTML页、WAV文件等。但我想以上这些是最有用,最要紧学习的。在结束之前,我还要告诉你Windows编程的一大强力特色——定制自己的资源类型。

定制资源
标准的程序资源给我们带来了很大方便。但不仅仅是这些标准的类型,你还可以创建自己的资源类型。资源可以是你希望的任何一种数据。使用自己定制的资源需要多付出一点劳动,因为你必须手工定位和读取资源数据。比想象的要容易,因为你已经习惯了定义资源的格式:

[identifier] [resource type name] [filename]

[resource type name]
资源类型名称是让你命名的一个字符串。还是举例说明吧:假设我们要用到plconfig.dat文件作为资源,它包含初始化游戏人物的必需信息。我们将把它定义为CHARCONFIG资源类型,脚本文件应该是这个样子:

DATA_PLAYERINIT CHARCONFIG p1config.dat

很简单,是不是?现在,你已经拥有了数据(plconfig.dat),你还必须分三步使一个指针指向资源数据。这包括我们还没有提到过的需要调用的函数让我们一起解决。第一步,我们必须调用FindResource()函数去发现资源。函数原形如下:

HRSRC FindResource(
    HMODULE hModule, // module handle
    LPCTSTR lpName,  // pointer to resource name
    LPCTSTR lpType   // pointer to resource type
);

返回值是一个资源信息块儿的句柄,如果调用失败,返回NULL。参数意义如下:
HMODULE hModuleHMODULE相当于HINSTANCE。不要问我为什么换了另一个名字,你只要把你的程序实例句柄传送给它就好了,你不需要什么类型转换,它们是相同的。
LPCTSTR lpName:这个是资源的标识符。如果你使用了数字的常量作为标识符,别忘了使用MAKEINTRESOURCE()宏。
LPCTSTR lpType:这个是资源的类型,你需要把你定义的资源类型名称的字符串传递给它。我们的是CHARCONFIG
调用函数方式如下:

HRSRC hRsrc = FindResource(hinstance, MAKEINTRESOURCE(DATA_PLAYERINIT), "CHARCONFIG");

这是信息块儿所在资源的句柄。下一步是要得到指向数据的指针。需要把句柄传递给LoadResource()函数,来调用数据。这将产生一个资源本身的句柄。下面是函数的原形:

HGLOBAL LoadResource(
    HMODULE hModule, // resource-module handle
    HRSRC hResInfo   // resource handle
);

返回类型HGLOBAL是一个普通句柄类型,是相对于我们说过的那些HBITMAPHICON等句柄类型。如果调用函数失败,将返回NULL。参数解释如下:
HMODULE hModule:老东西,程序实例的句柄。
HRSRC hResInfo:把FindResource()得到的句柄传递给它。
现在,我们有了资源的句柄,就可以得到指向数据(自定义的)的指针了,这需要调用LockResource()函数来完成。原形如下:

LPVOID LockResource(HGLOBAL hResData);

仅仅把调用LoadResource()函数得到的句柄传递给它就万事大吉了。如果返回值是NULL,说明函数调用失败。否则,我们就得到梦寐以求的指针!现在我们可以自由得处理数据了。注意:返回的类型是LPVOID,(相当于void*),所以若你想把指针指向队列符号,你还要注意转换成类似BYTE*型的哦!现在,我们完成了所有的步骤,这里,我将展示给你一个指针指向特殊资源的实例:

UCHAR* LoadCustomResource(int resID)
{
    HRSRC hResInfo;
    HGLOBAL hResource;

    // first find the resource info block
    if ((hResInfo = FindResource(hinstance, MAKEINTRESOURCE(resID), "CUSTOMRESOURCETYPE")) == NULL)
        return(NULL);

    // now get a handle to the resource
    if ((hResource = LoadResource(hinstance, hResInfo)) == NULL)
        return(NULL);

    // finally get and return a pointer to the resource
    return ((UCHAR*)LockResource(hResource));
}

总结
好了,以上就是关于资源的部分。看,Windows编程比想象的容易吧。学了这么多,好像还是不能做什么,所以,下一章,我将向你介绍一些基本的Windows图形设备接口函数,你就可以利用我们所学过的所有东西作一点作品出来了。

阅读(2753) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册