Visual C++游戏开发五十七 浅墨DirectX教程二十四 打造游戏GUI界面

Posted 浅墨_毛星云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Visual C++游戏开发五十七 浅墨DirectX教程二十四 打造游戏GUI界面相关的知识,希望对你有一定的参考价值。



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

文章链接: http://blog.csdn.net/poem_qianmo/article/details/16922703

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



hello,我们又如约相见了。:)

上一讲中我们已经实现了一个简单的GUI系统,显示出了游戏的GUI主菜单页面,而本篇文章的主要目的是在之前GUI系统的基础上,实现GUI中多个页面间的切换和返回,更具有实用意义。


首先依然是放出截图吧,首先是主菜单页面:


 

【开始新游戏】界面:

 

【载入游戏】界面:

 


【选项】界面:

 


嗯,做出来的效果还是可圈可点的。


为了更好理解的这篇文章的内容,推荐大家先回去稍微瞄一下之前的那篇文章,这里贴心的给出传送门:



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


那么,下面我们就继续开始我们的革命吧,首先完成之前遗留下来的任务,讲一下我们GUI系统的心脏——ProcessGUI。







一、核心函数ProcessGUI的讲解


 

 

这个传说中的ProcessGUI函数可谓是我们GUI系统中的最重要的一个函数,它渲染了整个GUI系统,同样还为控件调用了回调函数。它参数包括需要处理的GUI,表明鼠标左键是否被按下的布尔值,然后是以像素为单位的鼠标指针位置,以及指向处理控件时要用到的通用回调函数的指针。另外我们知道,一般而言对于回调函数的话,需要两个参数,一个是控件的ID,另一个就是控件的状态。根据描述,函数原型就跃然纸上了:

void ProcessGUI(D3DGUIClass *gui, boolLMBDown, int mouseX, int mouseY, void(*funcPtr)(int id, int state)):


函数体写法方面的话,首先我们获取了D3D对象,渲染了背景图。因为顾名思义,他是背景图,就应该显示在各控件的最下方,所以我们需要在绘制其他控件之前绘制它,这样就可以在它之上随心所欲的绘制其他控件了。第一部分的代码写起来就是这样:

 

  if(!gui)return;
 
         LPDIRECT3DDEVICE9device = gui->GetD3dDevice();
         if(!device)return;
 
         //绘制背景
         GUICONTROL*Background = gui->GetBackground();
         LPDIRECT3DVERTEXBUFFER9bdBuffer = gui->GetBackgroundBuffer();
 
         //已经创建出的东西才绘制,所以来个if
         if(gui->IsBackgroundUsed()&& Background && bdBuffer)
         
                   device->SetTexture(0,Background->m_Background);
                   device->SetStreamSource(0,bdBuffer, 0, sizeof(GUIVERTEX));
                   device->SetFVF(D3DFVF_GUI);
                   device->DrawPrimitive(D3DPT_TRIANGLESTRIP,0, 2);
                   device->SetTexture(0,NULL);
         

因为我们GUI系统目前就只和二维空间打交道,那么在渲染GUI时,可以避免考虑深度测试这方面的问题。函数接下来要做的就是循环控件列表,并按照每个控件的类型对其加以渲染。在循环语句中有一个switch语句,它是用来检查正在处理的控件类型的。若是静态文本控件,就获取这个文本使用的D3D字体对象,设置一下文本位置,并且调用DrawText来显示文本。那么代码写起来就是这样:

