DirectX3D游戏制作之---游戏界面的设计

前言:

   再深沉的感情,再真挚的牵挂,还是会有分开的一天......到头来又怎么敌得过生离死别......

                                                                                                             -------韩菱纱《仙剑奇侠传四》

PS:为了方便大家阅读,个人认为比较重要的内容-------红色字体显示

                                         个人认为可以了解的内容-------紫色字体显示

----------------------------------------------------------------------------------------------------

-------------------------------------------分  割   线-----------------------------------------------

       恰逢诛仙正在热映,一时兴起,就制作了一个关于诛仙的游戏GUI界面,当然这个只是实现了个大概,而且素材挺难找的,有些找到的素材还用不了,有些甚至要付费(这里不得不吐槽一下,大家把东西拿出来分享多好啊,这样中国的计算机行业才会更好地进步)      

       首先来看一下主页面


       接下来再来看一下开始界面


        点击Level1按钮之后,就会进入游戏载入界面:

DirectX3D游戏制作之---游戏界面的设计_第1张图片

       在载入游戏这个界面出现大约两秒之后,正式进入游戏界面  (这次的游戏场景比较简单)    


DirectX3D游戏制作之---游戏界面的设计_第2张图片

再来看一下其他的几个界面(载入界面,以及游戏设置界面



-----------------------------------------------正文分割线-----------------------------------------------------

      之前写了一个关于3D场景的程序,里面封装了一些的类(窗口类、摄像机类、地形类、天空盒类、粒子类、.X文件载入类以及骨骼动画类等),已经可也实现一些简单的场景了,今天换个方向,也就是相当于给之前的游戏穿上一件好看的衣服,主要来讲一讲GUI界面设计,也算是一个GUI界面类吧,可以把这些一个个类集中起来封装到一个大类之中,这样的话,以后写程序就方便多了,直接用这些写好的类就可以了,创建类的过程代码量大,但是使用类只要两三句代码就可以。说白了游戏引擎就是干了这些事情,不过游戏引擎的实现那可是相当复杂,而且一款好的游戏引擎绝不是一个人就可以完成的,现如今比较有名的游戏引擎(如:虚幻、寒霜,以及在国内很受欢迎的unity引擎),这些游戏引擎都是集结了很多人的研究,话说仙剑奇侠传七可能会采用虚幻引擎(虽然姚仙在仙六的时候就对外宣传要采用虚幻引擎),不过虚幻引擎在图像显示性能方面甩其他的一些游戏引擎不是一点半点,毕竟是采用C++开发的,图像性能方面自然有先天优势,不过其他游戏引擎也有自己的特点,比如unity,它的适用性就很广,主要采用C#为主要语言作为开发,使用的人非常多,尤其是在国内几乎随处可见,在手游、端游两个方面都有不可替代的作用。好了题外话就说到这里,在正式开始之前,有几个概念先拿出来说一说:

                                                      什么是GUI,什么又是UI

             虽然可能现在对于GUI与UI的界限不明显了,有时候甚至可以认为这两者是一回事,不过在我看来GUI(图形设计师)UI(交互设计师)还是有区别的(现在可能区分不明显了),通常是一个GUI设计师什么都干,很苦,甚至有些压抑。目前来看,一般情况下大家说的UI设计师和GUI设计师基本上是一回事,都会做图、都会画icon之类的。
       不过从传统意义上来讲,我个人认为UI设计师就是指交互设计师,是研究用户行为和操作逻辑的人。交互设计师的工作内容就是设计软件的操作流程、结构、操作规范(spec)、用户信息回馈等等。一个应用在写代码之前需要作的就是交互设计,并且确立交互模型,交互规范,手势动作等等,这就是前端设计。
       那么GUI设计师很多人称之为美工,但实际上不是单纯意义上的美术工作人员,而是软件产品的产品外形设计师。GUI设计师要从UI设计师那里提取设计细节,然后把这些细节通过视觉效果最后传达给用户,同时GUI设计师在设计过程中也会对UI设计师的方案起到一个检查和反馈作用,把一些视觉上的弊端反馈给UI设计师,同时要指导工程师进行应用布局。而不是画个icon或者提供资源图而已。换言之就是GUI设计师是枢纽,是连接UI和工程师的重要枢纽。因此为了用户体验更好更完善。二者缺一不可
       当然这只是个人意见,每个人可以有不同的看法。下面再来说一说另外一个概念。

                                                                                     什么是控件

       又一个比较关键的概念被引出了,什么是控件呢?控件是对数据和方法的封装。控件可以有自己的属性和方法。属性是控件数据的简单访问者。方法则是控件的一些简单而可见的功能。

       当然这样说太笼统了,可以这样想:小时候,我们都玩积木,任何单个积木都被视为基本元素(如输入框,按钮等),但通过合理的组合,我们可以将其中的几块积木做成小汽车,放在我们积木堆砌的城市,与堆积木不同的是,用积木堆得小汽车,再需要时,还需要重复劳动,而我们做成的控件则不同,它可以随时随地的初始化并可能通过接收参数改变自身属性(颜色,尺寸等)来使用),在具体到我们C++之中,更确切的说是在VisualC++之中,其实我们可以把控件当做一种特殊的窗口,不但如此,创建控件与创建窗口一样,使用CreateWindow或CreateWindowEx函数,不过,在窗口样式上面记得用上以下两个内容:

(1)、WS_CHILD:控件是放在我们的窗口上的,自然要作为窗口的子窗口,WS_CHILDWINDOW也一样,为了节约几个字母,用WS_CHILD吧。

(2)、WS_VISIBLE:既然要使用控件,自然要让别人看得见。否则即使再设计的再美也没有人知道了!

我们来简单看一下怎么样使用系统之中的控件:

       说了一大堆概念,接下来我们直接使用一下,控件的种类有很多,像什么按钮、对话框、滑动条、甚至是显示的文字都可以称之为控件,那么下面我们就来创建一个按钮控件,具体地,我们可以在窗口创建后,显示之前,创建一个控件,由于CreateWindow函数返回之前,我们会收到WM_CREATE消息,在这个时候我们可以创建一个按钮!

case WM_CREATE:  
    {  
        //创建按钮  
        HWND hButton = CreateWindow(L"Button", L"按钮一", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,  
            35, 45, 160, 65, hwnd, NULL, hInstance, NULL);  
    }  
    return 0; 
注:hInstance是一个全局实例句柄!

当然由于我们是调用系统的函数创建的控件,所以创建的按钮是这个样子的:(如图所示)
DirectX3D游戏制作之---游戏界面的设计_第3张图片

       是不是觉得很丑呢?确实我们在玩游戏的时候几乎看不到这样的按钮,很显然这样的控件是很难出现在游戏上的,有些玩家看到界面不好看,可能就不会去玩这个游戏了,界面做的不好看,很有可能就会让玩家失去玩的兴趣,这样估计也算不上一个成功的游戏,毕竟每个人都是喜欢美的事物的,所以我们在游戏之中不可能使用Win32之中自带的控件了,所以这时候我们就应该使用自定义控件了,我们自己来设计控件

       关于控件种类有很多,我们最重要的是关注用户界面控件

它主要用于开发构建用户界面(UI)的控件,帮助完成软件开发中视窗、文本框、按钮、下拉式菜单等界面元素的开发,至于其他的一些控件(如:图表控件报表控件表格控件条形码控件图像处理控件等可以暂时不去管),

关于控件的内容就暂时说到这里,具体控件的设计,下面会讲到。

                                                                  Alpha混合技术

       为什么要用到Alpha混合呢?我们创建出来的控件在放到界面上之后,自然会给这个控件(按钮)贴上纹理(也可以说给这个按钮贴上背景),通常来说,这些纹理都是一些与玩家交互相关内容,比如开始游戏,读取游戏等内容,来看一个具体的界面,如图所示:这是仙剑奇侠传4的界面(这也是博主对于仙剑系列最喜欢,最有感觉的一部),在图片中的椭圆形的,带有新的开始、旧的回忆等这样的文字的,可以点击的就是一个个按钮(控件)。

