第一章 DirectX 计算机图形学(下)

创建一个Windows窗体

如果使用C++语言在Windows平台上做应用开发的话,主要有3种基本方式:第一种就是使用Windows API来进行开发。它是Windows操作系统与应用程序之间通信的基本接口。第二种使用MFC来进行开发。MFC是微软对Windows API进行封装的C++类。第三种方式就是Windows Forms。它是微软.NET开发框架的图形用户界面部分。本套课程我们使用第一种方式,使用C++语言调用Windows API直接与操作系统通信,再配上能够支持显卡硬件的图形库DirectX,它的综合执行效率是最高的,当然也是最底层,最困难的。但是,这种方式能够让我们从底层更详细的了解游戏开发过程中的细节。也就是说,这种方式对于我们游戏基础知识的理解和掌握是最有效的。

Windows API基本数据类型包括:BYTE、CHAR、WORD、SHORT、INT等等。所有的Windows数据类型都是由C语言数据类型经过类型重定义得到的。DWORD实质上就是 unsigned long 数据类型,32位无符号整型。无符号类型一般是以“U”开头,比如“INT”是有符号类型,“UINT”是无符号类型。指针类型前加“LP”或“P”,比如指向DWORD的指针类型为“LPDWORD”和“PDWORD”。STR代表字符串,C代表const,T代表宽字符,例如,LPSTR代表字符指针,也就是字符串变量,LPCSTR代表字符串常量, LPCTSTR代表宽字符串常量。各种句柄类型的命名方式一般都是在对象名前加“H”,HWND代表操作窗口的句柄,HICON代表图标的句柄,HCURSOR代表光标的句柄。所有的Windows数据类型都是通过这种方式在SDK的头文件中进行定义的,它们都是来源于标准C语言的。接下来,我们使用 VS2019来创建一个窗体。首先,创建一个C++空项目。

 然后填写项目名称“D3D_01_Windows”和存放路径。

 需要注意的是,这个项目默认是“控制台”程序,然后点击创建:

右击“源文件”->“添加(D)->“新建项(W)…”,创建“main.cpp”源码文件。

复制如下代码到“main.cpp”文件中。 

#include <windows.h>

// Windows应用程序入口文件
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
	// 显示一个消息框
	MessageBox(NULL, L"Hello Windows!", L"标题", 0);
	return 0;
}

这里还需要修改一下程序运行方式,右击项目名称“D3D_01_Windows”选择“属性”,在“链接器”下的“系统”项目中,将“子系统”的值由“控制台”改为“窗口”。

点击工具栏上面的“本地Windows调试器”运行程序。

微软为了方便我们开发基于Windows的应用程序,为我们提供了各种各样的函数。这些函数就是Windows API,这些函数都在Windows.h头文件中进行声明了。任何Windows应用程序与Windows本身之间的所有通信,都要通过这个接口。MessageBox就是Windows API中的一个函数而已,它用于显示一个消息框。我们肯定不会使用MessageBox来进行开发游戏的,它只是一个提示框而已,并不是一个窗口。在使用C++控制台程序的时候,我们知道它的入口函数是main,而在Windows应用程序中的入口函数为wWinMain,如下:

int WINAPI wWinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    PWSTR pCmdLine, 
    int nCmdShow);

函数返回类型 int 之后的WINAPI 是一个宏,其实就是 __stdcall,它让编译器以兼容性性模式来编译代码。如果去掉WINAPI也是可以的,但是一般我们都保留即可。

第一个参数,HINSTANCE类型的hInstance,它表示该程序运行的实例句柄。hInstance其实就是一个数值。一个应用程序可以运行多个实例,每个实例都会分配一个句柄值,并通过hInstance参数传递给WinMain函数。这个参数我们需要使用的,非常的重要。

第二个参数,也是HINSTANCE 类型的hPrevInstance,它表示当前实例的前一个实例的句柄。官方已经说明,这个参数没有任何意义,我们根本不会使用到这个参数。

第三个参数,PWSTR类型 pCmdLine,它就是一个字符串,传递给程序的命令行参数。通常我们也不需要在命令行下执行exe文件,因此我们也基本不使用这个参数。

第四个参数,int 类型nCmdShow,是一个标志,用于指示主应用程序窗口是最小化、最大化还是正常显示。这个参数我们在显示窗口的时候会用到。

接下来,我们才真正的创建一个Windows窗体。每一个Windows应用程序至少要有一个窗体(也叫窗口),窗口中包含标题栏,菜单栏,工具栏等等。我们能看到的几乎所有的Windows的应用程序都会依附在窗口之上的。在Windows应用程序中,窗口都是通过窗口句柄(HWND)来标示的。句柄是Windows程序中标识资源的符号。比如说窗口,图片,鼠标等等。系统创建这些资源的时候,都会为其分配内存,并返回每个资源的符号标识,这就是句柄。本质就是持有特殊资源的变量而已。理解这些概念之后,我们就开始重写main.cpp

