HGE系列之七管中窺豹(圖形界面)ide
此次的HGE源碼之旅,讓咱們來看看HGE的圖形用戶界面(GUI)的實現,話說電腦技術發展至今,當年轟動一時的圖形用戶界面,而今早已司空見慣,想來不得不感嘆一下技術的突飛猛進啊……HGE做爲一款出色的2D遊戲引擎,GUI方面的支持天然不在話下,而且從實現方面來講仍是至關具備擴展性的 :)好了,簡略的介紹就到此爲止吧,就讓咱們立刻來看看源碼 :)函數
類名:hgeGUIObject動畫
功能:界面對象基類,像文本、按鈕之類的GUI控件皆派生於她ui
頭文件:hge/hge181/include/hgegui.hthis
實現文件:無spa
如下是該類的頭文件聲明,注意一下其中的各個虛擬函數:.net
class hgeGUIObject指針
{ orm
public:對象
// 默認的構造函數,用以建立hge對象以及設置顏色(color)
hgeGUIObject() { hge=hgeCreate(HGE_VERSION); color=0xFFFFFFFF; }
// 虛擬析構函數,用以釋放以前建立的HGE
virtual ~hgeGUIObject() { hge->Release(); }
// 純虛函數Render,功能顧名思義了 :)
virtual void Render() = 0;
// 根據間隔時間(dt)來更新內部狀態
virtual void Update(float dt) {}
// 「進入」此界面對象時執行的函數
virtual void Enter() {}
// 「離開」此界面對象時執行的函數
virtual void Leave() {}
// 重置界面對象狀態
virtual void Reset() {}
// 是否Enter或者Leave的調用已經結束
virtual bool IsDone() { return true; }
// 當控件獲取或失去焦點時調用
virtual void Focus(bool bFocused) {}
// 當鼠標進入或者離開控件範圍時調用
virtual void MouseOver(bool bOver) {}
// 響應鼠標移動
virtual bool MouseMove(float x, float y) { return false; }
// 響應鼠標左鍵
virtual bool MouseLButton(bool bDown) { return false; }
// 響應鼠標右鍵
virtual bool MouseRButton(bool bDown) { return false; }
// 響應鼠標滑輪
virtual bool MouseWheel(int nNotches) { return false; }
// 響應按鍵
virtual bool KeyClick(int key, int chr) { return false; }
// 設置控件顏色
virtual void SetColor(DWORD _color) { color=_color; }
// 控件id
int id;
// 是否靜態
bool bStatic;
// 是否可見
bool bVisible;
// 是否使能
bool bEnabled;
// 區域範圍,使用了hgeRect :)
hgeRect rect;
// 控件顏色,簡單的使用了一個DWORD進行表示
DWORD color;
// hgeGUI對象,用於管理hgeGUIObject,詳述見後
hgeGUI *gui;
// 控件鏈表後向指針
hgeGUIObject *next;
// 控件鏈表前向指針
hgeGUIObject *prev;
protected:
// 將複製構造函數及對應的賦值函數定義爲保護類型,
// 禁止外部的調用(子類和自身成員函數除外)
hgeGUIObject(const hgeGUIObject &go);
hgeGUIObject& operator= (const hgeGUIObject &go);
// 靜態hge指針,用於獲取hge提供的核心(core)函數
static HGE *hge;
};
一目瞭然,這個控件基類仍是相對簡單的,各類成員函數和變量的意義都十分清晰,藉助於上面的註釋和HGE文檔的幫助,相信你們都可以了其大意,惟一想再說一說的,只是這麼一段:
// 將複製構造函數及對應的賦值函數定義爲保護類型,
// 禁止外部的調用(子類和自身成員函數除外)
hgeGUIObject(const hgeGUIObject &go);
hgeGUIObject& operator= (const hgeGUIObject &go);
正如註釋所解,這段代碼的做用是屏蔽外界對於這兩個函數的調用,可是做者在這方面仍是作的不是特別完全,由於將其設置爲保護域(protected)並不能防止派生類對其的不當調用,雖然因爲這兩個函數源代碼中只是進行了聲明,並未進行實現,這意味着即便派生類中調用了這兩個函數,也總會遇到連接錯誤(Link),可是我想爲何不直接作的更完全一點呢?很簡單,將保護域(protected)聲明爲私有域(private)就OK了,固然最後的
static HGE *hge;
前仍是要再加一個「protected:」,不然派生類就用不了hge了(至少不能直接使用) :)
類名:hgeGUI
功能:界面控件管理類,用以控制各個界面
頭文件:hge/hge181/include/hgegui.h
實現文件:hge/hge181/src/helpers/hgegui.cpp
一樣的步驟,讓咱們想看一看該類的聲明:
class hgeGUI
{
public:
hgeGUI();
~hgeGUI();
// 添加控件
void AddCtrl(hgeGUIObject *ctrl);
// 移除控件
void DelCtrl(int id);
// 經過控件id獲取相對應控件
hgeGUIObject* GetCtrl(int id) const;
// 移動控件
void MoveCtrl(int id, float x, float y);
// 設置控件的顯示或隱藏
void ShowCtrl(int id, bool bVisible);
// 使能控件
void EnableCtrl(int id, bool bEnabled);
// 設置控件的顯示模式
void SetNavMode(int mode);
// 設置鼠標光標
void SetCursor(hgeSprite *spr);
// 設置全部控件的顏色
void SetColor(DWORD color);
// 設置控件的失焦穫焦狀態
void SetFocus(int id);
// 獲取控件的失焦穫焦狀態
int GetFocus() const;
// 開啓GUI「進入」動畫
void Enter();
// 開啓GUI「離開」動畫
void Leave();
// 重置控件狀態
void Reset();
// 移動全部控件
void Move(float dx, float dy);
// 更新控件狀態
int Update(float dt);
// 繪製控件
void Render();
private:
// 私有化複製構造函數和賦值函數,防止外界調用
hgeGUI(const hgeGUI &);
hgeGUI& operator= (const hgeGUI&);
// 處理控件邏輯
bool ProcessCtrl(hgeGUIObject *ctrl);
static HGE *hge;
// 控件鏈表(雙向)表頭
hgeGUIObject *ctrls;
// 指向被鎖定的控件
hgeGUIObject *ctrlLock;
// 指向獲的焦點的控件
hgeGUIObject *ctrlFocus;
// 指向鼠標移至其上的控件
hgeGUIObject *ctrlOver;
// 顯示模式
int navmode;
// Enter/Leave 標記
int nEnterLeave;
// 鼠標光標
hgeSprite *sprCursor;
// 鼠標x、y座標
float mx,my;
// 鼠標滑輪移動值
int nWheel;
// 鼠標左鍵是否按下及釋放
bool bLPressed, bLReleased;
// 鼠標右鍵是否按下及釋放
bool bRPressed, bRReleased;
};
能夠看到。hgeGUI的代碼也依然十分清晰,在此也沒必要完整列出實現的全部源碼,挑選一些值得注意的成員函數實現我想便足矣足矣:
首先是構造函數,很簡單,建立HGE並初始化成員變量,不過若是使用成員初始化列表的話效率會略高一些 :)
hgeGUI::hgeGUI()
{
hge=hgeCreate(HGE_VERSION);
ctrls=0;
ctrlLock=0;
ctrlFocus=0;
ctrlOver=0;
navmode=HGEGUI_NONAVKEYS;
bLPressed=bLReleased=false;
bRPressed=bRReleased=false;
nWheel=0;
mx=my=0.0f;
nEnterLeave=0;
sprCursor=0;
}
析構函數也很日常,除了釋放資源以外再無其餘:
hgeGUI::~hgeGUI()
{
hgeGUIObject *ctrl=ctrls, *nextctrl;
// 依次釋放鏈表(雙向)元素
while(ctrl)
{
nextctrl=ctrl->next;
delete ctrl;
ctrl=nextctrl;
}
// 釋放hge
hge->Release();
}
接着讓咱們看看AddCtrl :
void hgeGUI::AddCtrl(hgeGUIObject *ctrl)
{
hgeGUIObject *last=ctrls;
// 設置控件的hgeGUI指針
ctrl->gui=this;
// 若是指針鏈表爲空,則設置指針鏈表
if(!ctrls)
{
ctrls=ctrl;
ctrl->prev=0;
ctrl->next=0;
}
// 不然,將控件添加至鏈表尾部
else
{
while(last->next) last=last->next;
last->next=ctrl;
ctrl->prev=last;
ctrl->next=0;
}
}
一樣道理,void hgeGUI::DelCtrl(int id) 也即是依次查找對應id的控件,並刪除之。
而 hgeGUIObject* hgeGUI::GetCtrl(int id) const 這個成員變量則更加簡單,依次查找並返回控件指針,若是未有找到,則返回NULL。
移動控件的函數也並不困難:
void hgeGUI::MoveCtrl(int id, float x, float y)
{
// 首先獲取控件的指針
hgeGUIObject *ctrl=GetCtrl(id);
// 將控件的區域範圍設置爲 x、y 座標爲起點
ctrl->rect.x2=x + (ctrl->rect.x2 - ctrl->rect.x1);
ctrl->rect.y2=y + (ctrl->rect.y2 - ctrl->rect.y1);
ctrl->rect.x1=x;
ctrl->rect.y1=y;
}
以後的一些設置函數相對簡單,僅是賦值而已,在此再也不羅列。有興趣的朋友能夠看一看源文件 :)
Reset函數將控件鏈表中的元素依次重置:
void hgeGUI::Reset()
{
hgeGUIObject *ctrl=ctrls;
while(ctrl)
{
ctrl->Reset();
ctrl=ctrl->next;
}
ctrlLock=0;
ctrlOver=0;
ctrlFocus=0;
}
Move函數則是將全部控件依次移動dx、dy位移:
void hgeGUI::Move(float dx, float dy)
{
hgeGUIObject *ctrl=ctrls;
while(ctrl)
{
ctrl->rect.x1 += dx;
ctrl->rect.y1 += dy;
ctrl->rect.x2 += dx;
ctrl->rect.y2 += dy;
ctrl=ctrl->next;
}
}
讓咱們再來看看SetFoucus函數:
void hgeGUI::SetFocus(int id)
{
// 獲取給定id的控件指針
hgeGUIObject *ctrlNewFocus=GetCtrl(id);
// 若是當前的控件已是焦點控件,則當即返回
if(ctrlNewFocus==ctrlFocus) return;
// 若是獲取的控件爲空
if(!ctrlNewFocus)
{
// 則將原先的焦點控件失焦
if(ctrlFocus) ctrlFocus->Focus(false);
// 並將焦點控件置空
ctrlFocus=0;
}
// 不然(找到了),若是空間不是靜態的,而且可見、使能
else if(!ctrlNewFocus->bStatic && ctrlNewFocus->bVisible && ctrlNewFocus->bEnabled)
{
// 原先的焦點控件失焦
if(ctrlFocus) ctrlFocus->Focus(false);
// 當前的控件獲焦
if(ctrlNewFocus) ctrlNewFocus->Focus(true);
// 設置焦點控件指針
ctrlFocus=ctrlNewFocus;
}
}
看來代碼依然十分清晰明瞭 :)
接着即是Enter和Leave函數:
void hgeGUI::Enter()
{
hgeGUIObject *ctrl=ctrls;
// 依次調用控件鏈表中元素的Enter函數
while(ctrl)
{
ctrl->Enter();
ctrl=ctrl->next;
}
// Enter/Leave標記
nEnterLeave=2;
}
void hgeGUI::Leave()
{
hgeGUIObject *ctrl=ctrls;
// 依次調用控件鏈表中的Leave函數
while(ctrl)
{
ctrl->Leave();
ctrl=ctrl->next;
}
ctrlFocus=0;
ctrlOver=0;
ctrlLock=0;
// Enter/Leave標記
nEnterLeave=1;
}
Render函數與之相似,亦是遍歷並渲染的那一套,在此就不列出代碼了 :)
用於處理控件的ProcessCtrl以下:
bool hgeGUI::ProcessCtrl(hgeGUIObject *ctrl)
{
bool bResult=false;
// 處理鼠標左鍵按下
if(bLPressed) { ctrlLock=ctrl;SetFocus(ctrl->id);bResult=bResult || ctrl->MouseLButton(true); }
// 處理鼠標右鍵按下
if(bRPressed) { ctrlLock=ctrl;SetFocus(ctrl->id);bResult=bResult || ctrl->MouseRButton(true); }
// 處理鼠標左鍵釋放
if(bLReleased) { bResult=bResult || ctrl->MouseLButton(false); }
// 處理鼠標右鍵釋放
if(bRReleased) { bResult=bResult || ctrl->MouseRButton(false); }
// 處理鼠標滑輪
if(nWheel) { bResult=bResult || ctrl->MouseWheel(nWheel); }
// 最後處理鼠標的移動
bResult=bResult || ctrl->MouseMove(mx-ctrl->rect.x1,my-ctrl->rect.y1);
// 只要前面有一個消息處理成功,則bResult爲真
return bResult;
}
註釋中基本道明瞭這個函數的處理流程和做用,最後,讓咱們來看一看hgeGUI的Update函數,不像以前遇到的成員函數,這但是個你們夥,因此作好準備了 :
首先,他獲取鼠標的各個按鍵狀況:
// 更新鼠標變量
hge->Input_GetMousePos(&mx, &my);
bLPressed = hge->Input_KeyDown(HGEK_LBUTTON);
bLReleased = hge->Input_KeyUp(HGEK_LBUTTON);
bRPressed = hge->Input_KeyDown(HGEK_RBUTTON);
bRReleased = hge->Input_KeyUp(HGEK_RBUTTON);
nWheel=hge->Input_GetMouseWheel();
接着,其遍歷控件鏈表並依次調用各個控件的Update函數,用以更新控件的內部狀態:
// Update all controls
ctrl=ctrls;
while(ctrl)
{
ctrl->Update(dt);
ctrl=ctrl->next;
}
而後,處理Enter和Leave
// Handle Enter/Leave
if(nEnterLeave)
{
ctrl=ctrls; bDone=true;
// 查看各個控件的Enter和Leave是否結束
while(ctrl)
{
if(!ctrl->IsDone()) { bDone=false; break; }
ctrl=ctrl->next;
}
// 若是任意一個控件的Enter/Leave 沒有結束便返回0
if(!bDone) return 0;
else
{
// 若是nEnterLeave爲1(即調用Leave時),則返回-1
if(nEnterLeave==1) return -1;
// 不然返回0
else nEnterLeave=0;
}
}
再接着,即是處理鍵盤按鍵:
// Handle keys
key=hge->Input_GetKey();
// 若是顯示模式爲左右模式而且Left鍵被按下
// 或者顯示模式爲上下模式而且Up鍵被按下
if(((navmode & HGEGUI_LEFTRIGHT) && key==HGEK_LEFT) ||
((navmode & HGEGUI_UPDOWN) && key==HGEK_UP))
{
// 獲取焦點控件
ctrl=ctrlFocus;
// 若是焦點控件爲空,則嘗試使用第一個鏈表控件元素
if(!ctrl)
{
ctrl=ctrls;
// 若是控件仍是爲空(此時沒有控件),則直接返回0
if(!ctrl) return 0;
}
// 查找位於焦點控件以前的符合顯示條件的控件
do {
ctrl=ctrl->prev;
if(!ctrl && ((navmode & HGEGUI_CYCLED) || !ctrlFocus))
{
ctrl=ctrls;
while(ctrl->next) ctrl=ctrl->next;
}
if(!ctrl || ctrl==ctrlFocus) break;
} while(ctrl->bStatic==true || ctrl->bVisible==false || ctrl->bEnabled==false);
// 從新設置焦點控件
if(ctrl && ctrl!=ctrlFocus)
{
if(ctrlFocus) ctrlFocus->Focus(false);
if(ctrl) ctrl->Focus(true);
ctrlFocus=ctrl;
}
}
// 若是顯示模式爲左右模式而且Right鍵被按下
// 或者顯示模式爲上下模式而且Down鍵被按下
else if(((navmode & HGEGUI_LEFTRIGHT) && key==HGEK_RIGHT) ||
((navmode & HGEGUI_UPDOWN) && key==HGEK_DOWN))
{
ctrl=ctrlFocus;
if(!ctrl)
{
ctrl=ctrls;
if(!ctrl) return 0;
// 獲取控件鏈表最後的元素
while(ctrl->next) ctrl=ctrl->next;
}
// 查找位於焦點控件以後的符合顯示條件的控件
do {
ctrl=ctrl->next;
if(!ctrl && ((navmode & HGEGUI_CYCLED) || !ctrlFocus)) ctrl=ctrls;
if(!ctrl || ctrl==ctrlFocus) break;
} while(ctrl->bStatic==true || ctrl->bVisible==false || ctrl->bEnabled==false);
// 從新設置焦點控件
if(ctrl && ctrl!=ctrlFocus)
{
if(ctrlFocus) ctrlFocus->Focus(false);
if(ctrl) ctrl->Focus(true);
ctrlFocus=ctrl;
}
}
// 處理其餘按鍵狀況
else if(ctrlFocus && key && key!=HGEK_LBUTTON && key!=HGEK_RBUTTON)
{
if(ctrlFocus->KeyClick(key, hge->Input_GetChar())) return ctrlFocus->id;
}
最後則是對於鼠標的處理,使用了以前所見的ProcessCtrl函數,不過私認爲這個函數的名字不是太穩當,由於其內部只是處理裏鼠標的邏輯,因此稱其爲ProcessMouseCtrl之類的名字可能更適合,再者,既然單獨分離出了一個鼠標控制的函數,那麼其餘類型的控制爲什麼也不依勢分離一下呢?譬如來個ProcessKeyboardCtrl,固然,這僅是一家之言 :)
// Handle mouse
bool bLDown = hge->Input_GetKeyState(HGEK_LBUTTON);
bool bRDown = hge->Input_GetKeyState(HGEK_RBUTTON);
// 若是鎖定控件不爲空
if(ctrlLock)
{
ctrl=ctrlLock;
// 若是鼠標左鍵和右鍵都不爲空,則置鎖定控件爲空
if(!bLDown && !bRDown) ctrlLock=0;
// 使用ProcessCtrl處理鼠標按鍵
if(ProcessCtrl(ctrl)) return ctrl->id;
}
else
{
// Find last (topmost) control
ctrl=ctrls;
if(ctrl)
// 獲取最後一個控件元素
while(ctrl->next) ctrl=ctrl->next;
while(ctrl)
{
// 若是鼠標位於控件區域內而且控件使能
if(ctrl->rect.TestPoint(mx,my) && ctrl->bEnabled)
{
// 從新設置ctrlOver控件
if(ctrlOver != ctrl)
{
if(ctrlOver) ctrlOver->MouseOver(false);
ctrl->MouseOver(true);
ctrlOver=ctrl;
}
// 調用ProcessCtrl處理鼠標按鍵
if(ProcessCtrl(ctrl)) return ctrl->id;
else return 0;
}
// 處理前一個控件
ctrl=ctrl->prev;
}
// 處理完畢以後,將ctrlOver控件置爲空
if(ctrlOver) {ctrlOver->MouseOver(false); ctrlOver=0;}
}
呼,終算是將hgeGUI的Update函數講完了,不過咱們還不能休息,由於目前講述的兩個類還不能讓咱們在屏幕上組織圖形界面(不要忘了hgeGUIObject仍是個虛基類!),咱們必須手工編寫派生自hgeGUIObject的子類,並重載那些hgeGUIObject中留下的虛擬函數,看來工做量不小啊……幸而HGE爲咱們已經編寫好了四個派生自hgeGUIObject的子類,他們分別是:hgeGUIText(文本)、hgeGUIButton(按鈕)、hgeGUISlider(滾動條)以及hgeGUIListbox(列表框),限於篇幅在此不能所有講述,但因爲其間原理大同小異,因此讓咱們先細看看其中比較簡單的hgeGUIText,相信其餘三個的類亦能舉一反三 :)
類名:hgeGUIText
功能:文本類
頭文件:hge/hge181/include/hgeguictrls.h
實現文件:hge/hge181/src/helpers/hgeguictrls.cpp
首先天然是頭文件的聲明:
class hgeGUIText : public hgeGUIObject
{
public:
hgeGUIText(int id, float x, float y, float w, float h, hgeFont *fnt);
// 設置對其方式
void SetMode(int _align);
// 設置文本內容
void SetText(const char *_text);
// 不定參數形式的輸出函數
void printf(const char *format, ...);
// 渲染函數
virtual void Render();
private:
// 使用hgeFont進行渲染
hgeFont* font;
// 文本顯示的起始座標
float tx, ty;
// 對其方式
int align;
// 文本緩衝區
char text[256];
};
「清澈見底」 :)實現也是分外清明:
// 簡單的構造函數,仍是使用成員初始化列表更好一些 :)
hgeGUIText::hgeGUIText(int _id, float x, float y, float w, float h, hgeFont *fnt)
{
id=_id;
bStatic=true;
bVisible=true;
bEnabled=true;
rect.Set(x, y, x+w, y+h);
font=fnt;
tx=x;
// 用於居中顯示
ty=y+(h-fnt->GetHeight())/2.0f;
text[0]=0;
}
// 設置對其模式,注意座標的設置
void hgeGUIText::SetMode(int _align)
{
align=_align;
if(align==HGETEXT_RIGHT) tx=rect.x2;
else if(align==HGETEXT_CENTER) tx=(rect.x1+rect.x2)/2.0f;
else tx=rect.x1;
}
// 僅是一個strcpy,應該加個inline :)
void hgeGUIText::SetText(const char *_text)
{
strcpy(text, _text);
}
// 使用vsprintf完成任務,一樣應該加個inline :)
void hgeGUIText::printf(const char *format, ...)
{
vsprintf(text, format, (char *)&format+sizeof(format));
}
// 依舊簡單,設置顏色,而後渲染 !
void hgeGUIText::Render()
{
font->SetColor(color);
font->Render(tx,ty,align,text);
}
好了,至此hge的GUI部分也算是講解完畢了,一路上並不輕鬆,可是也未有遇到多大的羈絆,不過相信你們對於HGE的GUI系統應該有了一些認識,另一提的即是,HGE自帶的教程(tutorials)中,tutorial6正好是關於GUI的一個示例,有興趣的朋友能夠看一看 :)
OK,就此停筆了,我以爲我說的已經夠多,再說一下去本身都要受不了了,不過呢,最後我仍是得照例來上一句:下次再見嘍 :)
本文同步分享在 博客「tkokof1」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。