DirectX3D游戏制作之---游戏界面的设计_第4张图片

       从图片之中我们可以看出,界面上的一个一个按钮显得很自然,所以创建出一个按钮很简单,给按钮贴上纹理也不难,最重要的就是怎么样才可以让按钮与界面相融合,这样才显得不突兀,界面要的就是一种自然的感觉,让人看了之后很赏心悦目,有一种很想玩的感觉,界面我认为最重要的就是简洁、干净。关于仙侠类的游戏,个人感觉最好是和中国传统文化结合起来,仙侠游戏也算是中国特有的吧!自然要体现出中国的古代画风,没必要向西方那样的界面,虽然那样的界面也很好看。不过就是要做的和别人不一样。其实仙剑的界面画风我还是挺喜欢的(个人对于武侠小说也还是比较喜欢的)。其实我感觉中国古代的画风是水墨风格,这样的风格用在仙侠类游戏也不错啊,江湖江湖,有江有湖,在带上一些水墨风格,这样岂不更好,看上去很干净!不知道大家是否看过电视《琅琊榜》,个人感觉这样的画风真的很不错,不带一点多余的内容,宁静、淡泊的画风感觉就是江湖中人所梦寐以求的,为什么玩游戏,游戏是一种体验,要的就是生活之中体验不到的感觉,如今的社会我们不乏色彩的缺失,到处都是那种五颜六色的感觉,尤其是仙侠游戏,我们要的就是体会古人那样在江湖上自由自在的感觉,所以我个人感觉画风应该是个简洁为主,颜色以淡色系为主,水墨画风格是最适合不过了,个人感觉画风也是《琅琊榜》收获好评的一个重要原因吧!尤其是现在的电视很多都是浓妆艳抹(这里我就不提某某导演喜欢鹦鹉那样的画风了),突然间出现这种带有水墨画风的电视剧,都会眼前一亮!

DirectX3D游戏制作之---游戏界面的设计_第5张图片

       这样的图片是不是看了之后心情就会平静很多,尤其是在如今的社会,大家都在忙碌的生活。如果有这样一款以3D水墨风格的游戏,让我每天疲惫学习之后可以去这样的“江湖”上走一走,我想我是会去的。当然咯这里面的渲染技术要求应该会不一样吧,不过还是要做的真实一些,里面的人物着装可以更加贴近古代风格一些,玩游戏么,最重要的就是休闲,最好是把游戏当做是另一种方式的休息。

        其实以下面这张图片做游戏登录界面也不错啊,不过前提请胡歌来做一下形象代言

DirectX3D游戏制作之---游戏界面的设计_第6张图片

        中国传统文化强调的是朦胧感,有意境。所以个人感觉把画风做成水墨画的风格似乎也是挺好的,有意境。不过一般来说棋类的游戏界面做成水墨画的风格效果应该不错吧!尤其是象棋这样的带有中国传统文化的,把棋盘设计成水墨风格效果比较好(个人感觉而已)。

        不知不觉又扯了好多其他的内容,接下来正式讲一讲Alpha混合技术

        什么是Alpha混合:首先看一下Alpha通道,Alpha通道是计算机中存储图片透明度信息的通道,它是一个8位灰度的通道,用256级灰度记录图像中的透明信息,定义透明,不透明,半透明等,其中黑色表示完全透明,白色表示不透明,灰色为半透明。

            了解了什么是Alpha混合,就要说一说为什么要用了?如果不用Alpha混合,我们绘制图形的颜色总是替换当前颜色缓冲区中存在的颜色,这样后面的物体总是覆盖在原有的物体上。但是当想要绘制类似于玻璃、水等具有透明效果的物体时,这种方法显然满足不了要求。通过定义一个表示物体半透明度的Alpha值和一个半透明计算公式,可以将要绘制的物体颜色与颜色缓冲区中存在的颜色相混合,从而绘制出具有半透明效果的物体,即传说中的Alpha Blend

        进一步解释一下,Alpha其作用就是要实现一种半透明效果。假设一种不透明东西的颜色是A,另一种透明的东西的颜色是B,那么透过B去看A,看上去的颜色C就是B和A的混合颜色,可以用这个式子来近似,设B物体的透明度为alpha(取值为0-1,0为完全透明,1为完全不透明)

R(C)=alpha*R(B)+(1-alpha)*R(A)

G(C)=alpha*G(B)+(1-alpha)*G(A)

B(C)=alpha*B(B)+(1-alpha)*B(A)

其中R(x)、G(x)、B(x)分别指颜色x的RGB分量(这里自变量x取的是颜色C)
       当然这只是特定条件下一个颜色取值的式子,并不是官方通用式子,在DirectX中的Alpha融合公式如下:OutPutColor = (RGBsrc * Ksrc) OP (RGBdst * Kdst),这是官方给出的定义,先对每个名称解释一下:

OutPutColor表示alpha混合后的颜色值.

RGBsrc表示源颜色值,即将要绘制的图元的颜色值
Ksrc表示源混合系数,通常赋值为表示半透明程度的alpha值,也可以是属于枚举类型D3DBLEND的任意值,用来和RGBsrc相乘。
RGBdst表示目标颜色值,即当前颜色缓冲区中的颜色值
Kdst表示目标混合系数,可以是属于枚举D3DBLEND的任意值,用来和RGBdst相乘。
OP表示源计算结果与颜色缓冲区计算结果的混合方法,默认状态下OP为D3DBLEND_ADD,即源计算结果与颜色缓冲区计算结果相加。

      下面我们以这个公式展开来讲一讲,假设屏幕当前像素颜色值为SrcColor目标像素颜色值DestColor,屏幕像素的当前颜色值SrcColor可与目标像素颜色值DestColor进行如下运算,然后将获得的颜色值Color作为该像素的新颜色,以实现像素的目标颜色与源颜色的混合。我们举一个例子(这个例子是上面公式的一种情况)
       Color = SrcColor * SrcBlend + DestColor * DestBlend
       这里,SrcBlend和DestBlend为源混合因子目标混合因子(SrcBlend对应于Ksrc,DestBlend对应于Kdst),分别乘以源颜色和目标颜色。SrcColor (也就是上面的RGBsrc),SrcBlend(对应于上面的RGBdst), DestColor,DestBlend都是一个4维向量,而乘法运算 * 则是一个一个向量点积运算。官方给出的公式之中OP是一种运算方式,也可以说是融合方式,这里我们取了加法运算,当然什么乘法、减法都是可以的,不过需要显示给出,如果不给出,编译器默认的就是加法运算。不过我们一般用加法比较多。

       说完了这个,我们简单说一下具体是怎么计算的:(注意一下ARGB顺序)

       假设4维向量SrcColor=(As,Rs, Gs, Bs),SrcBlend=(S4, S1, S2, S3), DestColor=(Ad, Rd, Gd,Bd),DestBlend(D4, D1, D2, D3),则混合颜色Color可用4维向量表示为:
       Color = (As * S4 + Ad *D4, Rs * S1 + Rd * D1, Gs * S2 + Gd * D2, Bs * S3 + Bd * D3)

       注:Direct3D中依然是用Alpha通道来实现多个像素颜色值的融合,每个像素都包含四个分量:Alpha分量、红色分量、绿色分量和蓝色分量(即ARGB四分量)

       当然这是一般的公式,但是我们对于融合因子的取值直接可以用库之中定义的宏,不用自己去定义了,一般来说我们在实际运用的过程之中主要用一下两个融合因子比较多,大多数情况都可以解决了。

D3DBLEND_SRCALPHA融合因子=(1-A_src,A_src,A_src,A_src)
D3DBLEND_INVSRCALPHA 融合因子=(1-A_src,1-A_src,1-A_src,1-A_src)

不过还是给出所有的融合因子的取值:

typedef enum D3DBLEND {
  D3DBLEND_ZERO              = 1,
  D3DBLEND_ONE               = 2,
  D3DBLEND_SRCCOLOR          = 3,
  D3DBLEND_INVSRCCOLOR       = 4,
  D3DBLEND_SRCALPHA          = 5,
  D3DBLEND_INVSRCALPHA       = 6,
  D3DBLEND_DESTALPHA         = 7,
  D3DBLEND_INVDESTALPHA      = 8,
  D3DBLEND_DESTCOLOR         = 9,
  D3DBLEND_INVDESTCOLOR      = 10,
  D3DBLEND_SRCALPHASAT       = 11,
  D3DBLEND_BOTHSRCALPHA      = 12,
  D3DBLEND_BOTHINVSRCALPHA   = 13,
  D3DBLEND_BLENDFACTOR       = 14,
  D3DBLEND_INVBLENDFACTOR    = 15,
  D3DBLEND_SRCCOLOR2         = 16,
  D3DBLEND_INVSRCCOLOR2      = 17,
  D3DBLEND_FORCE_DWORD       = 0x7fffffff 
} D3DBLEND, *LPD3DBLEND;

        说完了这些,具体到用上面可以分为以下3步:

        第一步:启用Alpha混合,因为它默认状态是关闭的

