【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)


本系列文章由zhmxy555(毛星云)编写,转载请注明出处。

作者:毛星云(浅墨)    邮箱: happylifemxy@163.com


众所周知,GUI是游戏中不可缺少的元素,这篇文章中,我们首先了解了游戏GUI界面的知识与相关概念,然后一起设计了一个封装好GUI图形界面的C++类。这个类有着非常强的扩展性,使用也是极其方便,很适合二次开发。

先看一张实现的效果图吧:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)


其中的背景音乐,游戏图标和背景图片都出自育碧公司的招牌式大作《刺客信条》。

程序的窗口大小已经被浅墨调成了1366 x768,现阶段比较流行的笔记本分辨率尺寸(其实真的想吐槽这个奇葩万年不变的电脑屏幕分辨率,在如今的后PC时代。手机屏幕都开始对1920 x1080的分辨率不满足了。。。。)


嗯,开始正文吧。



一、UI和GUI的概述


首先我们看一下UI的比较正统的定义:用户界面(User Interface,简称UI,亦称使用者界面)是系统和用户之间进行交互和信息交换的媒介,它实现信息的内部形式与人类可以接受形式之间的转换。

用户界面是介于用户与硬件之间,为彼此之间交互沟通而设计的相关软件,使得用户能够方便有效地去操作硬件以达成双向之交互,完成所希望的工作,用户界面定义广泛,包含了人机交互与图形用户界面,凡参与人类与机械的信息交流的领域都存在着用户界面。

而GUI的正统定义是:图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受。

我们知道,在游戏中,能够和游戏玩家进行交互是至关重要的。这样的交流可以是文本形式的,视觉显示,声音信号等等。而对于视觉显示而言,绝大多数游戏毋庸置疑就是用图形用户界面(GUI)来和游戏玩家进行交互的。而在游戏过程中,依旧少不了GUI界面的出场,比如说状态显示界面(Head-up Display,HUD),HUD,这个界面提供一切和游戏有关的信息。用于提供玩家剩余任务时间,生命值,坐标位置,以及更多信息。


让我们欣赏一部分近期的3A游戏大作的UI界面美图吧:

《鬼泣5》:


【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

《英雄无敌6》:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

欣赏完了,我们继续开讲吧。





二、关于状态显示界面(HUD)



这一节里让我们一起科普HUD的概念。

我们知道,通常情况下在游戏中屏幕上会有很多的小的界面模块配上文字,为玩家提供了一些有用的信息,比如玩家剩余任务时间,生命值,魔法值,坐标位置等等更多的信息。而因为在玩家和游戏交互的过程中,这些界面是透过摄像机视角显示出来的,它们被称为HUD(heads-up-display)。在视频游戏中,HUD就是一类可以实时向玩家显示有用信息和符号的GUI,并且它和诸如游戏菜单这样的其他界面不同.HUD中通常没有按钮、编辑框或者适合玩家交互的内容,因为玩家往往都是被游戏本身的画面和内容所吸引,而不是去关注HUD。所以呢,HUD通常并不去使用可以交互的元素,而倾向于使用文本和图像来表示某些信息。所以,游戏选项和偏好设置这样的内容不是HUD的菜,它们直接交由系统菜单来完成。

下面我们看一看更加规范化的HUD的介绍。

HUDheads-up-display,硬着过来翻译就是“抬头显视设备”- -。这是一个从军事领域起源的技术,可以把一些重要的战术信息显示在正常观察方向的视野范围内,而同时又不会影响对于环境的注意,也不用总是转移视线去专门观察仪表板上的那些指针和数据。游戏借鉴了这个概念,把游戏相关的信息以类似HUD的方式显示在游戏画面上,让玩家可以随时了解那些最重要最直接相关的内容。当然玩家要获得游戏信息可以有别的方式,比如菜单。菜单有着专门的界面,可以容纳更大的信息量,但却不能和游戏画面同时出现。调出菜单意味着中断游戏流程,HUD则在提供必要的信息的同时完全避免了这个问题。

虽然菜单提供了大量的信息,然而对于处在*行动状态下的玩家而言,这些信息都不是立刻需要获取的。HUD只提供了最重要最基本的内容:当前场景的地图。切换到战斗状态下之后,HUD所提供的信息便会转变为那些只和战斗相关的部分。

记得最早的游戏pong在设计的时候就没有HUD的。对于这样一个简单的游戏而言,唯一对玩家有意义需要及时掌握的就是双方的比分,而最早版本的pong没有这个功能,玩家需要自己去记录比分。游戏设计者很快意识到了这个问题,HUD很快就被整合到了后来的游戏之中,并随着游戏的进化一起演变,完善。

如果被上面的这些书面语绕晕了没关系,说了这么多,一言以蔽之:菜单的目的是大而全,HUD的目的则是少而精。HUD主要注重的是实时向玩家显示有用信息和符号的GUI,和而系统菜单GUI可以给玩家提供更多更全的信息。

不同类型的游戏玩起来的重点不一样,HUD在提供的信息方面也有很大的差别。我们不妨按照游戏的类别,来看看各种游戏的HUD设计的模式和重点。


1.角色扮演类游戏

取决于游戏是否会在行动场景和战斗场景之间切换,角色扮演游戏的HUD设计会有所不同,关于行动的那一部分内容,比如地图和方向指示之类的信息可能会被单独分列出来。但总体上角色扮演游戏的HUD信息量基本是一致的:玩家的生命,魔法,行动力,状态(或者其它因游戏而异的内容)等等的数值,玩家可以一键接触到的物品魔法等等东西,如果物品和魔法的内容很复杂,那么一般都会把全面调节的功能交给菜单来完成。比如预设快捷键和菜单光标位置记忆功能就是这样的目的,把菜单中全面而细微的调节能力中,挑选出一些最重要的,放到HUD上来,共玩家选择使用。