//用来显示文本的对象
         LPD3DXFONTpFont = NULL;
         RECTfontPosition = 0, 0, (long)gui->GetWindowWidth(),
                   (long)gui->GetWindowHeight();
 
         //创建一个顶点缓存对象用于按钮的渲染
         LPDIRECT3DVERTEXBUFFER9pBuffer = NULL;
         intstatus = UGP_BUTTON_UP;
 
         //一个循环,用于各种控件的渲染
         for(inti = 0; i < gui->GetTotalControlNum(); i++)
         
                   //获取当前控件
                   GUICONTROL*pControl = gui->GetGUIControl(i);
                   if(!pControl)continue;
 
                   //根据不同的类型做不同的操作
                   switch(pControl->m_type)
                   
                   caseUGP_GUI_STATICTEXT:
                            //这种情况下获取字体对象
                            pFont= gui->GetFont(pControl->m_listID);
                            if(!pFont)continue;
 
                            //设置文字位置
                            fontPosition.left= pControl->m_xPos;
                            fontPosition.top= pControl->m_yPos;
 
                            //显示文字
                            pFont->DrawText(NULL,pControl->m_text, -1, &fontPosition,
                                     DT_LEFT,pControl->m_color);
                            break;


处理按钮的话,工作量就稍微大一点了。首先我们得到当前按钮使用的顶点缓存指针,这就和获取静态文本控件使用的D3D字体对象指针差不多。然后呢,启用透明混合处理功能,这样按钮可以有透明背景,可以为按钮增添些许效果,在设计按钮图像时就可以有更多的选择。接下来就顺势设置一下按钮状态。有了按钮状态的话,就可以确定要绑定的纹理了,我们使用if语句测试鼠标指针的位置是否落在按钮像素内,如果按钮的四个顶点的位置把鼠标指针的位置包含在其中了,那么就可以知道鼠标在按钮的区域里面。

若鼠标指针落在按钮区域中并且鼠标左键被按下,我们就知道玩家正在单击按钮,就把按键状态设成UGP_BUTTON_DOWN。如果这都不满足的话,那么玩家就只是把鼠标指针放在了按钮之上,并没了点击按钮,就else设成UGP_BUTTON_OVER。根据我们的思路,代码就这样写:

 

		case UGP_GUI_BUTTON:
                            status= UGP_BUTTON_UP;
 
                            //获取按钮所对应的顶点缓存对象
                            pBuffer= gui->GetVertexBuffer(pControl->m_listID);
                            if(!pBuffer)continue;
 
                            //设置纹理的alpha透明选项
                            device->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
                            device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
                            device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
 
                            //检查鼠标是否悬停或者点击了按钮
                            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;
                            

接着就是根据不同的鼠标和按钮之间缠绵悱恻的状态来准备不同的纹理图绑定上去了:

if(status== UGP_BUTTON_UP) device->SetTexture(0, pControl->m_upTex);
if(status== UGP_BUTTON_OVER) device->SetTexture(0, pControl->m_overTex);
if(status== UGP_BUTTON_DOWN) device->SetTexture(0, pControl->m_downTex);                        


一切准备就绪,然后开始麻溜的进行渲染,关闭上alpha混合.。

   

 			//万事俱备,开始渲染按钮
                            device->SetStreamSource(0,pBuffer, 0, sizeof(GUIVERTEX));
                            device->SetFVF(D3DFVF_GUI);
                            device->DrawPrimitive(D3DPT_TRIANGLESTRIP,0, 2);
 
                            //关闭alpha混合.
                            device->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
                            break;
                   

  

当然,最后我们不能忘了调用回调函数处理控件消息,也就是写上这一句:

		//调用回调函数处理控件消息
		if(funcPtr) funcPtr(pControl->m_id, status);
	

            

而为了给大家一个整体的概念,这里我们贴上这个函数的完整写法:

//-----------------------------------------------------------------------------
// Name:ProcessGUI
// Desc: 全局的函数,封装渲染整个GUI系统,同样还为控件调用回调函数
//-----------------------------------------------------------------------------
void ProcessGUI(D3DGUIClass *gui, bool LMBDown, int mouseX, int mouseY, void(*funcPtr)(int id, int state))

	if(!gui) return;

	LPDIRECT3DDEVICE9 device = gui->GetD3dDevice();
	if(!device) return;

	// 绘制背景
	GUICONTROL *Background = gui->GetBackground();
	LPDIRECT3DVERTEXBUFFER9 bdBuffer = gui->GetBackgroundBuffer();

	//已经创建出的东西才绘制,所以来个if
	if(gui->IsBackgroundUsed() && Background && bdBuffer)
	
		device->SetTexture(0, Background->m_Background);
		device->SetStreamSource(0, bdBuffer, 0, sizeof(GUIVERTEX));
		device->SetFVF(D3DFVF_GUI);
		device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
		device->SetTexture(0, NULL);
	

	//用来显示文本的对象
	LPD3DXFONT pFont = NULL;
	RECT fontPosition = 0, 0, (long)gui->GetWindowWidth(),
		(long)gui->GetWindowHeight();

	// 创建一个顶点缓存对象用于按钮的渲染
	LPDIRECT3DVERTEXBUFFER9 pBuffer = NULL;
	int status = UGP_BUTTON_UP;

	// 一个循环,用于各种控件的渲染
	for(int i = 0; i < gui->GetTotalControlNum(); i++)
	
		// 获取当前控件
		GUICONTROL *pControl = gui->GetGUIControl(i);
		if(!pControl) continue;

		// 根据不同的类型做不同的操作
		switch(pControl->m_type)
		
		case UGP_GUI_STATICTEXT:
			// 这种情况下获取字体对象
			pFont = gui->GetFont(pControl->m_listID);
			if(!pFont) continue;

			// 设置文字位置
			fontPosition.left = pControl->m_xPos;
			fontPosition.top = pControl->m_yPos;

			// 显示文字
			pFont->DrawText(NULL, pControl->m_text, -1, &fontPosition,
				DT_LEFT, pControl->m_color);
			break;

		case UGP_GUI_BUTTON:
			status = UGP_BUTTON_UP;

			//获取按钮所对应的顶点缓存对象
			pBuffer = gui->GetVertexBuffer(pControl->m_listID);
			if(!pBuffer) continue;

			// 设置纹理的alpha透明选项
			device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
			device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

			//检查鼠标是否悬停或者点击了按钮
			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;
				else status = UGP_BUTTON_OVER;
			

			//根据不同的鼠标和按钮之间缠绵悱恻的状态来准备不同的纹理图 
			if(status == UGP_BUTTON_UP) device->SetTexture(0, pControl->m_upTex);
			if(status == UGP_BUTTON_OVER) device->SetTexture(0, pControl->m_overTex);
			if(status == UGP_BUTTON_DOWN) device->SetTexture(0, pControl->m_downTex);

			// 万事俱备,开始渲染按钮
			device->SetStreamSource(0, pBuffer, 0, sizeof(GUIVERTEX));
			device->SetFVF(D3DFVF_GUI);
			device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

			// 关闭alpha混合.
			device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
			break;
		

		//调用回调函数处理控件消息
		if(funcPtr) funcPtr(pControl->m_id, status);
	


 这个看起来大块头的函数被我们这样一肢解,其实就很简单了。

接着,就来看看如何一步一步实现上面的多页面间的切换效果吧。


 






二、实现GUI系统的多页面间切换





上一节中我们自己设计和实现出了一个GUI系统的类D3DGUIClass,然后在实际应用中定义了一个类对象,就可以很轻松的实现游戏主窗口页面的显示。

那么,如果想实现GUI系统的多页面间切换,一种比较容易想到的做法就是,定义多个D3DGUIClass类对象,来分别代表多个GUI页面。

现在就开始把想法付诸实践吧。依然是一个五步曲:


第一步,依旧是老规矩,很多宏的声明,在D3DGUIClass.h头文件中:

 

// 菜单页面的宏定义
#define GUI_MAIN_SCREEN       1
#define GUI_START_SCREEN      2
#define GUI_LOAD_SCREEN       3
#define GUI_OPTION_SCREEN    4
 
