DirectX3D遊戲製作之---遊戲介面的設計
前言:
再深沉的感情,再真摯的牽掛,還是會有分開的一天......到頭來又怎麼敵得過生離死別......
-------韓菱紗《仙劍奇俠傳四》
PS:為了方便大家閱讀,個人認為比較重要的內容-------紅色字型顯示
----------------------------------------------------------------------------------------------------
-------------------------------------------分 割 線-----------------------------------------------
恰逢誅仙正在熱映,一時興起,就製作了一個關於誅仙的遊戲GUI介面,當然這個只是實現了個大概,而且素材挺難找的,有些找到的素材還用不了,有些甚至要付費(這裡不得不吐槽一下,大家把東西拿出來分享多好啊,這樣中國的計算機行業才會更好地進步)
首先來看一下主頁面:
接下來再來看一下開始介面
點選Level1按鈕之後,就會進入遊戲載入介面:
在載入遊戲這個介面出現大約兩秒之後,正式進入遊戲介面 (這次的遊戲場景比較簡單)
再來看一下其他的幾個介面(載入介面,以及遊戲設定介面)
-----------------------------------------------正文分割線-----------------------------------------------------
之前寫了一個關於3D場景的程式,裡面封裝了一些的類(視窗類、攝像機類、地形類、天空盒類、粒子類、.X檔案載入類以及骨骼動畫類等),已經可也實現一些簡單的場景了,今天換個方向,也就是相當於給之前的遊戲穿上一件好看的衣服,主要來講一講GUI介面設計
什麼是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是一個全域性例項控制代碼!當然由於我們是呼叫系統的函式建立的控制元件,所以建立的按鈕是這個樣子的:(如圖所示)
是不是覺得很醜呢?確實我們在玩遊戲的時候幾乎看不到這樣的按鈕,很顯然這樣的控制元件是很難出現在遊戲上的,有些玩家看到介面不好看,可能就不會去玩這個遊戲了,介面做的不好看,很有可能就會讓玩家失去玩的興趣,這樣估計也算不上一個成功的遊戲,畢竟每個人都是喜歡美的事物的,所以我們在遊戲之中不可能使用Win32之中自帶的控制元件了,所以這時候我們就應該使用自定義控制元件了,我們自己來設計控制元件。
關於控制元件種類有很多,我們最重要的是關注使用者介面控制元件
它主要用於開發構建使用者介面(UI)的控制元件,幫助完成軟體開發中視窗、文字框、按鈕、下拉式選單等介面元素的開發,至於其他的一些控制元件(如:圖表控制元件、報表控制元件、表格控制元件、條形碼控制元件、影象處理控制元件等可以暫時不去管),關於控制元件的內容就暫時說到這裡,具體控制元件的設計,下面會講到。
Alpha混合技術
為什麼要用到Alpha混合呢?我們創建出來的控制元件在放到介面上之後,自然會給這個控制元件(按鈕)貼上紋理(也可以說給這個按鈕貼上背景),通常來說,這些紋理都是一些與玩家互動相關內容,比如開始遊戲,讀取遊戲等內容,來看一個具體的介面,如圖所示:這是仙劍奇俠傳4的介面(這也是博主對於仙劍系列最喜歡,最有感覺的一部),在圖片中的橢圓形的,帶有新的開始、舊的回憶等這樣的文字的,可以點選的就是一個個按鈕(控制元件)。
從圖片之中我們可以看出,介面上的一個一個按鈕顯得很自然,所以創建出一個按鈕很簡單,給按鈕貼上紋理也不難,最重要的就是怎麼樣才可以讓按鈕與介面相融合,這樣才顯得不突兀,介面要的就是一種自然的感覺,讓人看了之後很賞心悅目,有一種很想玩的感覺,介面我認為最重要的就是簡潔、乾淨。關於仙俠類的遊戲,個人感覺最好是和中國傳統文化結合起來,仙俠遊戲也算是中國特有的吧!自然要體現出中國的古代畫風,沒必要向西方那樣的介面,雖然那樣的介面也很好看。不過就是要做的和別人不一樣。其實仙劍的介面畫風我還是挺喜歡的(個人對於武俠小說也還是比較喜歡的)。其實我感覺中國古代的畫風是水墨風格,這樣的風格用在仙俠類遊戲也不錯啊,江湖江湖,有江有湖,在帶上一些水墨風格,這樣豈不更好,看上去很乾淨!不知道大家是否看過電視《琅琊榜》,個人感覺這樣的畫風真的很不錯,不帶一點多餘的內容,寧靜、淡泊的畫風感覺就是江湖中人所夢寐以求的,為什麼玩遊戲,遊戲是一種體驗,要的就是生活之中體驗不到的感覺,如今的社會我們不乏色彩的缺失,到處都是那種五顏六色的感覺,尤其是仙俠遊戲,我們要的就是體會古人那樣在江湖上自由自在的感覺,所以我個人感覺畫風應該是個簡潔為主,顏色以淡色系為主,水墨畫風格是最適合不過了,個人感覺畫風也是《琅琊榜》收穫好評的一個重要原因吧!尤其是現在的電視很多都是濃妝豔抹(這裡我就不提某某導演喜歡鸚鵡那樣的畫風了),突然間出現這種帶有水墨畫風的電視劇,都會眼前一亮!
這樣的圖片是不是看了之後心情就會平靜很多,尤其是在如今的社會,大家都在忙碌的生活。如果有這樣一款以3D水墨風格的遊戲,讓我每天疲憊學習之後可以去這樣的“江湖”上走一走,我想我是會去的。當然咯這裡面的渲染技術要求應該會不一樣吧,不過還是要做的真實一些,裡面的人物著裝可以更加貼近古代風格一些,玩遊戲麼,最重要的就是休閒,最好是把遊戲當做是另一種方式的休息。
其實以下面這張圖片做遊戲登入介面也不錯啊,不過前提請胡歌來做一下形象代言
中國傳統文化強調的是朦朧感,有意境。所以個人感覺把畫風做成水墨畫的風格似乎也是挺好的,有意境。不過一般來說棋類的遊戲介面做成水墨畫的風格效果應該不錯吧!尤其是象棋這樣的帶有中國傳統文化的,把棋盤設計成水墨風格效果比較好(個人感覺而已)。
不知不覺又扯了好多其他的內容,接下來正式講一講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, <span style="font-family:Microsoft YaHei;">false</span>);
最後說一下使用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);
}
}
利用這個函式我們就可以實現多頁面直接的切換了,想一想還是有點開心的!最後用一張圖作為今天內容的結束吧!更好地明天再等我們,加油!