2.格斗游戏

无论格斗游戏系统本身怎么进化,从2D变到3D,格斗游戏的HUD总是保持着自己一贯的特色。格斗游戏的HUD大多分成两个部分:第一,对战斗数据的统计,第二,对战斗中精彩场面的描述。前者就是那些显眼的血槽,还包括时间,局数计分。后者则是对于连击之类的精彩动作的积分等等信息。其中血槽这个东西是游戏HUD设计上一个非常典型的东西。


3.体育游戏

体育类游戏在设计HUD的问题上有着天然的两个参考坐标系:电视转播画面和球队的战术分析图。游戏的HUD结合了这两者,即体现出了电视转播画面的现场感,也做出了战术分析图那样的清晰感,让玩家尽可能的在有身临其境的感觉的同时也对比赛局面有着清晰的了解。实际上体育类游戏的HUD设计作的是如此的好,以至于最近以来,很多体育项目的电视转播画面开始学习这类游戏的HUD设计,往画面上添加一些即时的比赛信息和战术分析。


4.驾驶模拟类游戏

这类游戏所要模仿的对象就是HUD这个概念的来源的地方,所以驾驶模拟类游戏HUD设计的原则也就变得非常简单而直接:尽可能的去重现模拟对象的原始HUD就可以了。当然根据游戏模拟真实的程度不同,再现真实HUD设计的程度也有所区别。HUD的设计始终存在一个如何抽取最重要的信息提示玩家的问题,过于仿真的游戏HUD设计将大量的信息不加选择的堆在玩家面前。对于游戏本身模拟这个概念而言,是好事,但对于像通过这些游戏来体验现实生活中不可能接触到的东西这个目的而言,高度的仿真模拟往往会成为上手的障碍。游戏毕竟是游戏,游戏的HUD如何在仿真模拟和抽象表现上把握平衡,是这类游戏的一个突出问题。


5.动作射击类游戏

射击类游戏的HUD通常都包括了玩家的状态,玩家的武器状态,地图,以及目标指示这四个方面。第三人称的射击游戏或者团队策略类射击游戏在HUD设计上和传统的第一人称射击游戏区别也不是很大,重点同样在这些要素上。HUD的引入给所有的射击类游戏,无论游戏本身的背景设定是在什么样的时代,都带来了一定的科幻未来的要素。真实的战斗中,对于战场情况的把握从来都是很大的挑战,不断进步的单兵信息化装备正是力求解决这些问题。射击类游戏通过HUD的引入超前的解决了这个问题。就目前而言,感觉做的最好的射击类游戏HUD恰恰就是一些未来题材的射击游戏,等下在HUD进化中我们会看到例子。


6.策略类游戏

最为复杂的一类游戏HUD,事实上在这类游戏里面HUD和菜单往往难以截然区分。因为玩家几乎每时每刻都需要确实的掌握整个游戏空间里大量单位的行动。HUD是简化了的菜单,但在很多的策略类游戏上,简化只是一个美好的愿望,游戏本身的复杂程度和玩家的上帝视角导致了这类游戏必然随时都有大量的信息反馈和大量的命令等待输入。策略类游戏的HUD如此的复杂,以至于在控制器键位相对较少的游戏主机上,这类游戏的流行程度始终达不到PC上同类游戏的流行度。游戏本身机制导致的复杂HUD设计应该是这类游戏受众群体局限性产生的原因之一。

其它类型的游戏,比如平台游戏,益智游戏等等在HUD设计上感觉并没有什么特别的地方。它们的HUD只要能清晰的交代出游戏人物(如果有的话)的状态和游戏的进展程度(得分之类的信息)就可以了。

如果看到这里觉得累了,依旧是欣赏一部分近期的3A游戏大作的UI界面美图吧:


《孤岛危机3》:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)


《仙剑奇侠传五 前传》:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)


《刺客信条4黑旗》:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)




三、开始GUI系统的设计

在这篇文章里面,我们将一起去实现一个简单而健全的GUI系统。这个系统既有HUD的功能,也可以创建GUI菜单。依旧是和之前的其他系统一样,封装在一个类中,这次的类是D3DGUIClass。

下面又到了天马行空的设计时刻了——在实现GUI系统之前,让我们设计出心目中的GUI系统该有的功能。

1.大体说明

首先需要说明的是,我们这次设计的GUI系统主要用于演示之用,扩展性很强,需要的更多功能完全可以在这个GUI的系统上进行二次开发,来增强它的特性和功能。

GUI系统主要由按钮和静态文本控件对象组成,当然,还少不了背景图的显示。由于GUI系统是2D的,这意味着指定对象位置时,是用不到Z轴的。另外,我们决定使用正交投影的方式来渲染GUI,这样可以根据像素的位置来指定屏幕的位置。

让我们来回忆一下,(0,0)对应的是屏幕的左上角。在windows系统中,左上角就是(0,0)。有了这个信息,就可以轻松地确定玩家的鼠标指针是否悬停在GUI系统的控件之上,或者说正在点击这个控件。


2.关于布局规划和背景图

在设计和规划按钮和文本位置时,我们只要牢记窗口左上角的坐标是(0,0),就能随心所欲的创建各式各样的限制的系统范围内的心仪的GUI布局出来。对于背景图的话,其实没必要考虑它的位置,因为这个图像就是一个由两个三角形构成的全屏图像。只要系统知道当前程序的宽度和高度,就可以显示一个全屏纹理图。

3.关于按钮控件的实现

