这个相对于我那个小游戏入门就显得比较专业了,但是仍然会给源码,关注可以下载相应的模板,模板是给全的。###感谢老司机(一个人)###
了解下游戏的过程吧,一般来讲游戏分下面几种状态
游戏状态(game_state)
#define Game_Menu 0 //游戏菜单
#define Game_Ready 1 //游戏加载
#define Game_Play 2 //游戏进行
#define Game_Pause 3 //游戏暂停
#define Game_Over 4 //游戏结束
就是这几个,所以在编程中,我们可以用一个int型变量来记录当前的游戏状态:
int game_state = Game_Menu;
Game_Play可延伸为Game_Play_Common和Game_Play_Boss甚至更多,这样做可以区分普通关卡和boss关卡。读者也可以采用枚举类型(enum)来储存游戏状态。
(二)认识WINAPI WinMain函数
这个函数就类似于DOS(控制台)程序中的int main()函数一样,它是窗体程序的入口。
一般来说,WinMain里有以下三部曲:
1.注册窗口 2.创建窗口 3.进入主事件循环
让我们来看一下WINAPI WinMain的函数原型:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
接下来让我们来看一下每个参数:
●hInstance:这是一个Windows为应用程序生成的实例句柄。句柄是什么呢?句柄是一种特殊的智能指针 。它可以指向一个窗口,一个场景设备等等,而不是我们普通的指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。而实例句柄又是什么呢?实例就是一个用来跟踪资源的指针或数,hInstance就像一个名字或地址一样,跟踪应用程序。
●hPrevInstance:这个是Windows旧版本里使用的实例句柄,现在已经不再使用了,但在WinMain的函数头里还是没有去除。
●lpCmdLine:这是一个空值终止字符串,和main(int argc,char **argv)函数中的命令行参数相似,由于我们在游戏制作中使用得较少,这里就不详细介绍。
●nCmdShow:这个参数在启动过程中被传递给应用程序,带有如何打开主应用程序的信息,不过这个参数在游戏制作中同样使用得较少,也不详细介绍。
当然,如果现在你看不懂,可以理解,读者可以先接着看下面的内容,待会再回来消化。
typedef struct WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
通过设置各个参数,就可以设置窗口的格式啦!让我们来看看是怎样的:
●cbSize:这是一个非常重要的参数,它是WNDCLASSEX结构本身的大小,可能读者会问,为什么需要知道结构的大小?原因是当这个结构作为一个指针被传递的话,其他函数在运行时就不必计算该类的大小,会提高速度。因此,我们这样写就可以了:
wndclass.cbSize = sizeof(WNDCLASSEX);
●style:这个参数是描述窗口一般属性的样式标志,比较常用的是CS_HREDRAW和CS_VREDRAW(若移动或改变窗口高度/宽度,则刷新整个窗口),想了解更多可以去百度百科上查。如果想定义多个标志,可以用逻辑或运算符“|”,如下:
wndclass.style = CS_HREDRAW | CS_VREDRAW;
●lpfnWndProc:这个参数是指向事件处理程序的函数指针(简单来说,事件处理程序就是处理键盘,鼠标输入等的一个函数),下面将会做更详细的介绍,我们现在先这样设定:
wndclass.lpfnWndProc = WndProc;
●cbClsExtra和cbWndExtra:这两个参数原是为指示Windows将附加的运行时间信息保存到Windows类某些单元而设计的,但是我们还是很少用到……所以,将其设为0甚至不设置它的值。
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
●hInstance:是不是觉得很熟悉呢?没错,它就是我们刚才提到的Winmain函数头里的hInstance!所以,直接复制过来就ok了。
wndclass.hInstance = hInstance;
●hIcon,hIconSm:这个从英文字面来看就可以理解,没错,这就是窗口程序的图标,我们可以选取一个ICO文件作为窗口的图标,但是!现在先忍一忍,将其设置为NULL,因为加载资源的函数会在后面统一介绍!
wndclass.hIcon = NULL; wndclass.hIconSm = NULL;
●hCursor:这个则是设置鼠标指针的形状,同样,可以选择别的图片来代替鼠标,还是那个原因,所以暂时设为LoadCursor(NULL, IDC_ARROW);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
●hbrBackground:这个是窗口的背景,一般没什么用,设为NULL,因为游戏过程会不断贴图,背景一下子就会被覆盖掉。但是由于这是第一个窗体程序,所以设计一个背景可能会好一点,可以使用两种方式赋值:
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.hbrBackground = CreateSolidBrush(RGB(0,0,0));
第一种方法是使用Windows已经帮制作者配备的几种基本画刷,比如WHITE_BRUSH,BLACK_BRUSH, GRAY_BRUSH
等等,有兴趣查找全部的可以百度百科上找,抑或去wingdi.h里查找。
第二种方法是自己创建一个画刷,颜色自己决定,就是RGB值。RGB是什么?R(Red)G(Green)B(Blue),为三原色,这三种颜色的混合可以产生不同的颜色,而数值则是它们的强度(0-255),通过设置三个数值大小就可以表示许多颜色,比如红色的RGB值为(255,0,0),因为它的R值为255,G值和B值为0,同理,蓝色的RGB值为(0,0,255),其他颜色可以参照RGB表。而创建自定义画刷所需函数则为CreateSolidBrush。
●lpszMenuName:它是菜单资源名称的空值终止ASCII字符串,用于加载和选用窗口,同样,对于我们还是没什么作用,就设置为NULL。
●lpszClassName:类名,可以这样理解。Windows需要一些途径来跟踪和识别类,而lpszClassName就用于该目的,可以根据自己喜好起一个好听的名字,不过还是建议用“WNDCLASS1”,“WindowsClass”等简洁易懂的名字。
wndclass.lpszClassName = "WindowClass";//注意是字符串!
呼!终于讲完了!一般来说,并不需要所有的结构体成员都全部赋值,可以根据需求来选择性赋值。下面就是一个简单的注册窗口的过程:
WNDCLASSEX wc; //创建一个类
memset(&wc,0,sizeof(wc));//重置
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszClassName = "WindowClass";
wc.hIcon = NULL;
wc.hIconSm = NULL;
表格填好了,接下来就将这个表格提交给Windows,注册窗口。
if(!RegisterClassEx(&wc)) {
MessageBox(NULL,"Window Registration
Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}
RegisterClassEx(&wc)
这个函数就是注册窗口的函数,但为什么会出现MessageBox这个函数呢?其实这是个错误检测,一旦注册窗口不成功,就会调用MessageBox函数,这个函数的作用是弹出一个窗口告诉用户注册失败。
至于MessageBox函数会在控件那一章详细介绍。记住!真正注册窗口的是这个函数: RegisterClassEx(&wc);
HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
CreateWindowEx函数里的参数同样也是用于设置,丰富窗口格式的。
●dwExStyle:高级属性,大多数情况下设为NULL,我个人使用的是WS_EX_CLIENTEDGE(其实也没什么用,就是带阴影边界),想了解更多可以去百度百科找。
●lpClassName:这就是刚才注册窗口类的类名,想起来了吗?直接复制进去即可。
●lpWindowName:窗口标题,想写什么就写什么,比如“Hello World!”,如果不想写什么东西,就“”即可。
●dwStyle:这是一个说明窗口外观和行为的通用窗口标志,非常重要也很好用!这里列出我常用的几种:
WS_VISIBLE:开始就可见的窗口
WS_OVERLAPPEDWINDOW:创建一个具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,WS_THICKFRAME,WS_MINIMIZEBOX,WS_MAXIMIZEBOX风格的层叠窗口。
WS_OVERLAPPED:产生一个层叠的窗口。一个层叠的窗口有一个标题条和一个边框。
WS_THICKFRAME:创建一个具有可调边框的窗口。
WS_SYSMENU:创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格。
WS_CAPTION:创建一个有标题框的窗口。
WS_MINIMIZEBOX,WS_MAXIMIZEBOX:有最大化和最小化按钮的窗口。
WS_POPUP:弹出式窗口,用于全屏。
值得一提的是,如果想创建一个没有最大化按钮和最小化按钮的窗口,可以这样做:WS_OVERLAPPEDWINDOWWS_MINIMIZEBOXWS_MAXIMIZEBOX,同样,WS_OVERLAPPEDWINDOW^WS_THICKFRAME 就是禁止调控边框。当然,还可以使用或运算符来进行不同组合。
●x,y:这个是窗口左上角在屏幕的位置,如果不想设置,可以用CW_USEDEFAULT,就交给Windows去设置就好,亲测效果就是,和控制台的小黑框的位置是一样的。
●nWidth,nHeight:这个也应该一目了然,就是窗口的宽度和高度,当然咯,如果还是无所谓,就依旧用CW_USEDEFAULT就好。
●hWndParent:这个是用来创建子窗口的,后面我们会详细介绍,现在先设为NULL。
●hInstance:不用我说,肯定能想起来吧!没错,复制下去就对了。
● lpParam:高级特征,此处设置为NULL即可。
介绍完了,那么,当CreateWindowEx函数运行成功时,将会返回一个窗口句柄,如果忘了句柄,就回去再看看,如果失败,将不返回值,所以我们要创建一个句柄来保存。因为这个句柄以后会被很多函数用到,所以要放在全局变量里。
HWND hwnd; //注意,要放在全局变量
hwnd =
CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","",WS_VISIBLE|WS_OVERLAPPEDWINDOW^WS_THICKFRAME^WS_MINIMIZEBOX^WS_MAXIMIZEBOX,
(Screen_FullWidth-Window_FullWidth)/2,(Screen_FullHeight-Window_FullHeight)/2,Window_FullWidth,Window_FullHeight,NULL,NULL,hInstance,NULL);//
这个在Winmain函数里调用
可能读者会注意到
(Screen_FullWidth-Window_FullWidth)/2和(Screen_FullHeight-Window_FullHeight)/2
这两个东西,这是什么呢?其实这是我设置窗口在屏幕居中位置的一个小技巧,扯一下思路:
另外,请注意电脑中的坐标系,和我们平常数学中的坐标系不一样,Y轴是反向的!
因为hwnd
是全局变量,所以hwnd
在定义时为NULL,而如果CreateWindowEx
函数运行失败,将不会返回值,这就意味着hwnd
仍为NULL,我们可以根据这点来检测错误:
if(hwnd == NULL) {
MessageBox(NULL,"Window Creation Failed!",
"Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}
一般来说,Winmain
函数到这里就已经会有一个窗口了,只要不出错,以及dwStyle
有设置WS_VISIBLE
。当然也可以调用下面的函数手工显示窗口。
UpdateWindow(hwnd);//更新窗口
这个主事件循环的思路为:检测在消息队列当中是否有消息,也就是 PeekMessage(&msg,0,0,0,PM_REMOVE)这个函数。如果有(例如键盘,鼠标输入),就处理,也就是TranslateMessage(&msg)和DispatchMessage(&msg)这两个函数。前者是一个虚拟加速键翻译器,其实就是输入工具,而后者指出所有操作发生的位置,在此就不做具体说明了,只管调用就好。
如果消息队列当中没有消息,那就进行游戏循环,也就是game_Play(hwnd)和 game_Paint(hwnd)函数,这两个函数也就是我们开头有讲的游戏过程性编程中的函数,前者负责计算,后者负责绘图,而现在这两样我们还没学,所以这两个函数目前是空的,不过在接下来几章都会讲到。当然了,这两个函数是我们自己定义的,不一定要叫Play和Paint,也可以叫Update和Render,只要能看得懂就好了,不过,在以后的教程中,都以Play和Paint命名。
简单来说,这个主事件循环的思路就是,要么接收消息并处理,要么进行计算和绘图。看到这里,我们就会发现键盘,鼠标输入和绘图的处理,并不是同时的,只是由于电脑运行速度够快,我们看不出罢了。
到这里,三部曲就完成了,抛开事件处理程序(这个在初探Windows窗体程序(下)会继续讲解),我们也就可以做出我们的第一个窗口程序了!
(三)创建一个工程
要注意的是,使用DEV-C++创建一个窗体程序,并不是像控制台那样,直接Ctrl+N就可以的,它比较娇气,需要建立一个工程,这也是以后要添加一些东西做准备。具体步骤如下:
打开DEVCPP.exe,点击“File”-“New”-“Project”,会弹出一个窗口,点击“Window Application”,再点击“Yes”,就会建立一个.dev文件,它负责链接各个文件并集中编译,而且C++会自动配置一个main.cpp文件,通过前面的学习,相信读者已经能看懂这些代码了,但这个main.cpp里的代码并不是很全面,所以我做了一个模板,将main.cpp里的内容替换成模板里的内容,再编译,运行,一个窗口程序生成了!
好了,就到这里了,读者可以再看几遍,慢慢消化。如果觉得差不多了
注意了,在项目里有winmain的,当时我,,,,把他背会了,,,,,