《鬼泣5》:
《英雄无敌6》:
欣赏完了,我们继续开讲吧。
二、关于状态显示界面(HUD)
这一节里让我们一起科普HUD的概念。
我们知道,通常情况下在游戏中屏幕上会有很多的小的界面模块配上文字,为玩家提供了一些有用的信息,比如玩家剩余任务时间,生命值,魔法值,坐标位置等等更多的信息。而因为在玩家和游戏交互的过程中,这些界面是透过摄像机视角显示出来的,它们被称为HUD(heads-up-display)。在视频游戏中,HUD就是一类可以实时向玩家显示有用信息和符号的GUI,并且它和诸如游戏菜单这样的其他界面不同.HUD中通常没有按钮、编辑框或者适合玩家交互的内容,因为玩家往往都是被游戏本身的画面和内容所吸引,而不是去关注HUD。所以呢,HUD通常并不去使用可以交互的元素,而倾向于使用文本和图像来表示某些信息。所以,游戏选项和偏好设置这样的内容不是HUD的菜,它们直接交由系统菜单来完成。
下面我们看一看更加规范化的HUD的介绍。
HUD,heads-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》:
《仙剑奇侠传五 前传》:
《刺客信条4黑旗》:
三、开始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系统类的实现细节留待下篇讲解(如果需要额外用一次更新来讲解的话)。
还是把详细注释的头文件贴一下吧:
-
-
-
-
-
-
- #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
-
-
- #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
-
-
-
-
- struct GUIVERTEX
- {
- floatx, y, z, rhw;
- unsignedlong color;
- floattu, tv;
- };
- #define D3DFVF_GUI (D3DFVF_XYZRHW |D3DFVF_DIFFUSE | D3DFVF_TEX1)
-
-
-
- struct GUICONTROL
- {
-
- intm_type;
- intm_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;
- };
-
-
-
- class D3DGUIClass
- {
-
- private:
- LPDIRECT3DDEVICE9m_pd3dDevice;
- LPD3DXFONT*m_pFonts;
- 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; }
- 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; }
- voidSetWindowSize(int w, int h) { m_nWindowWidth = w; m_nWindowHeight = h; }
-
- LPD3DXFONTGetFont(int id)
- {
- if(id< 0 || id >= m_nTotalFontNum) return NULL;
- returnm_pFonts[id];
- }
-
- GUICONTROL*GetGUIControl(int id)
- {
- if(id< 0 || id >= m_nTotalControlNum) return NULL;
- return&m_pControls[id];
- }
-
- LPDIRECT3DVERTEXBUFFER9GetVertexBuffer(int id)
- {
- if(id< 0 || id >= m_nTotalBufferNum) return NULL;
- returnm_pVertexBuffer[id];
- }
-
-
- boolCreateTextFont(wchar_t *fontName, int size, int *fontID);
- boolAddBackground(wchar_t *fileName);
- 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;
- int g_FontID = -1;
- bool g_LMBDown= false;
- int g_MouseX= 0, g_MouseY = 0;
可以看到,在这里我们定义了自己写的GUI系统类D3DGUIClass的类对象,然后是鼠标状态信息相关的变量和一个字体对象。
第二步,在消息处理函数的switch中添加一些新的case:
-
-
-
- LRESULT CALLBACK WndProc( HWND hwnd, UINTmessage, WPARAM wParam, LPARAM lParam )
- {
- switch(message )
- {
- caseWM_PAINT:
- Direct3D_Render(hwnd,0.0f);
- ValidateRect(hwnd,NULL);
- break;
-
- caseWM_KEYDOWN:
- if(wParam == VK_ESCAPE)
- DestroyWindow(hwnd);
- break;
- caseWM_DESTROY:
- Direct3D_CleanUp();
- PostQuitMessage(0 );
- break;
-
- 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:
- returnDefWindowProc( hwnd, message, wParam, lParam );
- }
-
- return0;
- }
其实这一步就是在之前消息的基础上,添加了左键按下,左键弹起,和鼠标移动的消息响应。
第三步,在进行渲染资源准备的Object_Init( )函数中,添加载入GUI系统中的资源到内存中的相关代码:
-
-
-
- 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);
-
-
-
-
- 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;
-
-
-
- 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;
-
-
-
- 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函数就可以了。这个函数的具体实现我们在稍后退出的文章中会讲到。
-
-
-
- g_pd3dDevice->BeginScene();
-
-
-
-
-
-
-
- ProcessGUI(g_gui,g_LMBDown, g_MouseX, g_MouseY, GUICallback);
-
-
- HelpText_Render(hwnd);
-
-
-
-
-
- g_pd3dDevice->EndScene();
五、详细注释的源代码欣赏
这次的工程因为是作为GUI的初步演示,自然就有了孑然一身的感觉,除了main.cpp和D3DUtil.h就是D3DGUIClass类的源文件和头文件了。如下图:
程序主要是实现了简单的GUI系统,通过GUI系统的功能在屏幕上添加了游戏菜单的四个按钮,并且在屏幕上输出了“浅墨DirectX教程第三季 之 打造游戏GUI界面”,"Version 浅墨1.0版"这两段文字。
而且目前对“quit”退出按钮的功能进行了实行实现,其实就简单一句if(state == UGP_BUTTON_DOWN) PostQuitMessage(0);。。。。。
那么,老规矩,上程序的核心部分,main函数的代码吧:
-
-
-
-
-
-
-
-
-
-
-
- #define WINDOW_WIDTH 1366 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
- #define WINDOW_HEIGHT 768 //为窗口高度定义的宏,以方便在此处修改窗口高度
- #define WINDOW_TITLE _T("【致我们永不熄灭的游戏开发梦想】 浅墨DirectX教程二十三 打造游戏GUI界面(一)博文配套示例程序 by浅墨") //为窗口标题定义的宏
-
-
-
-
-
-
- #include
- #include
- #include
- #include
- #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;
- LPD3DXFONT g_pTextFPS =NULL;
- LPD3DXFONT g_pTextAdaperName = NULL;
- LPD3DXFONT g_pTextHelper = NULL;
- LPD3DXFONT g_pTextInfor= NULL;
- float g_FPS= 0.0f;
- wchar_t g_strFPS[50] ={0};
- wchar_t g_strAdapterName[60] ={0};
-
- D3DGUIClass *g_gui = NULL;
- bool g_LMBDown = false;
- int g_MouseX = 0, g_MouseY = 0;
- int g_FontID = -1;
-
-
-
-
-
- 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);
-
-
-
-
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
- {
-
-
- 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=(HICON)::LoadImage(NULL,_T("GameMedia\\icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);
- wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
- wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);
- wndClass.lpszMenuName = NULL;
- wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");
-
- if( !RegisterClassEx( &wndClass ) )
- return -1;
-
- HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,
- WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
- WINDOW_HEIGHT, NULL, NULL, hInstance, NULL );
-
-
-
- if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
- {
- MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0);
- }
- PlaySound(L"GameMedia\\Heart - 刺客信条.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);
-
- MoveWindow(hwnd,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,true);
- ShowWindow( hwnd, nShowCmd );
- UpdateWindow(hwnd);
-
-
-
- MSG msg = { 0 };
- while( msg.message != WM_QUIT )
- {
- 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;
- }
-
-
-
-
-
- LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
- {
- switch( message )
- {
- case WM_PAINT:
- Direct3D_Render(hwnd,0.0f);
- ValidateRect(hwnd, NULL);
- break;
-
- case WM_KEYDOWN:
- if (wParam == VK_ESCAPE)
- DestroyWindow(hwnd);
- break;
- case WM_DESTROY:
- Direct3D_CleanUp();
- PostQuitMessage( 0 );
- break;
-
- 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:
- return DefWindowProc( hwnd, message, wParam, lParam );
- }
-
- return 0;
- }
-
-
-
-
-
- HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance)
- {
-
-
-
-
- LPDIRECT3D9 pD3D = NULL;
- if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
- return E_FAIL;
-
-
-
-
- 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;
-
-
-
-
- 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;
-
-
-
-
- if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
- hwnd, vp, &d3dpp, &g_pd3dDevice)))
- return E_FAIL;
-
-
-
- wchar_t TempName[60]=L"当前显卡型号:";
- D3DADAPTER_IDENTIFIER9 Adapter;
- pD3D->GetAdapterIdentifier(0,0,&Adapter);
- int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);
- MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);
- wcscat_s(TempName,g_strAdapterName);
- wcscpy_s(g_strAdapterName,TempName);
-
- if(!(S_OK==Objects_Init())) return E_FAIL;
-
- SAFE_RELEASE(pD3D)
-
- return S_OK;
- }
-
-
-
-
-
- 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);
-
-
-
-
- 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;
-
-
-
- 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;
-
-
-
- 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;
- }
- }
-
-
-
-
-
- void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta)
- {
-
- }
-
-
-
-
-
-
- void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta)
- {
-
-
-
- g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 255, 255), 1.0f, 0);
-
-
-
-
- g_pd3dDevice->BeginScene();
-
-
-
-
-
-
-
- ProcessGUI(g_gui, g_LMBDown, g_MouseX, g_MouseY, GUICallback);
-
-
- HelpText_Render(hwnd);
-
-
-
-
-
- g_pd3dDevice->EndScene();
-
-
-
- g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
-
- }
-
-
-
-
-
- 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));
- }
-
-
-
-
-
- float Get_FPS()
- {
-
-
- static float fps = 0;
- static int frameCount = 0;
- static float currentTime =0.0f;
- static float lastTime = 0.0f;
-
- frameCount++;
- currentTime = timeGetTime()*0.001f;
-
-
- if(currentTime - lastTime > 1.0f)
- {
- fps = (float)frameCount /(currentTime - lastTime);
- lastTime = currentTime;
- frameCount = 0;
- }
-
- return fps;
- }
-
-
-
-
-
-
- void Direct3D_CleanUp()
- {
-
- SAFE_RELEASE(g_pd3dDevice);
- SAFE_RELEASE(g_pTextFPS)
- SAFE_RELEASE(g_pd3dDevice)
- SAFE_DELETE(g_gui)
- }
那么,最后一起看看运行截图吧:
鼠标悬停在options按钮之上,可以发现按钮“下陷”了
点击quit按钮,游戏程序便会退出:
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/16384009
作者:毛星云(浅墨) 邮箱: [email protected]
众所周知,GUI是游戏中不可缺少的元素,这篇文章中,我们首先了解了游戏GUI界面的知识与相关概念,然后一起设计了一个封装好GUI图形界面的C++类。这个类有着非常强的扩展性,使用也是极其方便,很适合二次开发。
先看一张实现的效果图吧:
其中的背景音乐,游戏图标和背景图片都出自育碧公司的招牌式大作《刺客信条》。
程序的窗口大小已经被浅墨调成了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》:
《英雄无敌6》:
欣赏完了,我们继续开讲吧。
二、关于状态显示界面(HUD)
这一节里让我们一起科普HUD的概念。
我们知道,通常情况下在游戏中屏幕上会有很多的小的界面模块配上文字,为玩家提供了一些有用的信息,比如玩家剩余任务时间,生命值,魔法值,坐标位置等等更多的信息。而因为在玩家和游戏交互的过程中,这些界面是透过摄像机视角显示出来的,它们被称为HUD(heads-up-display)。在视频游戏中,HUD就是一类可以实时向玩家显示有用信息和符号的GUI,并且它和诸如游戏菜单这样的其他界面不同.HUD中通常没有按钮、编辑框或者适合玩家交互的内容,因为玩家往往都是被游戏本身的画面和内容所吸引,而不是去关注HUD。所以呢,HUD通常并不去使用可以交互的元素,而倾向于使用文本和图像来表示某些信息。所以,游戏选项和偏好设置这样的内容不是HUD的菜,它们直接交由系统菜单来完成。
下面我们看一看更加规范化的HUD的介绍。
HUD,heads-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》:
《仙剑奇侠传五 前传》:
《刺客信条4黑旗》:
三、开始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系统类的实现细节留待下篇讲解(如果需要额外用一次更新来讲解的话)。
还是把详细注释的头文件贴一下吧:
-
-
-
-
-
-
- #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
-
-
- #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
-
-
-
-
- struct GUIVERTEX
- {
- floatx, y, z, rhw;
- unsignedlong color;
- floattu, tv;
- };
- #define D3DFVF_GUI (D3DFVF_XYZRHW |D3DFVF_DIFFUSE | D3DFVF_TEX1)
-
-
-
- struct GUICONTROL
- {
-
- intm_type;
- intm_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;
- };
-
-
-
- class D3DGUIClass
- {
-
- private:
- LPDIRECT3DDEVICE9m_pd3dDevice;
- LPD3DXFONT*m_pFonts;
- 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; }
- 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; }
- voidSetWindowSize(int w, int h) { m_nWindowWidth = w; m_nWindowHeight = h; }
-
- LPD3DXFONTGetFont(int id)
- {
- if(id< 0 || id >= m_nTotalFontNum) return NULL;
- returnm_pFonts[id];
- }
-
- GUICONTROL*GetGUIControl(int id)
- {
- if(id< 0 || id >= m_nTotalControlNum) return NULL;
- return&m_pControls[id];
- }
-
- LPDIRECT3DVERTEXBUFFER9GetVertexBuffer(int id)
- {
- if(id< 0 || id >= m_nTotalBufferNum) return NULL;
- returnm_pVertexBuffer[id];
- }
-
-
- boolCreateTextFont(wchar_t *fontName, int size, int *fontID);
- boolAddBackground(wchar_t *fileName);
- 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;
- int g_FontID = -1;
- bool g_LMBDown= false;
- int g_MouseX= 0, g_MouseY = 0;
可以看到,在这里我们定义了自己写的GUI系统类D3DGUIClass的类对象,然后是鼠标状态信息相关的变量和一个字体对象。
第二步,在消息处理函数的switch中添加一些新的case:
-
-
-
- LRESULT CALLBACK WndProc( HWND hwnd, UINTmessage, WPARAM wParam, LPARAM lParam )
- {
- switch(message )
- {
- caseWM_PAINT:
- Direct3D_Render(hwnd,0.0f);
- ValidateRect(hwnd,NULL);
- break;
-
- caseWM_KEYDOWN:
- if(wParam == VK_ESCAPE)
- DestroyWindow(hwnd);
- break;
- caseWM_DESTROY:
- Direct3D_CleanUp();
- PostQuitMessage(0 );
- break;
-
- 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:
- returnDefWindowProc( hwnd, message, wParam, lParam );
- }
-
- return0;
- }
其实这一步就是在之前消息的基础上,添加了左键按下,左键弹起,和鼠标移动的消息响应。
第三步,在进行渲染资源准备的Object_Init( )函数中,添加载入GUI系统中的资源到内存中的相关代码:
-
-
-
- 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);
-
-
-
-
- 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;
-
-
-
- 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;
-
-
-
- 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函数就可以了。这个函数的具体实现我们在稍后退出的文章中会讲到。
-
-
-
- g_pd3dDevice->BeginScene();
-
-
-
-
-
-
-
- ProcessGUI(g_gui,g_LMBDown, g_MouseX, g_MouseY, GUICallback);
-
-
- HelpText_Render(hwnd);
-
-
-
-
-
- g_pd3dDevice->EndScene();
五、详细注释的源代码欣赏
这次的工程因为是作为GUI的初步演示,自然就有了孑然一身的感觉,除了main.cpp和D3DUtil.h就是D3DGUIClass类的源文件和头文件了。如下图:
程序主要是实现了简单的GUI系统,通过GUI系统的功能在屏幕上添加了游戏菜单的四个按钮,并且在屏幕上输出了“浅墨DirectX教程第三季 之 打造游戏GUI界面”,"Version 浅墨1.0版"这两段文字。
而且目前对“quit”退出按钮的功能进行了实行实现,其实就简单一句if(state == UGP_BUTTON_DOWN) PostQuitMessage(0);。。。。。
那么,老规矩,上程序的核心部分,main函数的代码吧:
-
-
-
-
-
-
-
-
-
-
-
- #define WINDOW_WIDTH 1366 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
- #define WINDOW_HEIGHT 768 //为窗口高度定义的宏,以方便在此处修改窗口高度
- #define WINDOW_TITLE _T("【致我们永不熄灭的游戏开发梦想】 浅墨DirectX教程二十三 打造游戏GUI界面(一)博文配套示例程序 by浅墨") //为窗口标题定义的宏
-
-
-
-
-
-
- #include
- #include
- #include
- #include
- #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;
- LPD3DXFONT g_pTextFPS =NULL;
- LPD3DXFONT g_pTextAdaperName = NULL;
- LPD3DXFONT g_pTextHelper = NULL;
- LPD3DXFONT g_pTextInfor= NULL;
- float g_FPS= 0.0f;
- wchar_t g_strFPS[50] ={0};
- wchar_t g_strAdapterName[60] ={0};
-
- D3DGUIClass *g_gui = NULL;
- bool g_LMBDown = false;
- int g_MouseX = 0, g_MouseY = 0;
- int g_FontID = -1;
-
-
-
-
-
- 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);
-
-
-
-
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
- {
-
-
- 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=(HICON)::LoadImage(NULL,_T("GameMedia\\icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);
- wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
- wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);
- wndClass.lpszMenuName = NULL;
- wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");
-
- if( !RegisterClassEx( &wndClass ) )
- return -1;
-
- HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,
- WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
- WINDOW_HEIGHT, NULL, NULL, hInstance, NULL );
-
-
-
- if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
- {
- MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0);
- }
- PlaySound(L"GameMedia\\Heart - 刺客信条.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);
-
- MoveWindow(hwnd,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,true);
- ShowWindow( hwnd, nShowCmd );
- UpdateWindow(hwnd);
-
-
-
- MSG msg = { 0 };
- while( msg.message != WM_QUIT )
- {
- 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;
- }
-
-
-
-
-
- LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
- {
- switch( message )
- {
- case WM_PAINT:
- Direct3D_Render(hwnd,0.0f);
- ValidateRect(hwnd, NULL);
- break;
-
- case WM_KEYDOWN:
- if (wParam == VK_ESCAPE)
- DestroyWindow(hwnd);
- break;
- case WM_DESTROY:
- Direct3D_CleanUp();
- PostQuitMessage( 0 );
- break;
-
- 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:
- return DefWindowProc( hwnd, message, wParam, lParam );
- }
-
- return 0;
- }
-
-
-
-