必不可少的特性就是可以点击的按钮控件,其实按钮控件的实现比点击图像更容易实现。由于按钮控件的位置是以像素来指定的,因此为了确定鼠标指针是否在按钮上或者按下了按钮,其实就是检查当前鼠标指针的位置是否落在按钮区域内。因为Windows操作系统用像素指定鼠标位置且屏幕的左上角为(0,0),所以检查鼠标的坐标是否处于按钮四个角的坐标范围内就可以了,即检查如下四个方面:

按钮的左侧坐标位置是否小于鼠标指针的X坐标,按钮右侧坐标位置是否大于鼠标指针的X坐标,按钮的上侧坐标是否小于鼠标指针的Y坐标,下侧坐标是否大于鼠标的Y坐标。如果四个都为真的话,那么就可以认定鼠标指针就在按钮控件之上了,然后便通过消息过程来检测是否按下的鼠标左键这个状态,如果按下了鼠标左键,那么就可以确定玩家不仅仅是把鼠标放在了按钮之上,而是同时点击了按钮。我们还在这个GUI系统中实现了通过鼠标的悬停和点击动作,来改变按钮的外观,在按下按钮后有一定的动画效果。

根据上面的描述,我们可以写出的实现代码如下:

其中pControl是一个自定义的GUICONTROL结构体的指针对象

                            //检查鼠标是否悬停或者点击了按钮                             if(mouseX> pControl->m_xPos && mouseX < pControl->m_xPos +pControl->m_width &&                                      mouseY> pControl->m_yPos && mouseY < pControl->m_yPos + pControl->m_height)                             {                                      if(LMBDown)status = UGP_BUTTON_DOWN;                                      elsestatus = UGP_BUTTON_OVER;                             }



4.类的框架设计

因为这个GUI系统的实现还有一定的细节需要详细说明,所以决定分两次更新来讲解。这次我们先把功能完整的类的实现结果给大家,并告诉大家如何使用,说明一下main函数相对于之前有哪些细节需要改变,然后GUI系统类的实现细节留待下篇讲解(如果需要额外用一次更新来讲解的话)。

还是把详细注释的头文件贴一下吧:


//==================================================== // Name: D3DGUIClass.h //      Des:一个游戏GUI界面系统类的头文件 // 2013年 11月17日  Create by 浅墨 //====================================================   #pragma once     // 所支持的控件类型宏 #define UGP_GUI_STATICTEXT                 1 #define UGP_GUI_BUTTON                     2 #define UGP_GUI_Background                 3   // 鼠标按键状态宏 #define UGP_BUTTON_UP                      1 #define UGP_BUTTON_OVER          	   2 #define UGP_BUTTON_DOWN                	   3   // 设置一些GUI中用到的控件ID #define STATIC_ID_1  1 #define STATIC_ID_2  2 #define BUTTON_ID_1  3 #define BUTTON_ID_2  4 #define BUTTON_ID_3  5 #define BUTTON_ID_4  6       // FVF灵活顶点类型的结构体 struct GUIVERTEX {          floatx, y, z, rhw;          unsignedlong color;          floattu, tv; }; #define D3DFVF_GUI (D3DFVF_XYZRHW |D3DFVF_DIFFUSE | D3DFVF_TEX1)     //控件属性结构体 struct GUICONTROL {          //操作类型,ID和颜色          intm_type;  //控件类型          intm_id;  //控件ID          unsignedlong m_color; //控件颜色          intm_listID;   //如果是文字的话,这个变量就表示它使用的字体,否则就表示顶点缓存          floatm_xPos, m_yPos;   //控件的起始位置          floatm_width, m_height;   // 控件的宽度和高度          wchar_t*m_text;   // 文字内容          LPDIRECT3DTEXTURE9m_Background;   // 控件背景的填充图像          LPDIRECT3DTEXTURE9m_upTex, m_downTex, m_overTex;   // 存放按钮弹起,按下和鼠标经过时的3张纹理图 };       class D3DGUIClass {   private:          LPDIRECT3DDEVICE9m_pd3dDevice;  //D3D设备对象              LPD3DXFONT*m_pFonts;  //D3D字体对象          GUICONTROL*m_pControls;  //控件对象          LPDIRECT3DVERTEXBUFFER9*m_pVertexBuffer;   //顶点缓存对象指针          GUICONTROLm_Background;  //背景图对象          LPDIRECT3DVERTEXBUFFER9m_BackgroundBuffer;   //背景图缓冲区对象            boolm_bIsBackgroundUsed;  //一个标识,用于标识是否已经用了背景          intm_nTotalFontNum;              //字体数目计数器          intm_nTotalControlNum;        //控件数目计数器          intm_nTotalBufferNum; //缓冲区数目计数器            intm_nWindowWidth;    //窗口宽度          intm_nWindowHeight;   //窗口高度       public:          D3DGUIClass(LPDIRECT3DDEVICE9device, int w, int h);          ~D3DGUIClass(){ ClearUp(); }            LPDIRECT3DDEVICE9GetD3dDevice() { return m_pd3dDevice; } //返回D3D设备对象的函数          GUICONTROL*GetBackground() { return &m_Background; } //返回背景的函数          LPDIRECT3DVERTEXBUFFER9GetBackgroundBuffer() { return m_BackgroundBuffer; }  //返回背景缓冲区对象的函数              intGetTotalFontNum() { return m_nTotalFontNum; } //返回所有字体数目的函数          intGetTotalControlNum() { return m_nTotalControlNum; }  //返回所有控件数目的函数          intGetTotalBufferNum() { return m_nTotalBufferNum; } //返回总的缓冲区数目的函数          intGetWindowWidth() { return m_nWindowWidth; } //返回窗口宽度的函数          intGetWindowHeight() { return m_nWindowHeight; } //返回窗口高度的函数          boolIsBackgroundUsed() { return m_bIsBackgroundUsed; }     //返回背景是否在使用的bool值的函数          voidSetWindowSize(int w, int h) { m_nWindowWidth = w; m_nWindowHeight = h; }  //设置窗口宽度和高度的函数            LPD3DXFONTGetFont(int id)  //返回字体ID函数          {                    if(id< 0 || id >= m_nTotalFontNum) return NULL;                    returnm_pFonts[id];          }            GUICONTROL*GetGUIControl(int id)  //返回GUI控件ID函数          {                    if(id< 0 || id >= m_nTotalControlNum) return NULL;                    return&m_pControls[id];          }            LPDIRECT3DVERTEXBUFFER9GetVertexBuffer(int id) //返回顶点缓存ID函数          {                    if(id< 0 || id >= m_nTotalBufferNum) return NULL;                    returnm_pVertexBuffer[id];          }              boolCreateTextFont(wchar_t *fontName, int size, int *fontID);  //字体创建函数          boolAddBackground(wchar_t *fileName);  //GUI背景添加函数          boolAddStaticText(int id, wchar_t *text, float x, float y, unsigned long color, intfontID); //添加静态文本函数          boolAddButton(int id, float x, float y, wchar_t *up, wchar_t *over, wchar_t *down);//添加按钮函数          voidClearUp( ); //资源清理函数     };   void ProcessGUI(D3DGUIClass *gui, boolLMBDown, int mouseX, int mouseY,          void(*funcPtr)(intid, int state));  //回调函数






