Tutorial 06 - Creating menus
This tutorial shows how to create custom GUI controls and menus.
Creating custom controls
First, we should define the custom control class, derived from 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;
};
The custom control constructor should initialize hgeGUIObject data members
id, bStatic, bVisible, bEnabled and 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 method is essential, every control must define it:
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);
}
All other methods are optional and you could not define them if you don't need.
Update is called each time the GUI is updated
and should do the animation. In this example we have two timers and adjust the
control's color and position with time:
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 is called when the GUI is about to appear
on the screen. A control should start it's Enter animation here:
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 is called when the GUI is about to disappear
from the screen. A control should start it's Leave animation here:
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 is used to test if the control has finished
it's Enter/Leave animation. When the animation is finished it should return true:
bool hgeGUIMenuItem::IsDone()
{
if(timer2==-1.0f) return true;
else return false;
}
Focus method is called when the control
gains or loses keyboard input focus. In this example we start our focus
animation here:
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 method is called to notify the
control that the mouse cursor has entered or left it's area. Here we just set
the input focus to our control when the mouse comes over it:
void hgeGUIMenuItem::MouseOver(bool bOver)
{
if(bOver) gui->SetFocus(id);
}
MouseLButton method is called to notify the
control that the left mouse button state has changed. If the control changes it's state
and wants the caller to be notified, it should return true:
bool hgeGUIMenuItem::MouseLButton(bool bDown)
{
if(!bDown)
{
offset=4;
return true;
}
else
{
hge->Effect_Play(snd);
offset=0;
return false;
}
}
KeyClick method is called to notify the
control that a key has been clicked. If the control changes it's state
and wants the caller to be notified, it should return true:
bool hgeGUIMenuItem::KeyClick(int key, int chr)
{
if(key==HGEK_ENTER || key==HGEK_SPACE)
{
MouseLButton(true);
return MouseLButton(false);
}
return false;
}
Well, we have our custom control behaviour defined now.
Using GUI
Here's the simple part. First, we'll need some resource handles:
HEFFECT snd;
HTEXTURE tex;
hgeGUI *gui;
hgeFont *fnt;
hgeSprite *spr;
In the WinMain function, during initialization
we should load the required resources:
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);
Now we can create the GUI and add our menu items to it. GUI controls
are managed internally and you could not hold the pointers:
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"));
Now we set the GUI navigation mode, mouse cursor image and the default
input focus, then we start the Enter animation:
gui->SetNavMode(HGEGUI_UPDOWN | HGEGUI_CYCLED);
gui->SetCursor(spr);
gui->SetFocus(1);
gui->Enter();
Now let's see how we update our menu and receive notifications.
In our frame function (FrameFunc) we call hgeGUI::Update method
to update the animation and process user's input. If a control has changed it's state, this method
returns the control identificator. If all the controls have finished their Leave animation, it returns -1.
If nothing interesting happened, it returns 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(); }
To render the menu we just call hgeGUI::Render method
within our RenderFunc:
hge->Gfx_BeginScene();
gui->Render();
hge->Gfx_EndScene();
So, the menu is up and running. Now back to the WinMain.
During shutdown we should delete the GUI and free the resources:
delete gui;
delete fnt;
delete spr;
hge->Texture_Free(tex);
hge->Effect_Free(snd);
The complete source code with detailed comments for this tutorial you may find in the folder
tutorials\tutorial06. The required media files you'll find in the folder tutorials\precompiled.
|