// 设置一些GUI中用到的控件ID
#define STATIC_TEXT_ID     1
#define BUTTON_START_ID    2
#define BUTTON_LOAD_ID     3
#define BUTTON_OPTION_ID   4
#define BUTTON_QUIT_ID     5
#define BUTTON_BACK_ID     6
#define BUTTON_LEVEL_1_ID  7

第二步,顺势在main.cpp中添加一些全局变量:

//创建四个GUI类对象,分别代表四个页面
D3DGUIClass            *g_MainGUI= NULL;//主窗口
D3DGUIClass            *g_StartGUI= NULL; //游戏开始窗口
D3DGUIClass            *g_LoadGUI= NULL; //游戏载入窗口
D3DGUIClass            *g_OptionGUI= NULL; //游戏设置窗口
 
int                    g_MainGUIFontID= -1;                  //  GUI中字体对象的ID
int                    g_StartGUIFontID= -1;                 //  GUI中字体对象的ID
int                    g_LoadGUIFontID= -1;                  //  GUI中字体对象的ID
int                    g_OptionGUIFontID= -1;                //  GUI中字体对象的ID
int                    g_currentGUI= GUI_MAIN_SCREEN;    //一个当前的GUI标识


第三步,在进行渲染资源准备的Object_Init( )函数中,添加载入GUI系统中的资源到内存中的相关代码。这里的代码最好分为几个部分来写,便会显得思路清晰。

 

首先,创建出这些GUI系统的页面,然后给四个页面分别添加背景图,接着分别给四个页面添加字体

 

//--------------------------------------------------【GUI系统相关代码】-------------------------------------------------------
 
       // 创建一些GUI系统
       g_MainGUI = new D3DGUIClass(g_pd3dDevice,WINDOW_WIDTH, WINDOW_HEIGHT); //主菜单页面
       g_StartGUI = newD3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT); //开始新游戏页面
       g_LoadGUI = new D3DGUIClass(g_pd3dDevice,WINDOW_WIDTH, WINDOW_HEIGHT); //载入游戏页面
       g_OptionGUI = new D3DGUIClass(g_pd3dDevice,WINDOW_WIDTH, WINDOW_HEIGHT); //设置页面
 
       // 给四个页面分别添加背景图
       if(!g_MainGUI->AddBackground(L"GameMedia/maingui.jpg"))return false; 
       if(!g_StartGUI->AddBackground(L"GameMedia/startgui.jpg"))return false;
       if(!g_LoadGUI->AddBackground(L"GameMedia/loadgui.jpg"))return false;
       if(!g_OptionGUI->AddBackground(L"GameMedia/optiongui.jpg"))return false;
 
 
       // 分别给四个页面添加字体
       if(!g_MainGUI->CreateTextFont(L"微软雅黑", 28, &g_MainGUIFontID))return false;
       if(!g_StartGUI->CreateTextFont(L"微软雅黑", 38, &g_StartGUIFontID))return false;
       if(!g_LoadGUI->CreateTextFont(L"微软雅黑", 38, &g_LoadGUIFontID))return false;
       if(!g_OptionGUI->CreateTextFont(L"微软雅黑", 38, &g_OptionGUIFontID))return false;

 