m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
        第二步:设置融合因子
//设置融合因子  
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);    
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
        第三步:设置Alpha融合运算方式

g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_SUBTRACT);
        其实严格来说还有第四部,使用完了时候别忘了关闭Alpha混合

m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
       最后说一下使用Alpha融合时,需要明确Alpha值的来源。我们在设置一个对象的颜色属性时,有三种方式:1.顶点颜色:这个是最古老的方法,也是最麻烦的。最早使用顶点缓冲区或者索引缓冲区绘图的时候设置过定点属性,其中有颜色属性,可以设置Alpha值。2.光照和材质:材质中各种光的反射系数是一个四元组,其中就包含了Alpha值。3.纹理:最容易的就是设置纹理来确定一个模型的颜色,所以这个也是最常用的。

      既然有三种设置对象的颜色Alpha值的方式,而且常用程度是纹理>光照材质>顶点颜色,所以Alpha值的来源顺序也就很明了了,如果有纹理,那就从纹理获取,如果没有纹理,那就从光照材质中获取,如果光照材质也没有,那就从顶点属性中获取。

      好了,铺垫就暂时说这么多了,接下来我们就一起看一看比较重要的代码:

先来看一看整个类的设计:

struct GUIVERTEX
{
	float x, y, z, rhw;
	unsigned long color;
	float tu, tv;
};
#define D3DFVF_GUI (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)

//控件属性结构体
struct GUICONTROL
{
	//操作类型,ID和颜色
	int m_type;							//控件类型
	int m_id;							//控件ID
	unsigned long m_color;						//控件颜色
	int m_listID;							//如果是文字的话,这个变量就表示它正在使用的字体,否则就表示顶点缓存
	float m_xPos, m_yPos;						//控件的起始位置
	float m_width, m_height;					//控件的高度和宽度
	wchar_t * m_text;						//文字内容
	LPDIRECT3DTEXTURE9 m_background;				//控件背景填充图像
	LPDIRECT3DTEXTURE9 m_upTex, m_downTex, m_overTex;		//存放按键按钮弹起、按键按下以及鼠标经过的3张纹理图
};
class D3DGUIClass
{
public:
	D3DGUIClass(LPDIRECT3DDEVICE9 pd3dDevice, int width, int height);
	virtual ~D3DGUIClass(){ ClearUp(); }

private:
	LPDIRECT3DDEVICE9			m_pd3dDevice;			//D3D设备
	LPD3DXFONT *				m_pFonts;			//D3D字体对象
	GUICONTROL *				m_pControls;			//控件对象
	LPDIRECT3DVERTEXBUFFER9 *		m_pVertexBuffer;		//顶点缓存对象指针
	GUICONTROL				m_Background;			//背景图对象
	LPDIRECT3DVERTEXBUFFER9			m_BackgroundBuffer;		//背景图缓冲区对象

	bool m_bIsBackgroundUsed;		//一个标识,用于标记是否使用了背景
	int m_nTotalFontNum;			//字体数目计数器
	int m_nTotalControlNum;			//控件数目计数器
	int m_nTotalBufferNum;			//缓冲区数目计数器

	int m_nWindowWidth;			//窗口宽度
	int m_nWindowHeight;			//窗口高度
	
public:
	LPDIRECT3DDEVICE9  GetD3dDevice()  { return m_pd3dDevice; }	//返回D3d设备对象
	GUICONTROL * GetBackground() { return &m_Background; }		//返回背景的函数
	LPDIRECT3DVERTEXBUFFER9 GetBackgroundBuffer() { return m_BackgroundBuffer; }//返回背景缓冲区对象的函数

	int GetTotalFontNum() { return m_nTotalFontNum; }		//返回所有字体数目的函数
	int GetTotalControlNum() { return m_nTotalControlNum; }		//返回所有控件数目的函数
	int GetTotalBufferNum() { return m_nTotalBufferNum; }		//返回所有缓冲区数目的函数
	int GetWindowWidth() { return m_nWindowWidth; }			//返回窗口宽度的函数
	int GetWindowHeight() { return m_nWindowHeight; }		//返回窗口高度的函数

