2D中小游戏开发C++游戏教程

初探Windows窗体程序(上)2D游戏制作从入门到入土。

这个相对于我那个小游戏入门就显得比较专业了,但是仍然会给源码,关注可以下载相应的模板,模板是给全的。###感谢老司机(一个人)###

First

了解下游戏的过程吧,一般来讲游戏分下面几种状态
游戏状态(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:这个参数在启动过程中被传递给应用程序,带有如何打开主应用程序的信息,不过这个参数在游戏制作中同样使用得较少,也不详细介绍。

当然,如果现在你看不懂,可以理解,读者可以先接着看下面的内容,待会再回来消化。

  1. 注册窗口
    就像账号一样先要注册才能登录,注册窗口就是向Windows申请建立一个窗口,并且窗口的样式由制作者决定。注册窗口的过程就像填一张表格,填完之后把表格交给Windows,让Windows按照表格上的内容创建窗口。
    那这张表格是什么呢?就是WNDCLASSEX数据结构,我们看下Windows头文件的定义:
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);

  1. 创建窗口
    当完成注册窗口类的任务之后,接下来就该创建一个窗口了。使用CreateWindowEx函数就可以创建一个窗口了。下面是函数原型:
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);//更新窗口
  1. 进入主事件循环
    好了,现在来到主事件循环了,这个一般是很简单的。
    while (msg.message != WM_QUIT) {
    if (PeekMessage(&msg,0,0,0,PM_REMOVE))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }
    else
    {
    game_Play();
    game_Paint();
    }
    }
    return msg.wParam;
    这里出现了一个msg,它是什么呢?其实从while循环的判断条件便能看出一二了,这个msg其实是一个实际的WinProc处理的消息标识符,这个将在后面讲解,现在可以把它理解为是窗口的状态(就像最开始提到的game_state一样!)而窗口的状态将在初探Windows窗体程序(下)继续讲解。

这个主事件循环的思路为:检测在消息队列当中是否有消息,也就是 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的,当时我,,,,把他背会了,,,,,

这篇文章原创是–》老司机

你可能感兴趣的:(游戏)