然后,是给主菜单页面添加一些布局的代码:

       

  //---------------------------------【主菜单main页面相关的页面布局代码】---------------------------------
         //添加静态文本到页面中
         if(!g_MainGUI->AddStaticText(STATIC_TEXT_ID,L"Version 浅墨2.0版",
                   1170,735, D3DCOLOR_XRGB(55,155,255), g_MainGUIFontID)) return false;
 
         if(!g_MainGUI->AddStaticText(STATIC_TEXT_ID,L"浅墨DirectX教程第三季 之 打造游戏GUI界面",
                   500,150, D3DCOLOR_XRGB(255,255,255), g_MainGUIFontID)) return false;
 
 
         //添加4个按钮,分别是开始游戏,载入进度,选项和退出游戏,每个按钮对应3幅图
         if(!g_MainGUI->AddButton(BUTTON_START_ID,650, 340, L"GameMedia\\\\startUp.png",
                   L"GameMedia\\\\StartOver.png",L"GameMedia\\\\startDown.png")) return false;
 
         if(!g_MainGUI->AddButton(BUTTON_LOAD_ID,650, 385, L"GameMedia\\\\loadUp.png",
                   L"GameMedia\\\\loadOver.png",L"GameMedia\\\\loadDown.png")) return false;
 
         if(!g_MainGUI->AddButton(BUTTON_OPTION_ID,650, 430, L"GameMedia\\\\optionsUp.png",
                   L"GameMedia\\\\optionsOver.png",L"GameMedia\\\\optionsDown.png")) return false;
 
         if(!g_MainGUI->AddButton(BUTTON_QUIT_ID,650, 475, L"GameMedia\\\\quitUp.png",
                   L"GameMedia\\\\quitOver.png",L"GameMedia\\\\quitDown.png")) return false;


 

接着,是给开始新游戏页面添加一些布局的代码:

 //------------------------【开始新游戏start页面相关的页面布局代码】------------------------
         //添加按钮到页面中
         if(!g_StartGUI->AddButton(BUTTON_LEVEL_1_ID,550,380, L"GameMedia/level1Up.png",L"GameMedia/level1Over.png",
                   L"GameMedia/level1Down.png"))return false;
 
         if(!g_StartGUI->AddButton(BUTTON_BACK_ID,750, 350, L"GameMedia/backUp.png",L"GameMedia/backOver.png",
                   L"GameMedia/backDown.png"))return false;


这里代码就比较简洁了,所以页面中自然就会显得空荡荡的,一千个人眼中有一千个哈姆雷特,大家可以根据自己的意愿,想在页面中添加什么控件就怎么去加代码。


又接着,是给载入游戏页面添加一些布局的代码:

       

  //------------------------【载入游戏load页面相关的页面布局代码】------------------------
         //添加静态文本到页面中
         if(!g_LoadGUI->AddStaticText(STATIC_TEXT_ID,L"这里是load game页面",
                   411,340, D3DCOLOR_XRGB(33,255,55), g_LoadGUIFontID)) return false;
         //添加按钮到页面中
         if(!g_LoadGUI->AddButton(BUTTON_BACK_ID,750, 400, L"GameMedia/backUp.png",L"GameMedia/backOver.png",
                   L"GameMedia/backDown.png"))return false;


依然是很简洁的代码,想额外添加什么就自己做主吧~

 

 最后是给option页面加上一些布局的代码:

 

  //------------------------【游戏设置option页面相关的页面布局代码】------------------------
         //添加按钮到页面中
         if(!g_OptionGUI->AddButton(BUTTON_BACK_ID,750, 450, L"GameMedia/backUp.png",L"GameMedia/backOver.png",
                   L"GameMedia/backDown.png"))return false;
         //添加静态文本到页面中
          if(!g_OptionGUI->AddStaticText(STATIC_TEXT_ID,L"这里是Option页面",
                           540,60, D3DCOLOR_XRGB(33,55,255), g_OptionGUIFontID)) return false;

      

我们可以发现上述代码基本上都是差不多的。其实,对如上四个不同页面布局的设置的话,就是看在g_MainGUI ,g_LoadGUI ,g_OptionGUI,g_StartGUI四个参数中选哪个来AddStaticText或者AddButton的。

 

第四步,修改我们的GUI系统的回调函数:


//-----------------------------------【GUICallback( )函数】---------------------------------------
//      描述:GUI系统的回调函数,填写按钮按下后的相关处理代码
//--------------------------------------------------------------------------------------------------
void GUICallback(int id, int state)

         switch(id)
         
         caseBUTTON_START_ID:   //start game开始游戏按钮
                   if(state== UGP_BUTTON_DOWN)
                            g_currentGUI= GUI_START_SCREEN;
                   break;
 
         caseBUTTON_LOAD_ID:  //load game载入游戏按钮
                   if(state== UGP_BUTTON_DOWN)
                            g_currentGUI= GUI_LOAD_SCREEN;
                   break;
 
         caseBUTTON_OPTION_ID: //option设置按钮
                   if(state== UGP_BUTTON_DOWN)
                            g_currentGUI= GUI_OPTION_SCREEN;
                   break;
 
         caseBUTTON_BACK_ID: //back返回按钮
                   if(state== UGP_BUTTON_DOWN)
                            g_currentGUI= GUI_MAIN_SCREEN;
                   break;
 
         caseBUTTON_QUIT_ID://quit退出按钮
                   if(state== UGP_BUTTON_DOWN)
                            PostQuitMessage(0);
                   break;
 
         caseBUTTON_LEVEL_1_ID:  //start game开始游戏页面中,Level1按钮
                   //等级一的游戏从这里开始写代码
                   break;
         

相较于之前的那个GUI回调函数,我们添加了一些按钮。然后因为在步骤一中控件的ID有变,所以这里的case后面的控件ID自然有改变。

 

第五步,在渲染五步曲的第三步中,用一个一个if 、else if、else组合句,处理和渲染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渲染五步曲之三】:正式绘制
         //--------------------------------------------------------------------------------------
 
 
         //一个if 、else if、else组合句,处理和渲染GUI系统
         if(g_currentGUI== GUI_MAIN_SCREEN)
                   ProcessGUI(g_MainGUI,g_LMBDown, g_MouseX,
                   g_MouseY,GUICallback);
         elseif(g_currentGUI == GUI_START_SCREEN)
                   ProcessGUI(g_StartGUI,g_LMBDown, g_MouseX,
                   g_MouseY,GUICallback);
         elseif(g_currentGUI == GUI_LOAD_SCREEN)
                   ProcessGUI(g_LoadGUI,g_LMBDown, g_MouseX,
                   g_MouseY,GUICallback);
         elseif(g_currentGUI == GUI_OPTION_SCREEN)
                   ProcessGUI(g_OptionGUI,g_LMBDown, g_MouseX,
                   g_MouseY,GUICallback);
         else
                   ProcessGUI(g_MainGUI,g_LMBDown, g_MouseX,
                   g_MouseY,GUICallback);
 
         //-----------------------------【绘制文字信息】-----------------------------
         HelpText_Render(hwnd);
 
 
         //--------------------------------------------------------------------------------------
         //【Direct3D渲染五步曲之四】:结束绘制
         //--------------------------------------------------------------------------------------
         g_pd3dDevice->EndScene();                       // 结束绘制
         //--------------------------------------------------------------------------------------
         //【Direct3D渲染五步曲之五】:显示翻转
         //--------------------------------------------------------------------------------------
         g_pd3dDevice->Present(NULL,NULL, NULL, NULL);  // 翻转与显示
 

嗯,上面讲的内容就是这篇文章配套示例程序的实现思路了。接着来看一些新鲜的内容吧。






三、进阶GUI书籍推荐

 



GUI相关的知识暂时就打算讲这两章了。如果大家嫌没过够瘾,那么浅墨给大家推荐一本专门讲解GUI的书籍。这本书的名字叫《Directx.9.User.Interfaces.-.Design.and.Implementation》,是一本专门介绍DirectX9中 UI设计和实现的一本书籍,虽然是英文原版,但是讲解清晰明了,很好理解。

书封面是这样的:



有对GUI有进阶了解兴趣的朋友们,不妨下载了回去研究研究。


这里是下载地址:


【浅墨推荐】Directx.9.User.Interfaces.-.Design.and.Implementation(百度云盘)

 

 




四、兼容DirectX的优秀开源GUI库推荐

 