	bool IsBackGroundUsed() { return m_bIsBackgroundUsed; }		//返回背景是否在使用的bool值得函数
	void SetWindowSize(int width, int height) { m_nWindowWidth = width; m_nWindowHeight = height; } //设置窗口宽度和高度的函数

	//返回字体ID的函数
	LPD3DXFONT GetFont(int id)					//返回字体ID函数
	{
		if (id < 0 || id >= m_nTotalFontNum)
		{
			return NULL;
		}
		return m_pFonts[id];
	}

	GUICONTROL * GetGUIControl(int id)				//返回GUI控件和ID函数
	{
		if (id < 0 || id >= m_nTotalControlNum)
		{
			return NULL;
		}
		return &m_pControls[id];
	}

	LPDIRECT3DVERTEXBUFFER9 GetVertexBuffer(int id)		//返回顶点缓存ID函数
	{
		if (id < 0 || id >= m_nTotalBufferNum)
		{
			return NULL;
		}
		return m_pVertexBuffer[id];
	}

	bool CreateFontText(wchar_t * fontName, int size, int * fontID);//字体创建函数
	bool AddBackground(wchar_t * fileName);				//GUI背景添加函数
	bool AddStaticText(int id, wchar_t * text, float x, float y, unsigned long color, int fontID);	//添加静态文本函数
	bool AddButton(int id, float x, float y, wchar_t * up, wchar_t * over, wchar_t * down);	//添加按钮函数
	void ClearUp();
};

        看完了类的设计,接下来就是按照框架填内容就行了,下面给出几个我认为比较关键的函数实现的代码 :

        个人认为里面最关键的就是添加按钮控件的代码了,下面给出来

//创建按钮控件的函数
bool D3DGUIClass::AddButton(int id, float x, float y, wchar_t * Mouseup, wchar_t * Mouseover, wchar_t * Mousedown)
{
	if (!Mouseup || !Mouseover || !Mousedown)		//若有一个为空,则返回
		return false;

	if (!m_pControls)
	{
		m_pControls = new GUICONTROL[1];
		if (!m_pControls)
			return false;
		memset(&m_pControls[0], 0, sizeof(GUICONTROL));
	}
	else
	{
		GUICONTROL * temp = NULL;
		temp = new GUICONTROL[m_nTotalControlNum + 1];
		if (!temp)		return false;
		memset(temp, 0, sizeof(GUICONTROL)* (m_nTotalControlNum + 1));
		memcpy(temp, m_pControls, sizeof(GUICONTROL)* m_nTotalControlNum);
		delete[] m_pControls;
		m_pControls = temp;
	}
	//设置所有需要渲染的数据填入
	m_pControls[m_nTotalControlNum].m_type = UGP_GUI_BUTTON;
	m_pControls[m_nTotalControlNum].m_id = id;
	m_pControls[m_nTotalControlNum].m_xPos = x;
	m_pControls[m_nTotalControlNum].m_yPos = y;
	m_pControls[m_nTotalControlNum].m_listID = m_nTotalBufferNum;	//编号,相当于容器之中size的作用

	//从文件之中加载纹理
	if (D3DXCreateTextureFromFile(m_pd3dDevice, Mouseup, &m_pControls[m_nTotalControlNum].m_upTex) != D3D_OK)
	{
		return false;
	}

	if (D3DXCreateTextureFromFile(m_pd3dDevice, Mouseover, &m_pControls[m_nTotalControlNum].m_overTex) != D3D_OK)
	{
		return false;
	}

	if (D3DXCreateTextureFromFile(m_pd3dDevice, Mousedown, &m_pControls[m_nTotalControlNum].m_downTex) != D3D_OK)
	{
		return false;
	}

	unsigned long white = D3DCOLOR_XRGB(255, 255, 255);

	//获取一下图形的高度和宽度
	D3DSURFACE_DESC desc;
	m_pControls[m_nTotalControlNum].m_upTex->GetLevelDesc(0, &desc);
	float width = (float)desc.Width;
	float height = (float)desc.Height;
	m_pControls[m_nTotalControlNum].m_width = width;
	m_pControls[m_nTotalControlNum].m_height = height;

	GUIVERTEX obj[] = {
		{ x + width, y + 0, 0.0f, 1.0f, white, 1.0f, 0.0f },
		{ x + 0, y + 0, 0.0f, 1.0f, white, 0.0f, 0.0f },
		{ x + width, y + height, 0.0f, 1.0f, white, 1.0f, 1.0f },
		{ x + 0, y + height, 0.0f, 1.0f, white, 0.0f, 1.0f }
	};
	//创建顶点缓存
	if (!m_pVertexBuffer)
	{
		m_pVertexBuffer = new LPDIRECT3DVERTEXBUFFER9[1];
		if (!m_pVertexBuffer)
			return false;
	}
	else
	{
		LPDIRECT3DVERTEXBUFFER9 * temp = NULL;
		temp = new LPDIRECT3DVERTEXBUFFER9[m_nTotalBufferNum + 1];
		if (!temp)
			return false;
		memcpy(temp, m_pVertexBuffer, sizeof(LPDIRECT3DVERTEXBUFFER9)* m_nTotalBufferNum);
		delete[] m_pVertexBuffer;
		m_pVertexBuffer = temp;
	}
	//创建一个顶点缓存对象
	if (FAILED(m_pd3dDevice->CreateVertexBuffer(sizeof(obj), 0, D3DFVF_GUI, D3DPOOL_DEFAULT, &m_pVertexBuffer[m_nTotalBufferNum], NULL)))
	{
		return false;
	}
	//填充缓存对象
	void * ptr;
	m_pVertexBuffer[m_nTotalBufferNum]->Lock(0, sizeof(obj), (void **)&ptr, 0);
	memcpy(ptr, obj, sizeof(obj));
	m_pVertexBuffer[m_nTotalBufferNum]->Unlock();
	//变量更新
	m_nTotalBufferNum++;
	m_nTotalControlNum++;
	return true;
}
            其余的在界面上添加控件的代码与之类似,大家可以发挥自己想象力自由添加。
       剩下的就是最后一个内容了,就是如何实现页面直接的切换,这个函数怎么写呢?要实现这个函数,肯定要做的一点就是定义每个页面的状态的名字,这个状态你可以作为宏定义在头文件之中之中给出,一般来讲有几个页面就定义结构状态,当鼠标按下之后,利用按键处理函数捕捉到这一消息,更改状态,这样就可以实现页面的切换了。
      下面我们给出这个函数的实现:

