Windows游戏编程 第二章.docx
《Windows游戏编程 第二章.docx》由会员分享,可在线阅读,更多相关《Windows游戏编程 第二章.docx(35页珍藏版)》请在冰豆网上搜索。
Windows游戏编程第二章
第2章游戏的Windows代码
一般来说,在Windows下建立一个应用程序,至少需要做4件事情。
第1件是定义一个窗口类;第2件是根据第一步里创建的窗口类建立窗口;第3件是建立消息机制;第4件是根据窗口类里指定的窗口回调函数指针建立具体的回调函数。
在Windows应用程序类中,包含了以上列举的所有功能,本章就来创建一个名为CApplication的Windows应用程序类。
2.1窗口与窗口类
在DOS下运行一个程序时,由于DOS是单任务操作,所以同一时刻只能运行一个程序,用户只看到一个显示区域(即,窗口)。
这样,编一个在DOS下运行的程序是比较简单的。
而Windows系统是多任务的系统,可以同时运行多个应用程序。
要创建一个简单的应用程序,需要给程序指定标题、边框、按钮、接收消息进程以及窗口类别等等。
所以,Windows应用程序需要做很多的工作。
可喜的是,这些工作的很大一部分由Windows本身完成。
2.1.1注册窗口类
Windows应用程序是根据窗口类来建立的。
窗口类定义了窗口的图标、鼠标指、针背景色和回调函数等。
用WNDCLASS来定义窗口类,该数据结构的定义如下:
typedefstructtagWNDCLASS{
UINTstyle;
WNDPROClpfnWndProc;
intcbClsExtra;
intcbWndExtra;
HINSTANCEhInstance;
HICONhIcon;
HCURSORhCursor;
HBRUSHhbrBackground;
LPCSTRlpszMenuName;
LPCSTRlpszClassName;
}WNDCLASS;
将CS_HREDRAW和CS_VREDRAW的复合值给style,它表示每当窗口的水平方向或者垂直方向的大小改变时,窗口要全部重画。
lpfnWndProc成员指定窗口类别的消息处理函数(回调函数),将它指定为WinProc函数。
cbClsExtra和cbWndExtra成员指定给窗口结构的额外空间,一般用不到它,所以将它们置为零。
hInstance代表程序的实例句柄。
hIcon和hCursor指定窗口类的图标和鼠标光标,将它们设置为Windows默认的值。
hbrBackground指定窗口类的背景画刷(背景色)。
lpszMenuName指定窗口类的菜单,由于程序没有用到菜单,所以置为NULL。
lpszClassName指定该窗口类的名称。
定义了一个窗口类后,应该注册该窗口类。
通过调用RegisterClass函数可以注册窗口类。
它的参数只有一个,即一个指向窗口类数据结构的指针参数。
如果注册失败,RegisterClass返回的值为NULL。
2.1.2Windows窗口的创建
创建窗口的方法是CreateWindow,其语法定义如下:
HWNDCreateWindow(
LPCTSTRlpClassName,
LPCTSTRlpWindowName,
DWORDdwStyle,
intx,
inty,
intnWidth,
intnHeight,
HWNDhWndParent,
HMENUhMenu,
HINSTANCEhInstance,
LPVOIDlpParam
);
参数说明:
♦lpClassName:
指定窗口类,对应于WNDCLASS结构的lpszClassName参数。
♦lpWindowName:
表示窗口的名称。
简单地说,就是标题栏中的名称。
♦dwStyle:
指定创建窗口的风格。
这些风格(标志)在Winuser.h中有定义。
可以试着一一使用这些风格,以看窗口会出现什么变化。
在窗口模式下,程序使用WS_OVERLAPPEDWINDOW&~WS_MAXIMIZEBOX的复合值,表示在外观上窗口有标题栏、边框以及在标题栏上有关闭按钮和最小化按钮(~WS_MAXIMIZEBOX表示去掉最大化按钮)。
♦x和y:
指定了窗口的左上角位置。
♦nWidth和nHeight:
分别代表窗口的宽度和高度。
♦hMenu:
窗口的菜单句柄。
♦hInstance:
应用程序的实例句柄。
♦lpParam:
创建参数。
下面是一个窗口的创建例程:
//...
HWNDhwnd;
TCHARwcName[]="Class1"
WNDCLASSwc;
wc.hInstance=hInstance;
wc.lpszClassName=wcName;
wc.lpfnWndProc=WinProc;
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.hIcon=LoadIcon(NULL,MAKEINTRESOURCE(IDI_APPLICATION));
wc.hCursor=LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW));
wc.lpszMenuName=NULL;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
if(!
RegisterClass(&wc))
{
MessageBox(NULL,"注册窗口类出错!
","Error!
",MB_OK);
returnFALSE;
}
hwnd=CreateWindow(
wcName,
"App_test",
WS_OVERLAPPEDWINDOW&~WS_MAXIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MYMENU));,
hInstance,
NULL);
//...
该例程定义了一个名为Class1的窗口类并注册,然后调用CreateWindow创建一个属于Class1窗口类的窗口,其名称为App_test,窗口的位置为系统默认。
在定义窗口类过程中,使用到了3个函数,分别是LoadIcon、LoadCursor和GetStockObject。
LoadIcon函数的功能是从与应用实例关联的可执行文件中载入指定的图标资源。
其语法如下:
HICONLoadIcon(HINSTANCEhInstance,LPCTSTRlpIconName);
♦hInstance:
程序的实例句柄。
如果该参数的值是NULL,表示将加载由系统定义好了的图标资源。
♦lpIconName:
当hInstance参数的值不为NULL时,表示要载入自定义的图标资源;此时lpIconName参数使用低位表示资源的标识符,而高位无用置为0;利用宏定义MAKEINTRESOURCE可以实现这个表示方法。
例如,定义了一个标识符为IDI_T的图标资源,则使用该图标的方法如下:
wc.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(IDI_T));
当hInstance参数的值为NULL时,lpIconName参数表示加载系统提供的图标。
这些图标的标识符必须是如下的几个之一:
♦IDI_APPLICATION:
表示默认应用程序图标。
♦IDI_ERROR:
一个圆形的中间有一个×的图标,表示出错的意思。
♦IDI_ASTERISK和IDI_INFORMATION:
一个白色的圆形中间有一个感叹号,表示有信息。
♦IDI_EXCLAMATION和IDI_WARNING:
一个黄色的三角形中间有一个感叹号,表示引起注意。
♦IDI_HAND:
手形图标。
♦IDI_QUESTION:
一个问号。
♦IDI_WINLOGO:
LOGO图标。
LoadCursor函数的功能是从与应用实例关联的可执行文件中载入指定的光标资源。
其与LoadIcon函数的使用方法有些类似。
语法为:
HCURSORLoadCursor(HINSTANCEhInstance,LPCTSTRlpCursorName);
它的两个参数hInstance和lpCursorName与LoadIcon的使用方法是一样的,只不过lpCursorName表示光标资源。
系统提供的光标资源主要有:
♦IDC_ARROW:
表示标准箭头。
♦IDC_APPSTARTING:
表示标准箭头加沙漏。
通常用于表示程序忙
♦IDC_CROSS:
表示十字光标。
♦IDC_IBEAM:
表示文本光标。
图形是一竖状的线。
♦IDC_WAIT:
表示沙漏光标。
表示很忙。
♦IDC_UPARROW:
表示向上指的箭头。
GetStockObject函数的功能是获取GDI对象句柄。
当参数的值是BLACK_BRUSH时,表示获取系统的黑色画刷句柄。
是WHITE_BRUSH时,表示获取白色画刷句柄。
一般情况下,如果注册窗口类不成功,则显示一个消息提示对话框;以告诉用户为什么不能注册窗口类。
消息提示对话框的生成函数是MessageBox。
其语法如下:
intMessageBox(HWNDhWnd,LPCTSTRlpText,LPCTSTRlpCap,UINTUType);
第1个参数可以是NULL,表示对话框不属于任何窗口,但是程序的控制权被对话框占有;如果是程序的窗口句柄,则表示对话框属于该窗口,同时程序的控制权也被对话框占有。
第2个参数是表示要显示的信息。
第3个参数表示该对话框的标题栏的名称。
第4个参数表示对话框的内容和行为属性;例如,这个参数的值是MB_OK标志时,表示只有一个“确定”按钮。
在调用CreateWindow创建窗口时,使用到了LoadMenu函数。
该函数的使用方法与LoadIcon相似。
也有两个参数,一个是实例句柄,另一个是菜单名称。
可以使用MAKEINTRESOURCE宏使用数值标识符资源。
2.2消息机制
当发生某一与应用程序相关的输入事件时,Windows会发送一相应的消息给应用程序,该消息被放在应用程序的消息队列中。
应用程序的任务是检查消息队列中的消息,并转换,然后将它们发送给Windows。
首先,必须从消息队列中取出消息。
方法有两个,即GetMessage函数和PeekMessage函数。
这两个方法是不同的,GetMessage的做法是当消息队列中没有消息时,则让程序处于等待状态,所以消耗的系统时间较少。
PeekMessage的做法是不管消息队列中有没有消息都会返回,即一直让程序处于消息循环中,这就很耗费系统时间。
对于一般的应用程序不使用PeekMessage方法,而对于游戏最好使用PeekMessage方法。
它们的语法声明如下:
BOOLGetMessage(
LPMSGlpMsg,
HWNDhWnd,
UINTwMsgFilterMin,
UINTwMsgFilteMax
);
BOOLPeekMessage(
LPMSGlpMsg,
HWNDhWnd,
UINTwMsgFilterMin,
UINTwMsgFilterMax,
UINTwRemoveMsg
);
这两个函数的前四个参数的使用是相同的,其说明如下。
♦lpMsg:
从消息队列中取到消息后的存放地址,其数据结构必须是MSG结构。
♦hWnd:
与消息对应的窗口的句柄。
♦wMsgFilterMin:
消息值的最小值。
♦wMsgFilteMax:
消息值的最大值。
♦PeekMessage函数的wRemoveMsg参数表示处理消息的方式。
其值可以是PM_NOREMOVE,表示消息被处理后仍处于消息队列中。
是PM_REMOVE时,表示消息被处理后被从消息队列里删除。
这一章的程序代码里,没有使用到PeekMessage,因为它耗费的系统时间太多。
在下一章里将使用到它,以及整个这本书提供的大游戏也使用到它,这会使程序的效率得到提高。
当然,也可以使用多线程来编制程序,这可以避免用到PeekMessage函数。
多线程程序较麻烦,这里不介绍。
如下是一个消息的检测循环:
//...
MSGmsg;
msg.message=WM_NULL;
while(msg.message!
=WM_QUIT)
{
if(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//...
GetMessage其中的第2个参数置为NULL,表示检测本窗口的消息,第3和第4个参数置为0表示检测所有的消息(PeekMessage函数也一样)。
如果检测到的消息(msg.message)不是退出程序消息(WM_QUIT),则继续进行消息循环检测。
当消息队列中没有消息时,GetMessage将处于等待状态,即不把控制权还给程序,直到有消息后,才返回。
这时调用TranslateMessage函数将虚拟键消息转换为字符消息,该函数只有一个指向消息数据结构的指针参数。
转换为字符消息后,调用DispatchMessage将转换后的消息传送给Windows。
Windows再把消息传送给消息处理函数。
2.3消息处理函数
可以说,用户是间接地通过消息处理函数来操作应用程序的,这些操作包括最大化、最小化窗口,关闭窗口,窗口切换,画图等等。
当然,如果是游戏程序,这些操作只占整个程序的一小部分。
由上一小节的分析可以知道,消息处理函数是在消息循环机制中被调用的,更确切地说是在调用DispatchMessage函数过程中被调用的。
下面将给应用程序加入具体的处理功能。
消息处理函数的语法如下(假设定义WinProc为消息处理函数的函数名):
LRESULTCALLBACKWinProc(
HWNDhWnd,
UINTuMsg,
WPARAMwParam,
LPARAMlParam
);
♦hWnd:
对应窗口的句柄。
♦uMsg:
传送给消息处理函数的消息。
♦wParam和lParam参数的值与对应的消息有关。
应该注意的是消息处理函数不予处理的所有消息都应该传给DefWindowProc函数处理。
一般用switch语句来处理消息。
如下是一个简单的例子:
LRESULTCALLBACKWinProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam)
{
switch(uMsg)
{
caseWM_DESTROY:
PostQuitMessage(0);
break;
}
//调用缺省消息处理过程
returnDefWindowProc(hWnd,message,wParam,lParam);
}
这个例子只处理了一个销毁程序消息(WM_DESTROY),其他的消息由DefWindowProc函数处理。
2.4代码整理
现在可以利用上面介绍的内容建立一个应用程序CApplication类。
因为以后会经常用到本章提供的代码,归类了以后有利于简化工作。
CApplication类的源代码文件为Application.h和Application.cpp。
2.4.1定义CApplication类
CApplication类的定义代码保存名为Application.h文件,其代码如下:
/*------------------------------------------------------------------
|Application.h
|Application类的相关处理功能的声明
|(c)mochsh,2004
-------------------------------------------------------------------*/
#include
classCApplication
{
protected:
//是否是全屏模式
intm_bFullScreen;
//窗口客户区宽度
intScreenW;
//窗口客户区高度
intScreenH;
//窗口背景画刷
intClientBKColor;
//是否可以改变窗口大小的属性变量
intChange_Size_Able;
//是否使用鼠标指针
intShow_Mouse_Cursor;
//窗口的激活状态
BOOLbActive;
//窗口客户区的相对于屏幕左上角的位置
RECTrectWin;
//窗口图标
HICONhIcon;
//窗口光标
HICONhCursor;
//窗口菜单
HMENUhMenu;
//窗口句柄
HWNDhWnd;
public:
//构造函数
CApplication(void);
CApplication(intbFullScr,intScrW,intScrH);
//设置窗口的属性函数
voidisFullScreen(BOOLbFScr){m_bFullScreen=bFScr;}
voidSetScreenW(intScrW){ScreenW=ScrW;}
voidSetScreenH(intScrH){ScreenH=ScrH;}
voidChangeSizeAble(intChAble){Change_Size_Able=ChAble;}
voidShowMouseCursor(intM){Show_Mouse_Cursor=M;}
voidSetClientBKColor(intC){ClientBKColor=C;}
//设置应用程序的图标
voidSet_hIcon(HINSTANCEhInstance,WORDIcon)
{
hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(Icon));
}
//设置应用程序的光标
voidSet_hCursor(HINSTANCEhInstance,WORDCursor)
{
hCursor=LoadCursor(hInstance,MAKEINTRESOURCE(Cursor));
}
//设置应用程序的菜单
voidSet_hMenu(HINSTANCEhInstance,WORDMenu)
{
hMenu=LoadMenu(hInstance,MAKEINTRESOURCE(Menu));
}
//创建窗口
HWNDCreateWin(WNDPROCWinProc,
HINSTANCEhInstance,
TCHAR*WindowName,
DWORDwinStyle);
//处理主窗口消息
LRESULTMsgProc(HWNDhWnd,UINTuMsg,
WPARAMwParam,LPARAMlParam);
//默认消息循环机制
intRunDefault(void);
//外部调用成员变量
intGet_m_bFullScreen(void){returnm_bFullScreen;}
intGet_ScreenW(void){returnScreenW;}
intGet_ScreenH(void){returnScreenH;}
intGet_ClientBKColor(void){returnClientBKColor;}
intGet_Change_Size_Able(void){returnChange_Size_Able;}
intGet_Show_Mouse_Cursor(void){returnShow_Mouse_Cursor;}
BOOLGet_bActive(void){returnbActive;}
RECTGet_rectWin(void){returnrectWin;}
HICONGet_hIcon(void){returnhIcon;}
HICONGet_hCursor(void){returnhCursor;}
HMENUGet_hMenu(void){returnhMenu;}
HWNDGet_hWnd(void){returnhWnd;}
};
CApplication类的所有成员变量是被保护的。
要说明的是CApplication.cpp文件包含了CApplication类的大部分成员函数的代码。
其头部必须是这样的:
#include"CApplication.h"
#include
//…
如果没有这两行代码,将不能编译程序。
2.4.2初始化变量
CApplication类在构造函数里初始化各种成员变量。
其构造函数是可重载的,以方便参数的设置。
构造函数的代码如下:
/********************************************************
*函数名:
CApplication(...)属于CApplication类的成员
*功能:
构造函数
*参数说明:
*bFullScr是否全屏
*ScrW客户区的宽度(全屏时就是屏幕的宽度)
*ScrH客户区的高度(全屏时就是屏幕的高度)
*(c)mochsh,2004
*********************************************************/
CApplication:
:
CApplication(intbFullScr,intScrW,intScrH)
{
m_bFullScreen=bFullScr;
//全屏模式
if(bFullScr){
if(ScrW>=1024){
ScreenW=1024;
ScreenH=768;
}
elseif(ScrW>=800){
ScreenW=800;
ScreenH=600;
}
else{
ScreenW=640;
ScreenH=480;
}
}
//窗口模式
else{
ScreenW=ScrW;
ScreenH=ScrH;
}
bActive=FALSE;
hWnd=NULL;
Sho