另外给大家推荐一个开源的GUI库—— CEGUI。CEGUI它为Crazy Eddie's GUI的缩写,它是一个用C++开发的面向对象的免费界面库,CEGUI是一个兼容OpenGLDirectX的优秀开源GUI库,。针对游戏开发者,提供了3D环境中的窗口及其部件的图形API。可以在DirectX3D中使用CEGUI中使用。

CRGUI的官方网站是: 
http://cegui.org.uk/   

 然后放两张DirectX中使用CEGUI的截图吧:

 



 

比较炫的吧~所以呢,更多炫酷的游戏GUI推荐大家站在巨人的肩膀上实现。这样省时省力。

 



 



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

 



这次的工程除了main.cpp和D3DUtil.h就是D3DGUIClass类的源文件和头文件,非常简约。如下图:


 

程序主要是实现了一个多页面的GUI系统,有主菜单页面,开始游戏页面,载入游戏页面,选项页面这四页面。

后面的3个页面有返回按钮可以供我们返回到主菜单。然后“quit”退出按钮可以退出游戏程序。



那么,老规矩,上程序的主体部分,main.cpp的代码吧:

//-----------------------------------【程序说明】----------------------------------------------
// 【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;   //包含显卡名称的字符数组

bool												g_LMBDown = false;      // GUI中的鼠标状态信息,鼠标左键是否按下的标识
int												g_MouseX = 0, g_MouseY = 0;      //存储鼠标坐标的两个变量

 //创建四个GUI类对象,分别代表四个页面
D3DGUIClass		*g_MainGUI = NULL;//主窗口
D3DGUIClass		*g_StartGUI = NULL; //游戏开始窗口
D3DGUIClass		*g_LoadGUI = NULL; //游戏载入窗口
D3DGUIClass		*g_OptionGUI = NULL; //游戏设置窗口

int						g_MainGUIFontID = -1;						//  GUI中字体对象的ID
int						g_StartGUIFontID = -1;						//  GUI中字体对象的ID
int						g_LoadGUIFontID = -1;						//  GUI中字体对象的ID
int						g_OptionGUIFontID = -1;					//  GUI中字体对象的ID
int						g_currentGUI = GUI_MAIN_SCREEN;    //一个当前的GUI标识


//-----------------------------------【全局函数声明部分】-------------------------------------
//	描述:全局函数声明,防止“未声明的标识”系列错误
//------------------------------------------------------------------------------------------------
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\\\\Assassins Creed Theme 刺客信条.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_MainGUI = new D3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT); //主菜单页面
	g_StartGUI = new D3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT); //开始新游戏页面
	g_LoadGUI = new D3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT); //载入游戏页面
	g_OptionGUI = new D3DGUIClass(g_pd3dDevice, WINDOW_WIDTH, WINDOW_HEIGHT); //设置页面

	// 给四个页面分别添加背景图
	if(!g_MainGUI->AddBackground(L"GameMedia/maingui.jpg")) return false;  
	if(!g_StartGUI->AddBackground(L"GameMedia/startgui.jpg")) return false;
	if(!g_LoadGUI->AddBackground(L"GameMedia/loadgui.jpg")) return false;
	if(!g_OptionGUI->AddBackground(L"GameMedia/optiongui.jpg")) return false;


	// 分别给四个页面添加字体
	if(!g_MainGUI->CreateTextFont(L"微软雅黑", 28, &g_MainGUIFontID)) return false;
	if(!g_StartGUI

以上是关于Visual C++游戏开发五十七 浅墨DirectX教程二十四 打造游戏GUI界面的主要内容,如果未能解决你的问题,请参考以下文章

第五十七天

游戏开发大神“浅墨”《OpenCV3编程入门》作者毛星云“意外”离世

“全栈2019”Java第五十七章:多态与构造方法详解

梁平HTML 5网页编程bootstrap响应式开发培训(十五十七)

LLVM每日谈之五十七 TableGen

Visual C++游戏开发笔记之五——游戏画面绘图绘制位图