//这个函数主要封装渲染整个GUI系统,同样还为控件调用回调函数
void RenderGUI(D3DGUIClass * Gui, bool LIsMouBDown, int mouseX, int mouseY, void(*CallbackFun)(int id, int state))
{
	//同样的先进行参数检测
	if (!Gui)
		return;
	LPDIRECT3DDEVICE9  device = Gui->GetD3dDevice();
	if (!device)
		return;
	//绘制背景
	GUICONTROL * Background = Gui->GetBackground();
	LPDIRECT3DVERTEXBUFFER9 bdBuffer = Gui->GetBackgroundBuffer();
	//已经创建出来的才绘制,所以先进行判断
	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 (NULL == pControl)
			continue;
		//根据不同的类型做不同的操作
		switch (pControl->m_type)
		{
		case UGP_GUI_STATICTEXT:
			//这种情况下获取字体对象
			pFont = Gui->GetFont(pControl->m_listID);
			if (NULL == pFont)
				continue;
			//开始设置字体的位置
			fontPosition.left = (long)pControl->m_xPos;
			fontPosition.top = (long)pControl->m_yPos;
			//显示文字
			pFont->DrawText(NULL, pControl->m_text, -1, &fontPosition, DT_LEFT | DT_TOP, pControl->m_color);
			break;
		case UGP_GUI_BUTTON:
			status = UGP_BUTTON_UP;
			//获取按钮所对应的顶点缓存
			pBuffer = Gui->GetVertexBuffer(pControl->m_listID);
			if (NULL == 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 (LIsMouBDown)
					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;
		default:
			break;
		}
		//调用回调函数处理控件消息
		if (CallbackFun)
			CallbackFun(pControl->m_id, status);
	}
}
              利用这个函数我们就可以实现多页面直接的切换了,想一想还是有点开心的!
         最后用一张图作为今天内容的结束吧!更好地明天再等我们,加油!

DirectX3D游戏制作之---游戏界面的设计_第7张图片


你可能感兴趣的:(DirectX3D游戏学习)