本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
http://blog.csdn.net/zhmxy555/article/details/7688515
作者:毛星云 邮箱: [email protected]
这节笔记主要讨论DirectX 11现有的组件构成,随着DirectX各个版本的更新被时代抛弃的一些组件以及第一个围绕着DirectX 11 的Demo创建全过程。
最近的几次文章的评论中不少读者提到文章的更新速度的更新量的问题,浅墨会逐渐重视起来,尽量在保证质量的基础上,每周都拿出够分量的内容。本周的文章算是近几次更新量的一个弥补,这篇文章的字数达到了近一万字。
一、DirectX11的现有组件
DirectX的API被分为颇多组件,每一组件都展现了系统不同方面的功能。其中的每一套API都能被独立的使用,因此我们可以按照所需有选择地自由添加我们游戏中需要的功能。在最新版本的DirectX中,不少组件迎来了更新,如Direct3D。其他的组件保持着现有的功能,当然,也有一些被弃用的组件面临着被移除的命运。值得一提的是,DirectX中的各个内部组件可以独立进行新功能的升级。
下面进行分别介绍:
1.Direct2D
Direct2D用于Win32应用程序中2D图形的绘制。Direct2D善于高性能矢量图形的渲染。
2. DirectWrite
DirectWrite用于Direct2D应用程序中的字体和文字渲染。
3 . DXGI
DXGI(DirectX Graphics Infrastructure)即DirectX图形基础,用于
Direct3D交换链(swap chains)和枚举设备适配器(enumeration of deviceadapters)的创建。
4 .Direct3D
Direct3D用于DirectX中所有的与3D图形相关的操作。Direct3D可谓DirectX中的大牌级API,受到了微软最多的关怀与多次针对性的更新,它也最多被世人所熟知。这个专栏后续内容的大部分的篇幅将集中于讲解Direct3D上。
5. XAudio2
XAudio2 是一款底层的音频处理API,以前为XDK(Xbox Development Kit)的一部分,目前,隶属于DIRECTXSDK。XAudio2替换了DirectSound。初始版本的XAudio用于的第一代Xbox游戏机。
6. XACT3
XACT3是一款建立在XAudio2之上的更高级别的音频处理API。XACT3允许开发者在他们的应用中使用跨平台音频创作工具。若开发者希望从底层控制音频系统或者希望创建自己的类型于XACT3的更高级别的音频系统,可以运用XAdio2来完成各项功能。XACT3,我们已经在附录B中讨论过,作为“DIRECTX中的声音”从配套网站,是一款非常容易上手的游戏音频制作工具。
7. XInput
XInput是XDK和DirectX SDK的负责输入功能的 API,用于处理Xbox 360控制器输入。基本上任何可以在Xbox 360上可以使用的命令,都可以在PC上使用,而XInput就是帮助我们在这些设备上进行输入相关操作的API。这些设备不仅包含Xbox手柄,也包含了其他很多设备。需要说明的是,XInput 替换了之前版本的DirectInput。
注:XAudio是只能用于Xbox游戏机的音效API。 XAudio2,其继任者,可用于Xbox游戏机和基于Windows的PC。
8 . XNAMath
新出现的XNA Math 不是一款API,而是是一个数学库,进行电子游戏中常见运算的优化工作。XNA Math采用了SIMD (Single Instruction Multiple Data单指令多数据)来执行多个操作的单指令调用。XNA Math库适用于基于Windows的PC以及Xbox 360。相关内容我们将在后续内容继续讲解。
注:XNA GameStudio为一款基于DirectX的游戏开发工具,可供我们使用C#和.NET编程语言来为Xbox360与Windows PC平台编写游戏。而XNA Math是一款DirectX SDK中数学库的名字,可独立于XNA Game Studio单独使用。我们使用XNA Math不必下载XNA Game Studio SDK。
9 . DirectCompute
DirectCompute是一款DIRECTX 11中新加入的API,作用为支持GPU的通用多线程计算(general purpose multi threading computing)。GPU具有并行处理多任务的能力,如物理,视频压缩与视频解压,音频处理等。并非所有的任务都适合GPU来完成,但是对于这些,由GPU来完成的可能性是巨大的(but for those that are, the possibilities are tremendous)。想了解DirectCompute的更多信息,可以查阅相关资料。
10. DirectSetup
当我们完成游戏开发的时候,我们理所当然地需要把完成品呈现给用户。DirectSetup给我们提供了在用户的电脑上安装最新版本的DirectX的功能。DirectSetup也具有检查已经安装了的DirectX版本号的功能。
11.Windows Games Explorer
WindowsGames Explorer(游戏资源管理器)是Windows Vista与Windows 7中的新特性,可供游戏开发者在他们的操作系统上展示(测试)他们的游戏。比如游戏的显示,标题,评级,描述,特定区域的风格框,评分内容(如M为成人,T为青少年等),游戏数据统计和通知,家长控制等。DirectX SDK中提供了大量如何使用自己的游戏的游戏资源管理器的教程,这对于发行一款游戏是非常有用的。下面的图1是浅墨电脑上的Windows7游戏资源管理器的界面截图
图1 Windows 7中游戏资源管理器的示例
12. DirectInput
DirectInput是一款侦测键盘、鼠标和操作杆输入的API。目前Xinput用于所有的游戏控制器。对于键盘和鼠标我们可以使用Win32函数或者使用DirectInput,后续内容将对DirectInput展开讲解。根据DirectX SDK,DirectInput将继续保留目前的形式,直到它被新的技术所取代。
二、已过时DirectX组件
开门见山吧,下面这些组件已经过气,或者已被DirectX SDK移除:
1 . DirectDraw
DirectDraw曾经用于2D图形的渲染,目前我们可以运用Direct2D或者Direct3D来进行2D图形的绘制。在DirectX 8中,DirectDraw与Direct3D进行了合并,并改名为DirectX Graphics。
注:在早期版本的DIRECTX中,2D图形绘制功能由DirectDraw完成。因为DirectDraw不再被更新,我们最好在Direct3D和Direct2D中完成图形的绘制。
2 . DirectPlay
DirectPlay用于网络游戏的网络功能配置,基于UDP(UserDatagram Protocol)协议,并担任更高级别的抽象层网络通信。目前这款API被移除于DIRECTX SDK之外,以便于PC与Xbox360平台上Windows Live中游戏的更好的整合。
3. DirectShow
DirectShow是一款用于多媒体渲染和录音的API。DirectShow能够播放常见的视频文件,并提供DVD视频导航菜单等功能。当前DirectShow为Windows SDK的一部分,而不再隶属于DirectX SDK。此外,Windows Vista与Windows 7用户可以使用的微软媒体中心(Microsoft’s MediaFoundations),也是Windows SDK的一部分。在电子游戏中,若需要显示切换CG的场景与视频文件, DirectShow也可以派上用场。
4. DirectMusic
作用为在应用程序中播放音频内容的DirectMusic ,在DirectX 7及后续版本中被移除。DirectMusic提供了与底层音频与硬件沟通的渠道,在众多的DirectX相关教程中叱咤风云多年。
现今,在游戏和多媒体应用相关的音频操作中,我们使用XAudio2(底层)或XACT3(高层)来替代服役了多年的,颇具疲态的DirectMusic。
5. DirectSound
DirectSound是DirectX中另一款被废置的底层音频API,现今被XAudio2取代。
三、第一个DirectX Demo
学习DirectX的最好方式就是勤动手,从头开始一步一步创建简单的demo应用程序。在接下来连续的几篇文章中,我们将一起通过一步步简单的操作,创建我们的第一个DirectX应用程序。随着学习的深入,我们将对D3D从开始设置到结束过程有一个坚实的认识。
Ⅰ.工程的创建
首先我们需要创建一个工程。
创建一个空白的Win32 窗口新工程请执行以下步骤:
1. 打开Visual Studio 2010,从菜单中依次选择 【文件】->【新建】->【工程】
(【File】 > 【New】 > 【Project】),这时会弹出一个新建工程的对话框,如下图 2
图2
弹出如下窗口:
图3
2. 我们以“BlankWindowDemo”作为工程名,然后在工程模板列表中选择“EmptyProject”,完成后,点击OK按钮,如图4
图4
3.点击“完成”按钮。
一个新的空工程就创建完毕了。下一步就是在工程中添加模板代码了。
Ⅱ.代码的书写
完成上面的步骤,Visual Studio 2010已经为我们创建好一个空的工程了,下一步就是加入源代码来初始化主程序的窗口了。我们以添加一个空白的Cpp文件到工程中开始,这个文件将称为我们的main源程序文件,我们将它命名为main.cpp。创建main.cpp文件的步骤如下:
⑴ 在Visual Studio的资源管理器中右击源文件文件夹,然后在弹出的对话框中选择【Add New Item】,如图5
图5
⑵ 在弹出的对话框中选择C++源文件类型,命名为main.cpp。如图6
⑶点击“OK”完成。
在工程中创建好main.cpp源文件后,我们便可以在其中添加完成Win32空白窗口工作的源代码。一旦我们加入了主程序入口点的代码,我们就可以初始化D3D 11然后利用D3D渲染窗口画布。
代码讲解之一:主函数入口点
main.cpp需要注意的第一个要点是包含必要的Win32头文件和入口点函数。我们应该知道,Win32应用程序的入口点是WinMain函数。目前为止,我们只需要在源文件顶部包括Windows.h头文件即可。在代码段1中可以看到空的WinMain函数和main.cpp的开始部分。
代码段1:Blank Win32 Window Demo书写步骤之一:wWinMain 函数框架
#include<Windows.h> int WINAPI wWinMain( HINSTANCEhInstance , HINSTANCE prevInstance, LPWSTRcmdLine , int cmdShow ) { return0; }
在上面的代码中我们可以看到用wWinMain替换了一般情况下会采用的WinMain。两者的区别是wWinMain用来处理Unicode类型的变量,特别是它的第三个参数cmdLine,而WinMain会执行Unicode和ANSI之间的转换。而这样的转换可能导致缺少一个Unicode字符串中的字符。所以运用wWinMain能让我们正确的处理被传递给应用程序的Unicode类型的参数。
(w)WinMain函数有四个参数。这些参数被定义为如下:
▲HINSTANCE hInstance.应用程序的当前句柄实例
▲HINSTANCEprevInstance. 应用程序的之前句柄实例。MSDN文档中表明这个参数恒为NULL。由于此参数恒为NULL,如果我们需要找到一个判断之前应用程序的实例是否运行的方法,文档中建议我们可以利用CreateMutex函数创建一个唯一命名的互斥变量(mutex)。若该互斥变量被创建了,CreateMutex函数将返回ERROR_ALREADY_EXISTS。
▲LPSTR cmdLine (orLPWSTR in Unicode). 应用程序不包括程序名的命令行指令。
这使得我们可以将命令行指令传递给应用程序,比如在命令提示行中利用命令行字符串提供的快捷操作。
▲int cmdShow. 一个窗口如何被显示的特殊的ID,可取很多已有完善的代码支持的ID。具体取值见下表:
ID值名称
描述
SW_HIDE
隐藏此窗口,激活其他窗口
SW_SHOW
以当前的尺寸和位置激活和显示该窗口
SW_SHOWNA
以当前的状态显示该窗口,并保持激活状态
SW_SHOWNOACTIVATE
用最近的尺寸和大小显示一个窗口,并保持激活状态
SW_SHOWNORMAL
激活和显示一个窗口,如果这个窗口是最小化或者最大化,系统将它重置为原始尺寸和位置(与SW_RESORE相同)
在窗口创建过程中显示窗口的时候,需要使用show ID命令,后面中我们会具体讲解。
代码讲解之二:窗口的初始化
如果没有进行窗口创建过程的话,即使应用程序运行了,也不会在屏幕上显示出来。所以下一步我们的任务是创建Win32窗口。首先,我们注册窗口类,然后创建窗口本身。如代码段2中所述。应用程序必须在系统中注册它的窗口。
代码段2:Blank Win32 Window Demo书写步骤之二:窗口的注册和创建
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCEprevInstance, LPWSTR cmdLine, int cmdShow ) { UNREFERENCED_PARAMETER( prevInstance ); UNREFERENCED_PARAMETER( cmdLine ); WNDCLASSEX wndClass = { 0 }; wndClass.cbSize = sizeof( WNDCLASSEX ) ; wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = WndProc; wndClass.hInstance = hInstance; wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndClass.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 ); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = "DIRECTX11BookWindowClass"; if( !RegisterClassEx( &wndClass ) ) return -1; RECT rc = { 0, 0, 640, 480 }; AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE ); HWND hwnd = CreateWindowA( "DIRECTX11BookWindowClass","Blank Win32 Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right- rc. left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL ); if( !hwnd ) return -1; ShowWindow( hwnd, cmdShow ); return 0; }
Win32宏UNREFERENCED_PARAMETER用于避免变量定义后没在函数体中使用的编译警告。虽然这在技术上来讲是毫无必要的,但是作为养成良好的编程习惯,我们在组建源代码时,要力争零warning。由于这样的宏实际上什么都没有做,Visual Studio的编译器在编译时,会将其忽视的,就像忽视喜闻乐见的注释一样。
在两句UNREFERENCED_PARAMETER宏之后,我们用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX。这个窗口类中包含很多Win32窗口的很多属性,比如窗口图标,窗口菜单,窗口的应用实例,光标的样式等等。WNDCLASSEX包含在头文件Winuser.h中,而Winuser.h又包含在windows.h中。即我们只需在头文件中包含Windows.h即可。我们可以在windows.h源代码中找到如下描述:
typedef struct tagWNDCLASSEX { 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;
WNDCLASSEX结构体中的参数详细剖析如下:
▲cbSize.结构体的字节数大小
▲style.第二个成员变量style指定这一类型窗口的样式,常用的样式如下:
ID值名称
描述
CS_HREDRAW
当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。
CS_VREDRAW
当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。
CS_NOCLOSE
禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。
CS_DBLCLKS
当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。
▲lpfnWndProc. 第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数实现的机制是:
(1)定义一个回调函数。
(2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。
(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
针对Windows的消息处理机制,窗口过程函数被调用的过程如下:
(1)在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc成员变量。
(2)调用RegsiterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。
(3)当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。
一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联(通过WNDCLASS结构体中的lpfnWndProc成员变量指定),基于该窗口过程。
lpfnWndProc成员变量的类型是WNDPROC,我们在VC++开发环境中使用goto definition功能,可以看到WNDPROC的定义:
typedef LRESULT (CALLBACK*WNDPROC)(HWND, UINT, WPARAM, LPARAM);
在这里又出现了两个新的数据类型LRESULT和CALLBACK,再次使用goto definition,可以看到它们实际上是long和__stdcall。
从WNDPROC的定义可以知道,WNDPROC实际上是函数指针类型。
注意:WNDPROC被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同。
知识点 在函数调用过程中,会使用栈。__stdcall与__cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。关于这两个调用约定的详细信息,大家可参看MSDN。对于参数个数可变的函数,例如printf,使用的是__cdecl调用约定,Win32的API函数都遵循__stdcall调用约定。在VC++开发环境中,默认的编译选项是__cdecl,对于那些需要__stdcall调用约定的函数,在声明时必须显式地加上__stdcall。在Windows程序中,回调函数必须遵循__stdcall调用约定,所以我们在声明回调函数时要使用CALLBACK。使用CALLBACK而不是__stdcall的原因是为了告诉我们这是一个回调函数。注意,在Windows 98和Windows 2000下,声明窗口过程函数时,即使不使用CALLBACK也不会出错,但在Windows NT4.0下,则会出错。
▲cbClsExtra. 类附加内存。
▲cbWndExtra. 窗口附加内存。
▲hInstance. 指定包含窗口过程的程序的实例句柄。
▲hIcon. 指定窗口类的图标句柄。
▲hCursor. 指定窗口类的光标句柄。
▲hbrBackground. 指定窗口类的背景画刷句柄。
▲lpszMenuName. 一个以空终止的字符串,指定菜单资源的名字。
▲lpszClassName. 一个以空终止的字符串,指定窗口类的名字。
▲hIconSm. 指定窗口类的小图标句柄。(就像在任务栏右下角托盘中应用程序显示的小图标)
大部分的Win32应用程序的窗口特性我们是不做考虑的,如创建一个菜单(在编辑区之外,我们一般不会在游戏中创建一个Win32菜单)。这些成员变量我们在这里一般设为0。
随着WNDCLASSEX结构体的创建,我们可以将它用RegisterClassEx( )调用,来注册一个窗口。RegisterClassEx()必须在我们创建窗口之前调用,且调用时需要用到窗口类的地址作为变量。若函数返回值为0,则表示注册失败,这时,我们需要检查窗口相关值的合法性。
下一步就是创建实际的窗口了。首先我们调用AdjustWindowRect()函数来根据我们设定的尺寸和风格来计算窗口的尺寸。窗口的类型取决于我们需要的真实尺寸。如果我们需要Win32应用程序,就会有像标题栏这样的非客户区,一个环绕着应用程序的边框,等等。如果我们要创建一个特殊的窗口尺寸,我们需要牢记在心的是,应用程序既有客户区,也有非客户区。
AdjustWindowRect函数首先用利用一个矩形定义左下角,右下角,左上角,右上角窗口区域的坐标。左上角的属性代表了窗口的起始位置,结合右下角则可以反应窗口的宽度和高度。AdjustWindowRect函数中也专门有一个布尔类型的标明窗口类型的变量,指示窗口是否拥有菜单栏,而有无菜单栏影响着非客户区。
下一步我们调用Win32函数CreateWindow来创建我们的窗口。在下表中我们讲解它的一个变体CreateWindowA。两者的主要的区别是CreateWindowA接受ANSI编码类型的字符串变量,而CreateWindows接受Unicode编码类型的字符串变量。若要使用Unicode类型,我们可以用L”XX”将我们的Unicode类型字符串括起来,而其中“XX”处填写Unicode字符串的内容。
Win32函数CreateWindowA的变量如下:
▲ lpClassName (可选) 窗口类名(和窗口类结构体一致)
▲ lpWindowName (可选) 窗口标题栏文本
▲ dwStyle 窗口类型标识
▲ X—窗口的水平位置。
▲ Y—窗口的竖直位置。
▲ nWidth—窗口宽度
▲ hHeight—窗口高度
▲ hWndParent (可选)—父窗口句柄的一个句柄 (若此新窗口为弹出窗口或者子窗口,本变量为为可选).
▲ hMenu (可选)—窗口菜单的资源句柄
▲ hInstance (可选)—应用程序实例ID (wWinMain的第一个变量).
▲ lpParam (optional)—通过窗口过程回调函数的lpParam参数,传递给窗口数据
(在窗口回调过程中深入讲解).
CreateWindow(A)的返回值是一个空句柄。如果CreateWindow(A)创建成功,我们可以调用Win32函数ShowWindow来显示窗口,ShowWindow()函数需要使用ShowWindow函数返回的窗口句柄,以及cmdShow参数(wWinMain的最后一个变量)。
当窗口被创建后,应用程序就能开始执行它的工作了。Win32 GUI应用程序是基于事件的应用程序。这就意味着当事件发生时,应用程序得到通知,然后进行相关的响应。
这样的消息响应机制持续到应用程序退出。例如,当Microsoft Word运行后,一个“创建(Create)”事件被激发,应用程序便开始加载。当用户点击工具栏上与菜单上等的按钮的时候,一个事件就被触发,发送到应用程序处进行处理。如果打开文件按钮的鼠标点击事件被触发,然后一个对话框会显示出来,使用户可以直观地选择需要打开的文件。许多应用程序都是基于事件而运行与工作的。
在电子游戏中,应用程序是实时的,这意味着很多事件与行为的发生与否并不是应用程序一直在执行很多任务的。若用户按下游戏控制器的按钮,通常会在游戏循环的更新步骤中被检测到,然后游戏程序进行对应的响应。如果没有事件发生,游戏程序依然会通过渲染当前的游戏状态而运行(例如,渲染菜单,电影,游戏地图等),并执行逻辑更新,查找和响应网络数据,进行音频播放等等。
无论是实时还是基于事件的程序,都会在启动运行后一直处于运行的状态,直到用户决定退出。在这里介绍应用程序循环的概念。应用程序循环我们解释为,是一个持续的无限的循环,直到用户进行了相关打破这种循环的操作。可以通过接受到一个WM_QUIT(Win32运用程序的退出消息)消息。在应用程序中点击“退出”来关闭该程序。(例如,在游戏运行时按下Esc键也许会出现一个暂停的画面,但不应该是退出,除非我们去那样设计),或者设计任何其他方式的退出。在此专栏系列的后续内容,我们将演示只有当用户按Esc键,或者单击窗口右上角的“X”关闭按钮的时候,应用程序才会退出。
这是一个我们之后中会用到的应用循环的例子
代码段3:Blank Win32 Window Demo书写步骤之三—— 应用循环
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCEprevInstance, LPWSTR cmdLine, int cmdShow ) { UNREFERENCED_PARAMETER( prevInstance ); UNREFERENCED_PARAMETER( cmdLine ); WNDCLASSEX wndClass ={ 0 }; wndClass.cbSize =sizeof( WNDCLASSEX ) ; wndClass.style = CS_HREDRAW| CS_VREDRAW; wndClass.lpfnWndProc =WndProc; wndClass.hInstance =hInstance; wndClass.hCursor =LoadCursor( NULL, IDC_ARROW ); wndClass.hbrBackground= ( HBRUSH )( COLOR_WINDOW + 1 ); wndClass.lpszMenuName= NULL; wndClass.lpszClassName= "DIRECTX11BookWindowClass"; if( !RegisterClassEx(&wndClass ) ) return -1; RECT rc = { 0, 0, 640,480 }; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE ); HWND hwnd =CreateWindowA( "DIRECTX11BookWindowClass", "Blank Win32Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom -rc.top, NULL, NULL, hInstance, NULL ); if( !hwnd ) return -1; ShowWindow( hwnd,cmdShow ); // 初始化 MSG msg = { 0 }; while( msg.message !=WM_QUIT ) { if( PeekMessage(&msg, 0, 0, 0, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else { // 进行更新 // 进行绘图操作 } } // 收尾工作 returnstatic_cast<int>( msg.wParam ); }
其中static_cast<>运算符是用于进行强制类型转换的C++运算。
代码讲解之三:窗口回调过程
这里就直接上代码吧。
代码段4:Blank Win32 Window Demo书写步骤之四:窗口回调过程
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam ) { PAINTSTRUCT paintStruct; HDC hDC; switch( message ) { case WM_PAINT: hDC = BeginPaint( hwnd, &paintStruct ); EndPaint( hwnd, &paintStruct ); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc( hwnd, message, wParam, lParam ); } return 0; }
窗口过程函数返回了LRESULT类型,运用了CALLBACK类型。函数本身可以被随意命名,我们在这里采用WndProc。
代码讲解之四:完整的源代码
将以上几个部分的讲解串联起来,加之细节上的修改,就得到了Blank Win32 Window Demo的完整源代码:
#include<Windows.h> //函数声明 LRESULTCALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); //****wWinMain函数,程序入口点函数************************************** intWINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance, LPWSTR cmdLine,int cmdShow ) { UNREFERENCED_PARAMETER( prevInstance ); UNREFERENCED_PARAMETER( cmdLine ); WNDCLASSEX wndClass = { 0 }; wndClass.cbSize = sizeof( WNDCLASSEX ) ; wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = WndProc; wndClass.hInstance = hInstance; wndClass.hCursor = LoadCursor( NULL, IDC_ARROW); wndClass.hbrBackground = ( HBRUSH )(COLOR_WINDOW + 1 ); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = "DIRECTX11BookWindowClass"; if( !RegisterClassEx( &wndClass ) ) return -1; RECT rc = { 0, 0, 640, 480 }; AdjustWindowRect( &rc,WS_OVERLAPPEDWINDOW, FALSE ); HWND hwnd = CreateWindowA( "DIRECTX11BookWindowClass","Blank Win32 Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL,hInstance, NULL ); if( !hwnd ) return -1; ShowWindow( hwnd, cmdShow ); // 初始化 MSG msg = { 0 }; while( msg.message != WM_QUIT ) { if( PeekMessage( &msg, 0, 0, 0,PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else { // 进行更新 // 进行绘图操作 } } // 收尾工作 return static_cast<int>( msg.wParam); } //****消息处理函数*********************************** LRESULTCALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { PAINTSTRUCT paintStruct; HDC hDC; switch( message ) { case WM_PAINT: hDC = BeginPaint( hwnd,&paintStruct ); EndPaint( hwnd, &paintStruct); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc( hwnd,message, wParam, lParam ); } return 0; }
对代码进行编译运行,就可以得到一个空白的窗口,如下图。这个Demo放在这里讲很有必要,可以作为我们之后讲解的程序的模板,后面我们会接着创建很多Demo,都是在这个最基础的Demo之上添加相关代码即可。
程序运行后得到如下窗口:
图7
本节笔记到这里就结束了。
本篇文章配套的源代码请点击这里下载: 【Visual C++】Note_Code_26
感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们。
【Visual C++】游戏开发 系列文章才刚刚展开一点而已,因为游戏世界实在是太博大精深了~
但我们不能着急,得慢慢打好基础。做学问最忌好高骛远,不是吗?
浅墨希望看到大家的留言,希望与大家共同交流,希望得到睿智的评论(即使是批评)。
你们的支持是我写下去的动力~
精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。
大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~
如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨相关的问题。
最后,谢谢你们一直的支持~~~
——————————浅墨于2012年6月24日