// 引入头文件
#include <windows.h>

#define WINDOW_LEFT		200				// 窗口位置
#define WINDOW_TOP		100				// 窗口位置
#define WINDOW_WIDTH	800				// 窗口宽度
#define WINDOW_HEIGHT	600				// 窗口高度
#define WINDOW_TITLE	L"D3D游戏开发"	// 窗口标题
#define CLASS_NAME		L"D3D游戏开发"	// 窗口类名

// 声明窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

我们首先定义一些宏,这些都是用来配置即将生成的窗口。需要注意的就是窗口过程函数的声明,这个是Windows窗体程序的一种交互机制,创建窗口的时候必须使用这个函数,后续我们在讲解这个函数的用途,它可以接收键盘和鼠标的输入。当然创建窗体的代码肯定要放在入口函数wWinMain中。创建一个窗体共计6个步骤。

// 第一步,构造 WNDCLASSEX 结构体
WNDCLASSEX wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEX);		                // 设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW;	                // 设置窗口的风格样式
wndClass.lpfnWndProc = WndProc;				                // 设置窗口过程函数的指针
wndClass.cbClsExtra = 0;						            // 设置窗口类的附加内存
wndClass.cbWndExtra = 0;						            // 设置窗口的附加内存
wndClass.hInstance = hInstance;					            // 设置程序实例句柄
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);			// 设置图标句柄
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);				// 设置光标句柄
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);// 设置背景画刷句柄
wndClass.lpszMenuName = NULL;								// 设置菜单名称
wndClass.lpszClassName = CLASS_NAME;						// 设置窗口类名称
wndClass.hIconSm = NULL;									// 设置窗口小图标句柄

以上的参数设置其实并不重要,大致理解就可以了。

// 第二步:注册窗口
if (!RegisterClassEx(&wndClass)) return -1;

// 第三步:创建窗口
HWND hwnd = CreateWindow(
	CLASS_NAME,				    // 窗口类名称
	WINDOW_TITLE,				// 窗口标题
	WS_OVERLAPPEDWINDOW,		// 窗口风格
	CW_USEDEFAULT,				// 窗口初始 x 坐标
	CW_USEDEFAULT,				// 窗口初始 y 坐标
	WINDOW_WIDTH,				// 窗口初始宽度
	WINDOW_HEIGHT,			    // 窗口初始高度
	NULL,						// 父窗口句柄
	NULL,						// 窗口菜单句柄
	hInstance,					// 程序实例句柄
	NULL);						// 附加参数

// 第四步:显示窗口
MoveWindow(hwnd, WINDOW_LEFT, WINDOW_TOP, WINDOW_WIDTH, WINDOW_HEIGHT, true);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

我们之前的控制台程序,基本上都是按照代码的顺序运行,代码执行完毕,程序也就结束了。Windows窗体程序属于事件(消息)响应机制,也就是说,它的逻辑代码的运行是通过外界用户的事件(消息)来驱动的。这种事件包括键盘输入,鼠标点击等等。当窗体生成后,如果不发生任何事件,则窗体会一直存在且无变化。Windows窗体程序的事件(消息)监听是由操作系统来完成的,然后交给Windows窗体程序的窗口过程函数来处理。所有的事件都是无序的,因为你并不知道用户会在那个时间点击那个按钮事件。因此,我们的Windows窗体程序需要使用一些无限循环来不停的接收操作系统发过来的事件(消息)。

// 第五步:消息循环过程
MSG msg = { 0 }; // 定义一个消息对象(msg)
// 使用while循环消息队列,如果消息不是WM_QUIT消息,就继续循环
while (msg.message != WM_QUIT)
{
	// 获取消息并交给窗口过程函数
	if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

// 第六步:注销窗口
UnregisterClass(CLASS_NAME, wndClass.hInstance);
return 0;

至此,窗体创建的6个步骤就完成了。接下来,我们还得完成窗口过程函数。

// 定义窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch (message)
	{
	case WM_PAINT:
		ValidateRect(hwnd, NULL);	// 窗口重绘消息
		break;
	case WM_DESTROY:
		PostQuitMessage(0);			// 窗口销毁消息
		break;
	default:						// 调用缺省的窗口过程
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;
}

我们说窗口过程函数就是用来处理事件的,事件不同,处理的逻辑也就不同。上文中我们只处理两种事件,一个是窗口重绘,一个是窗口销毁。窗口重绘的意思就是改变窗口大小的时候,比如最大化窗口。窗口销毁就简单了,点击窗口右上角的X就是关闭窗口,窗口关闭了,Windows窗体程序也就是随之结束了。点击工具栏上面的“本地Windows调试器”运行程序,就能看到我们创建的窗体了。游戏开发就是在这个窗体上绘制2D图像或3D模型。这个窗口程序虽然涉及代码很多,但是基本都是固定的,也不是游戏开发的主要内容,因此大家能够理解就行了。

本课程的所有代码案例下载地址:

workspace.zip

备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咆哮的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值