具体的实现细节如果现在讲会有一定的篇幅,我们留待下次讲解吧。




四、GUI系统类的使用



接下来,一起来研究研究如果要在我们之前的demo框架里使用GUI系统的话,需要添加哪些代码。

第一步,依旧是添加一些必要的全局变量:

D3DGUIClass      g_gui= NULL;  		//创建GUI类对象 int 		 g_FontID = -1;      	//  GUI中字体对象的ID bool             g_LMBDown= false;      // GUI中的鼠标状态信息,鼠标左键是否按下的标识 int              g_MouseX= 0, g_MouseY = 0;   //存储鼠标坐标的两个变量




可以看到,在这里我们定义了自己写的GUI系统类D3DGUIClass的类对象,然后是鼠标状态信息相关的变量和一个字体对象。


第二步,在消息处理函数的switch中添加一些新的case:

//-----------------------------------【WndProc( )函数】-------------------------------------- //      描述:窗口过程函数WndProc,对窗口消息进行处理 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINTmessage, WPARAM wParam, LPARAM lParam )  //窗口过程函数WndProc {          switch(message )                               //switch语句开始          {          caseWM_PAINT:                                          // 客户区重绘消息                    Direct3D_Render(hwnd,0.0f);          //调用Direct3D_Render函数,进行画面的绘制                    ValidateRect(hwnd,NULL);   // 更新客户区的显示                    break;                                                                                 //跳出该switch语句            caseWM_KEYDOWN:                // 键盘按下消息                    if(wParam == VK_ESCAPE)    // ESC键                             DestroyWindow(hwnd);    // 销毁窗口, 并发送一条WM_DESTROY消息                    break;          caseWM_DESTROY:                                    //窗口销毁消息                    Direct3D_CleanUp();     //调用Direct3D_CleanUp函数,清理COM接口对象                    PostQuitMessage(0 );              //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息                    break;                                                     //跳出该switch语句            caseWM_KEYUP:                    if(wParam== VK_ESCAPE) PostQuitMessage(0);                    break;            caseWM_LBUTTONDOWN:                    g_LMBDown= true;                    break;            caseWM_LBUTTONUP:                    g_LMBDown= false;                    break;            caseWM_MOUSEMOVE:                    g_MouseX= LOWORD (lParam);                    g_MouseY= HIWORD (lParam);                    break;              default:                                                   //若上述case条件都不符合,则执行该default语句                    returnDefWindowProc( hwnd, message, wParam, lParam );                  //调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。          }            return0;                                       //正常退出 }



其实这一步就是在之前消息的基础上,添加了左键按下,左键弹起,和鼠标移动的消息响应。



第三步,在进行渲染资源准备的Object_Init( )函数中,添加载入GUI系统中的资源到内存中的相关代码:

//-----------------------------------【Object_Init()函数】-------------------------------------- //      描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化 //-------------------------------------------------------------------------------------------------- HRESULT Objects_Init() {          //创建字体          D3DXCreateFont(g_pd3dDevice,36, 0, 0, 1000, false, DEFAULT_CHARSET,                    OUT_DEFAULT_PRECIS,DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS);          D3DXCreateFont(g_pd3dDevice,20, 0, 1000, 0, false, DEFAULT_CHARSET,                    OUT_DEFAULT_PRECIS,DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName);          D3DXCreateFont(g_pd3dDevice,23, 0, 1000, 0, false, DEFAULT_CHARSET,                    OUT_DEFAULT_PRECIS,DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper);          D3DXCreateFont(g_pd3dDevice,26, 0, 1000, 0, false, DEFAULT_CHARSET,                    OUT_DEFAULT_PRECIS,DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor);              //设置纹理采样参数          g_pd3dDevice->SetSamplerState(0,D3DSAMP_MINFILTER, D3DTEXF_NONE);          g_pd3dDevice->SetSamplerState(0,D3DSAMP_MAGFILTER, D3DTEXF_NONE);          g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER, D3DTEXF_NONE);     //-----------------------------------【GUI系统相关代码】-------------------------------          //创建GUI系统          g_gui= new D3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT);          if(!g_gui)return false;            //添加背景图片          if(!g_gui->AddBackground(L"GameMedia\\Assassinscreed.jpg")) return false;            //添加字体          if(!g_gui->CreateTextFont(L"微软雅黑",28, &g_FontID)) return false;              //添加静态文本到GUI中          if(!g_gui->AddStaticText(STATIC_ID_1,L"Version 浅墨1.0版",                    1170,735, D3DCOLOR_XRGB(55,155,255), g_FontID)) return false;            if(!g_gui->AddStaticText(STATIC_ID_2,L"浅墨DirectX教程第三季之 打造游戏GUI界面",                    500,10, D3DCOLOR_XRGB(255,255,255), g_FontID)) return false;              //添加4个按钮,分别是开始游戏,载入进度,选项和退出游戏,每个按钮对应3幅图          if(!g_gui->AddButton(BUTTON_ID_1,650, 340, L"GameMedia\\startUp.png",                    L"GameMedia\\StartOver.png",L"GameMedia\\startDown.png")) return false;            if(!g_gui->AddButton(BUTTON_ID_2,650, 385, L"GameMedia\\loadUp.png",                    L"GameMedia\\loadOver.png",L"GameMedia\\loadDown.png")) return false;            if(!g_gui->AddButton(BUTTON_ID_3,650, 430, L"GameMedia\\optionsUp.png",                    L"GameMedia\\optionsOver.png",L"GameMedia\\optionsDown.png")) return false;            if(!g_gui->AddButton(BUTTON_ID_4,650, 475, L"GameMedia\\quitUp.png",                    L"GameMedia\\quitOver.png",L"GameMedia\\quitDown.png")) return false;            returnS_OK; }



代码中都注释的比较详细,就不多说明了哈。

第四步,添加一个GUI系统的回调函数:

void GUICallback(int id, int state) {          switch(id)          {          caseBUTTON_ID_1:                    //“开始游戏”按钮的相关代码实现                    break;          caseBUTTON_ID_2:                    //“载入游戏”按钮的相关代码实现                    break;          caseBUTTON_ID_3:                    //“选项”按钮的相关代码实现                    break;          caseBUTTON_ID_4:                    //如果点击了退出按钮,就退出程序                    if(state== UGP_BUTTON_DOWN) PostQuitMessage(0);                    break;          } }



这里是一个switch—case语句总领的回调函数。

大家也许暂时会对这个函数的使用不太理解,其实它和我们自定义的ProcessGUI有着千丝万缕的联系,具体内容我们在出GUI的第二篇文章的时候再详细讲解。这里我们只要知道它是和ProcessGUI搞基的就可以了。

举个例子吧,对其中的BUTTON_ID_1按钮,就是指的GUI界面中的“Start Game”按钮,而点击之后的余下响应代码(也就是消息响应代码就写在这个case之后,比如说点击了新游戏的开始后需要渲染游戏画面等等一系列代码。

第五步,在渲染五步曲的第三步中调用一个封装好功能的ProcessGUI函数就可以了。这个函数的具体实现我们在稍后退出的文章中会讲到。

 //--------------------------------------------------------------------------------------          //【Direct3D渲染五步曲之二】:开始绘制          //--------------------------------------------------------------------------------------          g_pd3dDevice->BeginScene();                     // 开始绘制            //--------------------------------------------------------------------------------------          //【Direct3D渲染五步曲之三】:正式绘制          //--------------------------------------------------------------------------------------              //处理和渲染GUI系统          ProcessGUI(g_gui,g_LMBDown, g_MouseX, g_MouseY, GUICallback);            //-----------------------------【绘制文字信息】-----------------------------          HelpText_Render(hwnd);              //--------------------------------------------------------------------------------------          //【Direct3D渲染五步曲之四】:结束绘制          //--------------------------------------------------------------------------------------          g_pd3dDevice->EndScene();                       // 结束绘制







五、详细注释的源代码欣赏


这次的工程因为是作为GUI的初步演示,自然就有了孑然一身的感觉,除了main.cpp和D3DUtil.h就是D3DGUIClass类的源文件和头文件了。如下图:


【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

程序主要是实现了简单的GUI系统,通过GUI系统的功能在屏幕上添加了游戏菜单的四个按钮,并且在屏幕上输出了“浅墨DirectX教程第三季 之 打造游戏GUI界面”,"Version 浅墨1.0版"这两段文字。

而且目前对“quit”退出按钮的功能进行了实行实现,其实就简单一句if(state == UGP_BUTTON_DOWN) PostQuitMessage(0);。。。。。


那么,老规矩,上程序的核心部分,main函数的代码吧:

//-----------------------------------【程序说明】---------------------------------------------- // 【Visual C++】游戏开发系列配套源码五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一) // VS2010版 // 2013年11月 Create by 浅墨 // 背景音乐素材出处: 刺客信条 //------------------------------------------------------------------------------------------------   //-----------------------------------【宏定义部分】-------------------------------------------- // 描述:定义一些辅助宏 //------------------------------------------------------------------------------------------------ #define WINDOW_WIDTH	1366						//为窗口宽度定义的宏,以方便在此处修改窗口宽度 #define WINDOW_HEIGHT	768							//为窗口高度定义的宏,以方便在此处修改窗口高度 #define WINDOW_TITLE	 _T("【致我们永不熄灭的游戏开发梦想】 浅墨DirectX教程二十三 打造游戏GUI界面(一)博文配套示例程序 by浅墨") //为窗口标题定义的宏    //-----------------------------------【头文件包含部分】--------------------------------------- //	描述:包含程序所依赖的头文件 //------------------------------------------------------------------------------------------------                                                                                       #include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include <time.h>  #include "D3DUtil.h" #include "D3DGUIClass.h"    //-----------------------------------【库文件包含部分】--------------------------------------- //	描述:包含程序所依赖的库文件 //------------------------------------------------------------------------------------------------   #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") #pragma comment(lib, "dinput8.lib")     // 使用DirectInput必须包含的库文件,注意这里有8 #pragma comment(lib,"dxguid.lib") #pragma comment(lib, "winmm.lib")     // 地板的顶点结构 struct CUSTOMVERTEX { 	FLOAT _x, _y, _z; 	FLOAT _u, _v ; 	CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v) 		: _x(x), _y(y), _z(z), _u(u), _v(v) {} }; #define D3DFVF_CUSTOMVERTEX  (D3DFVF_XYZ | D3DFVF_TEX1)   //-----------------------------------【全局变量声明部分】------------------------------------- //	描述:全局变量的声明 //------------------------------------------------------------------------------------------------ LPDIRECT3DDEVICE9					g_pd3dDevice = NULL;				//Direct3D设备对象 LPD3DXFONT								g_pTextFPS =NULL;    //字体COM接口 LPD3DXFONT								g_pTextAdaperName = NULL;  // 显卡信息的2D文本 LPD3DXFONT								g_pTextHelper = NULL;  // 帮助信息的2D文本 LPD3DXFONT								g_pTextInfor= NULL;  // 绘制信息的2D文本 float												g_FPS= 0.0f;       //一个浮点型的变量,代表帧速率 wchar_t										g_strFPS[50] ={0};    //包含帧速率的字符数组 wchar_t										g_strAdapterName[60] ={0};   //包含显卡名称的字符数组  D3DGUIClass								*g_gui = NULL;  //创建GUI类对象 bool												g_LMBDown = false;      // GUI中的鼠标状态信息,鼠标左键是否按下的标识 int												g_MouseX = 0, g_MouseY = 0;      //存储鼠标坐标的两个变量 int g_FontID = -1;						//  GUI中字体对象的ID   //-----------------------------------【全局函数声明部分】------------------------------------- //	描述:全局函数声明,防止“未声明的标识”系列错误 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK		WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULT						Direct3D_Init(HWND hwnd,HINSTANCE hInstance); HRESULT						Objects_Init(); void								Direct3D_Render( HWND hwnd,FLOAT fTimeDelta); void								Direct3D_Update( HWND hwnd,FLOAT fTimeDelta); void								Direct3D_CleanUp( ); float								Get_FPS(); void								HelpText_Render(HWND hwnd); void								GUICallback(int id, int state);  //-----------------------------------【WinMain( )函数】-------------------------------------- //	描述:Windows应用程序的入口函数,我们的程序从这里开始 //------------------------------------------------------------------------------------------------ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) {  	//开始设计一个完整的窗口类 	WNDCLASSEX wndClass={0} ;				//用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化     	wndClass.cbSize = sizeof( WNDCLASSEX ) ;	//设置结构体的字节数大小 	wndClass.style = CS_HREDRAW | CS_VREDRAW;	//设置窗口的样式 	wndClass.lpfnWndProc = WndProc;				//设置指向窗口过程函数的指针 	wndClass.cbClsExtra		= 0; 	wndClass.cbWndExtra		= 0; 	wndClass.hInstance = hInstance;				//指定包含窗口过程的程序的实例句柄。 	wndClass.hIcon=(HICON)::LoadImage(NULL,_T("GameMedia\\icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标 	wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );    //指定窗口类的光标句柄。 	wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);  //为hbrBackground成员指定一个灰色画刷句柄 	wndClass.lpszMenuName = NULL;						//用一个以空终止的字符串,指定菜单资源的名字。 	wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");		//用一个以空终止的字符串,指定窗口类的名字。  	if( !RegisterClassEx( &wndClass ) )				//设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 		return -1;		  	HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,			//喜闻乐见的创建窗口函数CreateWindow 		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, 		WINDOW_HEIGHT, NULL, NULL, hInstance, NULL );   	//Direct3D资源的初始化,调用失败用messagebox予以显示 	if (!(S_OK==Direct3D_Init (hwnd,hInstance))) 	{ 		MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口  	} 	PlaySound(L"GameMedia\\Heart - 刺客信条.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);   //循环播放背景音乐  	MoveWindow(hwnd,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,true);   //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,10)处 	ShowWindow( hwnd, nShowCmd );    //调用Win32函数ShowWindow来显示窗口 	UpdateWindow(hwnd);  //对窗口进行更新,就像我们买了新房子要装修一样   	//消息循环过程 	MSG msg = { 0 };  //初始化msg 	while( msg.message != WM_QUIT )			//使用while循环 	{ 		static FLOAT fLastTime  = (float)::timeGetTime(); 		static FLOAT fCurrTime  = (float)::timeGetTime(); 		static FLOAT fTimeDelta = 0.0f; 		fCurrTime  = (float)::timeGetTime(); 		fTimeDelta = (fCurrTime - fLastTime) / 1000.0f; 		fLastTime  = fCurrTime;  		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。 		{ 			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息 			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。 		} 		else 		{ 			Direct3D_Update(hwnd,fTimeDelta);         //调用更新函数,进行画面的更新 			Direct3D_Render(hwnd,fTimeDelta);			//调用渲染函数,进行画面的渲染			 		} 	}  	UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance); 	return 0;   }   //-----------------------------------【WndProc( )函数】-------------------------------------- //	描述:窗口过程函数WndProc,对窗口消息进行处理 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口过程函数WndProc { 	switch( message )				//switch语句开始 	{ 	case WM_PAINT:					 // 客户区重绘消息 		Direct3D_Render(hwnd,0.0f);          //调用Direct3D_Render函数,进行画面的绘制 		ValidateRect(hwnd, NULL);   // 更新客户区的显示 		break;									//跳出该switch语句  	case WM_KEYDOWN:                // 键盘按下消息 		if (wParam == VK_ESCAPE)    // ESC键 			DestroyWindow(hwnd);    // 销毁窗口, 并发送一条WM_DESTROY消息 		break; 	case WM_DESTROY:				//窗口销毁消息 		Direct3D_CleanUp();     //调用Direct3D_CleanUp函数,清理COM接口对象 		PostQuitMessage( 0 );		//向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 		break;						//跳出该switch语句  	case WM_KEYUP: 		if(wParam == VK_ESCAPE) PostQuitMessage(0); 		break;  	case WM_LBUTTONDOWN: 		g_LMBDown = true; 		break;  	case WM_LBUTTONUP: 		g_LMBDown = false; 		break;  	case WM_MOUSEMOVE: 		g_MouseX = LOWORD (lParam); 		g_MouseY = HIWORD (lParam); 		break;   	default:						//若上述case条件都不符合,则执行该default语句 		return DefWindowProc( hwnd, message, wParam, lParam );		//调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。 	}  	return 0;					//正常退出 }   //-----------------------------------【Direct3D_Init( )函数】---------------------------------- //	描述:Direct3D初始化函数,进行Direct3D的初始化 //------------------------------------------------------------------------------------------------ HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance) {  	//-------------------------------------------------------------------------------------- 	// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象 	//-------------------------------------------------------------------------------------- 	LPDIRECT3D9  pD3D = NULL; //Direct3D接口对象的创建 	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 		return E_FAIL;  	//-------------------------------------------------------------------------------------- 	// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息 	//-------------------------------------------------------------------------------------- 	D3DCAPS9 caps; int vp = 0; 	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) 	{ 		return E_FAIL; 	} 	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) 		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;   //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的 	else 		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算  	//-------------------------------------------------------------------------------------- 	// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体 	//-------------------------------------------------------------------------------------- 	D3DPRESENT_PARAMETERS d3dpp;  	ZeroMemory(&d3dpp, sizeof(d3dpp)); 	d3dpp.BackBufferWidth            = WINDOW_WIDTH; 	d3dpp.BackBufferHeight           = WINDOW_HEIGHT; 	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8; 	d3dpp.BackBufferCount            = 2; 	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE; 	d3dpp.MultiSampleQuality         = 0; 	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD;  	d3dpp.hDeviceWindow              = hwnd; 	d3dpp.Windowed                   = true; 	d3dpp.EnableAutoDepthStencil     = true;  	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8; 	d3dpp.Flags                      = 0; 	d3dpp.FullScreen_RefreshRateInHz = 0; 	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;  	//-------------------------------------------------------------------------------------- 	// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口 	//-------------------------------------------------------------------------------------- 	if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,  		hwnd, vp, &d3dpp, &g_pd3dDevice))) 		return E_FAIL;   	//获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串 	wchar_t TempName[60]=L"当前显卡型号:";   //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中 	D3DADAPTER_IDENTIFIER9 Adapter;  //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息 	pD3D->GetAdapterIdentifier(0,0,&Adapter);//调用GetAdapterIdentifier,获取显卡信息 	int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型 	MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了 	wcscat_s(TempName,g_strAdapterName);//把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中 	wcscpy_s(g_strAdapterName,TempName);//把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~  	if(!(S_OK==Objects_Init())) return E_FAIL;  	SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉  		return S_OK; }   //-----------------------------------【Object_Init( )函数】-------------------------------------- //	描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化 //-------------------------------------------------------------------------------------------------- HRESULT Objects_Init() { 	//创建字体 	D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET,  		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS); 	D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET,  		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName);  	D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET,  		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper);  	D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET,  		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor);    	//设置纹理采样参数 	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_NONE); 	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_NONE); 	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);   //-----------------------------------【GUI系统相关代码】------------------------------- 	// 创建GUI系统 	g_gui = new D3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT); 	if(!g_gui) return false;  	// 添加背景图片 	if(!g_gui->AddBackground(L"GameMedia\\Assassins creed.jpg")) return false;  	// 添加字体 	if(!g_gui->CreateTextFont(L"微软雅黑", 28, &g_FontID)) return false;   	// 添加静态文本到GUI中 	if(!g_gui->AddStaticText(STATIC_ID_1, L"Version 浅墨1.0版", 		1170, 735, D3DCOLOR_XRGB(55,155,255), g_FontID)) return false;  	if(!g_gui->AddStaticText(STATIC_ID_2, L"浅墨DirectX教程第三季 之 打造游戏GUI界面", 		500, 10, D3DCOLOR_XRGB(255,255,255), g_FontID)) return false;   	// 添加4个按钮,分别是开始游戏,载入进度,选项和退出游戏,每个按钮对应3幅图 	if(!g_gui->AddButton(BUTTON_ID_1, 650, 340, L"GameMedia\\startUp.png", 		L"GameMedia\\StartOver.png", L"GameMedia\\startDown.png")) return false;  	if(!g_gui->AddButton(BUTTON_ID_2, 650, 385, L"GameMedia\\loadUp.png", 		L"GameMedia\\loadOver.png", L"GameMedia\\loadDown.png")) return false;  	if(!g_gui->AddButton(BUTTON_ID_3, 650, 430, L"GameMedia\\optionsUp.png", 		L"GameMedia\\optionsOver.png", L"GameMedia\\optionsDown.png")) return false;  	if(!g_gui->AddButton(BUTTON_ID_4, 650, 475, L"GameMedia\\quitUp.png", 		L"GameMedia\\quitOver.png", L"GameMedia\\quitDown.png")) return false;  	return S_OK; }   void GUICallback(int id, int state) { 	switch(id) 	{ 	case BUTTON_ID_1:  		//“开始游戏”按钮的相关代码实现 		break; 	case BUTTON_ID_2: 		//“载入游戏”按钮的相关代码实现 		break; 	case BUTTON_ID_3: 		//“选项”按钮的相关代码实现 		break; 	case BUTTON_ID_4: 		//如果点击了退出按钮,就退出程序 		if(state == UGP_BUTTON_DOWN) PostQuitMessage(0); 		break; 	} }   //-----------------------------------【Direct3D_Update( )函数】-------------------------------- //	描述:不是即时渲染代码但是需要即时调用的,如按键后的坐标的更改,都放在这里 //-------------------------------------------------------------------------------------------------- void	Direct3D_Update( HWND hwnd,FLOAT fTimeDelta) { 	//GUI的实现暂时不需要在这里写代码 }    //-----------------------------------【Direct3D_Render( )函数】------------------------------- //	描述:使用Direct3D进行渲染 //-------------------------------------------------------------------------------------------------- void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta) { 	//-------------------------------------------------------------------------------------- 	// 【Direct3D渲染五步曲之一】:清屏操作 	//-------------------------------------------------------------------------------------- 	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 255, 255), 1.0f, 0);  	//-------------------------------------------------------------------------------------- 	// 【Direct3D渲染五步曲之二】:开始绘制 	//-------------------------------------------------------------------------------------- 	g_pd3dDevice->BeginScene();                     // 开始绘制  	//-------------------------------------------------------------------------------------- 	// 【Direct3D渲染五步曲之三】:正式绘制 	//--------------------------------------------------------------------------------------   	// 处理和渲染GUI系统 	ProcessGUI(g_gui, g_LMBDown, g_MouseX, g_MouseY, GUICallback);  	//-----------------------------【绘制文字信息】----------------------------- 	HelpText_Render(hwnd);   	//-------------------------------------------------------------------------------------- 	// 【Direct3D渲染五步曲之四】:结束绘制 	//-------------------------------------------------------------------------------------- 	g_pd3dDevice->EndScene();                       // 结束绘制 	//-------------------------------------------------------------------------------------- 	// 【Direct3D渲染五步曲之五】:显示翻转 	//-------------------------------------------------------------------------------------- 	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示  }   //-----------------------------------【HelpText_Render( )函数】------------------------------- //	描述:封装了帮助信息的函数 //-------------------------------------------------------------------------------------------------- void HelpText_Render(HWND hwnd) { 	//定义一个矩形,用于获取主窗口矩形 	RECT formatRect; 	GetClientRect(hwnd, &formatRect);  	//在窗口右上角处,显示每秒帧数 	formatRect.top = 5; 	int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() ); 	g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255));  	//显示显卡类型名 	g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect,  		DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f)); }   //-----------------------------------【Get_FPS( )函数】------------------------------------------ //	描述:用于计算每秒帧速率的一个函数 //-------------------------------------------------------------------------------------------------- float Get_FPS() {  	//定义四个静态变量 	static float  fps = 0; //我们需要计算的FPS值 	static int    frameCount = 0;//帧数 	static float  currentTime =0.0f;//当前时间 	static float  lastTime = 0.0f;//持续时间  	frameCount++;//每调用一次Get_FPS()函数,帧数自增1 	currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间  	//如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零 	if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟 	{ 		fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值 		lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间 		frameCount    = 0;//将本次帧数frameCount值清零 	}  	return fps; }    //-----------------------------------【Direct3D_CleanUp( )函数】-------------------------------- //	描述:对Direct3D的资源进行清理,释放COM接口对象 //--------------------------------------------------------------------------------------------------- void Direct3D_CleanUp() { 	//释放COM接口对象 	SAFE_RELEASE(g_pd3dDevice); 	SAFE_RELEASE(g_pTextFPS) 	SAFE_RELEASE(g_pd3dDevice) 	SAFE_DELETE(g_gui) }   



那么,最后一起看看运行截图吧:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

鼠标悬停在options按钮之上,可以发现按钮“下陷”了

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

点击quit按钮,游戏程序便会退出:

【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

嗯,本篇文章到这里就基本结束了,最后放出本篇文章配套示例程序的下载地址。


本篇文章的配套源代码请点击这里下载:



【浅墨DirectX提高班】配套源代码之二十三下载 (CSDN下载频道)


【浅墨DirectX提高班】配套源代码之二十三下载 (百度云盘)



文章最后,依旧是送大家一些正能量,不过今天有所不同,它们是:

Seven Things I Would Really Tresure In Life(生命中我将珍视的七个习惯):

1) 对某件事(某种物)持久的热爱和坚持。


2)早晨起来写下今天要做的三件事,晚上入睡前能真的将它们做完。


3)永远对未知和未见的充满着好奇,当它们出现时毫不犹豫地去拥抱它们。


4)并不因他人的非议或外界的阻力而改变对自己的看法的坚持,当要*使违背心愿去做某事时坚定不移地说"不"。


5)活在当下,珍视已有的一切,someday you will miss today.


6)对和自己完全无关的人,仅因其所处的境遇而产生同情与关心,并尽己所能去帮助。


7)对一个更好的世界和一个更好的自己的向往与不放下的执念。


是的,塑造自己的过程会很疼,但是最终你能收获一个更好的自己。加油:)


【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)


下周一,让我们离游戏开发的梦想更近一步。

下周一,游戏开发笔记,我们,不见不散。