教學 06 – 選單製作
這個教學將展示如何製作自訂GUI組件和選單。
產生自訂的組件
首先,我們需要定義一個衍生自「hgeGUIObject」的組件:
class hgeGUIMenuItem : public hgeGUIObject { public: hgeGUIMenuItem(int id, hgeFont *fnt, HEFFECT snd, float x, float y, float delay, char *title); virtual void Render(); virtual void Update(float dt); virtual void Enter(); virtual void Leave(); virtual bool IsDone(); virtual void Focus(bool bFocused); virtual void MouseOver(bool bOver); virtual bool MouseLButton(bool bDown); virtual bool KeyClick(int key, int chr); private: hgeFont *fnt; HEFFECT snd; float delay; char *title; hgeColor scolor, dcolor, scolor2, dcolor2, color; hgeColor sshadow, dshadow, shadow; float soffset, doffset, offset; float timer, timer2; }; |
組件的建構元(Constructor)需要初始化「hgeGUIObject」的成員資料 id, bStatic, bVisible, bEnabled, rect:
hgeGUIMenuItem::hgeGUIMenuItem(int _id, hgeFont *_fnt, HEFFECT _snd, float _x, float _y, float _delay, char *_title) { id=_id; fnt=_fnt; snd=_snd; delay=_delay; title=_title; color.SetHWColor(0xFFFFE060); shadow.SetHWColor(0x30000000); offset=0.0f; timer=-1.0f; timer2=-1.0f; bStatic=false; bVisible=true; bEnabled=true; float w=fnt->GetStringWidth(title); rect.Set(_x-w/2, _y, _x+w/2, _y+fnt->GetHeight()); } |
Render 方法是必要的,所有組件都需要定義之:
void hgeGUIMenuItem::Render() { fnt->SetColor(shadow.GetHWColor()); fnt->Render(rect.x1+offset+3, rect.y1+3, HGETEXT_LEFT, title); fnt->SetColor(color.GetHWColor()); fnt->Render(rect.x1-offset, rect.y1-offset, HGETEXT_LEFT, title); } |
其他方法則視情況需求再定義即可。
「Update」方法會在每次 GUI 更新和動畫更新時呼叫。在這範例中,我們使用兩個計時器(timers),根據時間控制組件的顏色和位置:
void hgeGUIMenuItem::Update(float dt) { if(timer2 != -1.0f) { timer2+=dt; if(timer2 >= delay+0.1f) { color=scolor2+dcolor2; shadow=sshadow+dshadow; offset=0.0f; timer2=-1.0f; } else { if(timer2 < delay) { color=scolor2; shadow=sshadow; } else { color=scolor2+dcolor2*(timer2-delay)*10; shadow=sshadow+dshadow*(timer2-delay)*10; } } } else if(timer != -1.0f) { timer+=dt; if(timer >= 0.2f) { color=scolor+dcolor; offset=soffset+doffset; timer=-1.0f; } else { color=scolor+dcolor*timer*5; offset=soffset+doffset*timer*5; } } } |
「Enter」方法是在組件剛出現於螢幕上時呼叫。組件的起始動畫應該會從這裡啟動:
void hgeGUIMenuItem::Enter() { hgeColor tcolor2; scolor2.SetHWColor(0x00FFE060); tcolor2.SetHWColor(0xFFFFE060); dcolor2=tcolor2-scolor2; sshadow.SetHWColor(0x00000000); tcolor2.SetHWColor(0x30000000); dshadow=tcolor2-sshadow; timer2=0.0f; } |
「Leave」方法是在GUI消失於螢幕上時呼叫。組件的結束動畫應該要從這裡啟動:
void hgeGUIMenuItem::Leave() { hgeColor tcolor2; scolor2.SetHWColor(0xFFFFE060); tcolor2.SetHWColor(0x00FFE060); dcolor2=tcolor2-scolor2; sshadow.SetHWColor(0x30000000); tcolor2.SetHWColor(0x00000000); dshadow=tcolor2-sshadow; timer2=0.0f; } |
「IsDone」是用來測試組件的(進入/離開)動畫是否已經完成。如果動畫已完結,則會回傳ture:
bool hgeGUIMenuItem::IsDone() { if(timer2==-1.0f) return true; else return false; } |
「Focus」方法是在組件(獲得/失去)控制權時呼叫。在這範例裡,我們把取得控制權的動畫在這裡開始:
void hgeGUIMenuItem::Focus(bool bFocused) { hgeColor tcolor; if(bFocused) { hge->Effect_Play(snd); scolor.SetHWColor(0xFFFFE060); tcolor.SetHWColor(0xFFFFFFFF); soffset=0; doffset=4; } else { scolor.SetHWColor(0xFFFFFFFF); tcolor.SetHWColor(0xFFFFE060); soffset=4; doffset=-4; } dcolor=tcolor-scolor; timer=0.0f; } |
「MouseOver」方法會再滑鼠游標(進入/離開)組件作用範圍時呼叫。當滑鼠移入組件範圍時,這裡我們只將GUI的目標設置在組件上:
void hgeGUIMenuItem::MouseOver(bool bOver) { if(bOver) gui->SetFocus(id); } |
「MouseLButton」方法會再滑鼠左鍵的狀態改變時呼叫。當組件改變狀態並且想要提醒呼叫者時,應該回傳ture:
bool hgeGUIMenuItem::MouseLButton(bool bDown) { if(!bDown) { offset=4; return true; } else { hge->Effect_Play(snd); offset=0; return false; } } |
「KeyClick」方法是當某個按鍵被點擊(click)時會通知控制組件。當組件改變狀態並且想要提醒呼叫者時,應該回傳ture:
bool hgeGUIMenuItem::KeyClick(int key, int chr) { if(key==HGEK_ENTER || key==HGEK_SPACE) { MouseLButton(true); return MouseLButton(false); } return false; } |
OK,現在我們定義好了組件行為。
使用GUI
這是精簡的一部份,首先我們需要一些資源手柄(resource handles):
HEFFECT snd; HTEXTURE tex; hgeGUI *gui; hgeFont *fnt; hgeSprite *spr; |
在 WinMain 方法裡,在初始化的期間,我們必須讀取需要的資源:
snd=hge->Effect_Load("menu.wav"); tex=hge->Texture_Load("cursor.png"); fnt=new hgeFont("font1.fnt"); spr=new hgeSprite(tex,0,0,32,32); |
現在,我們可以產生GUI並加入選單項目進去。GUI組件內部會統一管理,我們不用保留項目的指標:
gui=new hgeGUI(); gui->AddCtrl(new hgeGUIMenuItem( 1,fnt,snd,400,200,0.0f,"Play")); gui->AddCtrl(new hgeGUIMenuItem( 2,fnt,snd,400,240,0.1f,"Options")); gui->AddCtrl(new hgeGUIMenuItem( 3,fnt,snd,400,280,0.2f,"Instructions")); gui->AddCtrl(new hgeGUIMenuItem( 4,fnt,snd,400,320,0.3f,"Credits")); gui->AddCtrl(new hgeGUIMenuItem( 5,fnt,snd,400,360,0.4f,"Exit")); |
接著設置GUI的導航模式、滑鼠指標的圖示以及預設的選取項目。最後,啟動進入選單的動畫:
gui->SetNavMode(HGEGUI_UPDOWN | HGEGUI_CYCLED); gui->SetCursor(spr); gui->SetFocus(1); gui->Enter(); |
接著來看如何更新選單以及接收通知訊息。在我們的框架方法(FrameFunc)呼叫「hgeGUI::Update」方法來更新動畫和處理使用者輸入。如果有組件狀態改變,則會回傳該組件的訊息通知者(identificator)。如果所有組件都完成他們的離開動畫,則回傳-1。如果甚麼事都沒發生,則回傳0。
int id; static int lastid=0; float dt=hge->Timer_GetDelta(); id=gui->Update(dt); if(id == -1) { switch(lastid) { case 1: case 2: case 3: case 4: gui->SetFocus(1); gui->Enter(); break; case 5: return true; } } else if(id) { lastid=id; gui->Leave(); } |
在 RenderFunc 方法裡,這裡僅呼叫「hgeGUI::Render」繪製選單:
hge->Gfx_BeginScene(); gui->Render(); hge->Gfx_EndScene(); |
回到 WinMain 方法。關閉程式時,也要記得釋放GUI和資源們:
delete gui; delete fnt; delete spr; hge->Texture_Free(tex); hge->Effect_Free(snd); |
完整的程式碼可以在「tutorials\tutorial06」資料夾底下找到。所需的資源檔在「tutorials\precompiled」底下。
※handle: 其實一直不知道要怎樣翻譯才好,對岸大多翻譯為「句柄」,總之就是用來代表某個東西的位置或編號之類的代號。在這裡我就翻成「手柄」比較直接的名稱,就是「啊~拉下這個手柄就會牽連到對應的裝置」的這種感覺吧!其實不用太在意這個翻譯啦~XD