这几个月的开发工作主要是关于游戏内GUI的,业务开发之余也时常会看看客户端工程里的GUI系统这一块的代码,这里系统的总结下。
一、GUI树形结构
在GUI中所有的控件都遵循树形结构:
在客户端初始化时,会创建出一个无形的,跟客户端窗口等大的root窗口:g_FBClient_Global.m_pRootWnd
。在游戏中添加的窗口,一般都挂载在root窗口下。要在游戏中新增一个窗口,当在GUI编辑器中创建好了窗口资源后,代码中创建该窗口资源的的方式一般是在对应的类的构造函数中使用:CreateFromRes(strWndResourceName, g_FBClient_Global.m_pRootWnd)
。第一个参数是在编辑器中对应窗口资源的名称;第二个为要挂载的父窗口,如前面所述,这里一般都是挂载在根窗口下。该方法成功执行后,该窗口及窗口下的所有子控件便都创建出来了并且都是默认显示的。(关于GUI编辑器的介绍及使用,这个是内部资料了,见/src/Tools/ArkGUIEditor/bin/用ArkGUI创建一个窗口的步骤.doc)
GUI的绘制时,是在游戏主循环中调用m_pRootWnd->Render(),从根窗口自顶向下地依此树形结构递归地显示绘制。游戏退出时对资源的回收,也是如此进行的(所以在窗口中创建的控件,如果在创建时挂载在了该窗口下的话,都无需自己手动释放)。
二、关于CArkWnd类
class CArkWnd;是所有窗口和控件的基类。其中绝大部分为虚方法,包括窗口生命周期函数如OnCreated()、OnDestroy()、OnUpdateDataBeforeDraw()等;响应消息的一些函数如OnMouseWheel()、OnChildmsg()以及设置窗口属性功能的函数如EnableWnd()、SetFontSize()、SetWndText()等。
一个窗口的生命周期大致可以表示如下:
一些常用的控件:
按钮类
像按钮CArkButton、复选框CArkCheckoutButton等的显示,是在GUI编辑器中设置了一个按钮的各个状态的贴图包括点击、鼠标经过、禁用等状态。 按钮自己接收到鼠标事件进行不同状态的响应,在重写的虚函数DrawWndBackground()中实现:void CArkButton::DrawWndBackground()
{
DrawBackImage(m_pWndData->m_Enable,m_pWndData->m_Disable,
m_MouseOverImage,m_ClickDownImage,IsHeld());// 传入各个状态的图片链表
}
富文本框
游戏内大部分显示文本的地方都是使用的富文本控件,最典型的像任务描述面板和聊天面板。在富文本框中的文字可以设置字体风格颜色及换行。
通过CArkRichText和CArkRichTextFormat进行换行和颜色解析等功能的具体实现。(RichWnd格式化文字说明(内部网址):http://192.168.1.252:8080/pages/viewpage.action?pageId=9110273)其他常用的控件还有诸如文本编辑框CArkEdit、泡泡提示框CTorchbearerWnd、确认框/确认取消框CRichMsgBox等等。
三、消息响应
一个消息响应流程大致如下:
由根窗口接收到消息:
g_FBClient_Global.m_pRootWnd->ReceiveMsg(message, wParam, lParam);
再由CArkWnd类中的窗口管理器CArkGUIManage,调用:pGUI->WndProc(message,wParam,lParam);
在这个函数中分别处理鼠标消息和键盘消息:
CArkMouseMsgProc::ProcessMsg(uint32 message, WPARAM wParam, LPARAM lParam)
CArkKeyMsgProc::ProcessMsg(uint32 message, WPARAM wParam, LPARAM lParam)
在上面的处理函数中,会找到最顶层的CArkWnd窗口,也就是我们在界面上实际操作的控件。然后调用这个CArkWnd的方法:
CArkWnd::WndProc(uint32 message, WPARAM wParam, LPARAM lParam)
在WndProc函数中,根据消息类型执行对应的处理函数;假设我们是点击了某个按钮,那么根据消息为点击事件,就执行虚方法OnLButtonDown()。那么实际就是由按钮类CarkButton的OnLButtonDown函数执行,在里面做一些特殊操作例如播放该按钮被点击时的特效。
在平时GUI的开发中最主要接触的是这个OnChildMsg函数。在自己编写的窗口中,由这个函数的pChild和uMsgID参数得到子控件名称和消息类型,就能确定在界面上进行了什么操作,然后做后续的业务逻辑;比如在商城点击了“购买”按钮,那么当GenWndName(pChild)== ”BuyBtn”&& uMsgID==BUTTON_CLICK时就响应BuyItem()函数发消息给服务器。
大致流程还可以看下面这张图: