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源创计划”,欢迎正在阅读的你也加入,一块儿分享。