利用CEGUI+Lua实现灵活的游戏UI框架
??
?????? 在上一篇文章中,介绍了一种基于组件方式的游戏UI架构设计方案,在这里,笔者将介绍如何利用CEGUI和Lua来实现这种灵活的框架。
?????? CEGUI是一个兼容OpenGL、DirectX的优秀开源GUI库,关于她的介绍以及如何在Direct3D中使用她,可以参考
http://blog.csdn.net/Lodger007/archive/2007/07/02/1675141.aspx一文。Lua是一种强大的脚本语言,她使用栈作为数据接口,能够很容易地与其它语言交互,关于她的介绍可以参考
http://www.lua.org/,以及笔者以前翻译的三篇系列文章:Lua入门(
http://blog.csdn.net/Lodger007/archive/2006/06/26/836466.aspx)、调用Lua函数(
http://blog.csdn.net/Lodger007/archive/2006/06/26/836897.aspx)、在Lua中调用C++函数(
http://blog.csdn.net/Lodger007/archive/2006/06/26/837349.aspx)。
?????? 在实现中,作为UI组件管理器的GUISystem是一个单件,这样,你能够很方便地在任何地方使用其全局唯一的对象。下面是Singleton和GUISystem的实现代码:
/**/
///?Singleton.h
#pragma ?once #define ?SINGLETON(class_name)? ????friend? class ?Singleton < ?class_name? > ;? ???? private :???? ????class_name()? ... {} ???? ???? ~ class_name()? ... {} ? ????class_name( const ?class_name & );? ????class_name & ? operator ? = ?( const ?class_name & ); #define ?SINGLETON2(class_name)? ????friend? class ?Singleton < ?class_name? > ;? ???? private :???? ????class_name( const ?class_name & );? ????class_name & ? operator ? = ?( const ?class_name & ); template < ?typename?T? > ? class ?Singleton ... { protected: ????Singleton()?...{} ????virtual?~Singleton()?...{} ????Singleton(const?Singleton<?T?>&)?...{} ????Singleton<?T?>&?operator?=?(const?Singleton<?T?>&)?...{} public: ????static?T&?GetSingleton() ????...{ ????????static?T?_singleton; ????????return?_singleton; ????} } ;
/**/
///?GUISystem
#pragma ?once #include? " Singleton.h " #include? " UIObject.h " #include? < CEGUI.h > #include? < RendererModules / directx9GUIRenderer / d3d9renderer.h > #include? < map > #include? < set > class ?GUISystem?:? public ?Singleton < ?GUISystem? > ... { SINGLETON(?GUISystem?) private: ????std::map<std::string?,?UIObject*>?_UIMap;????????/**////?游戏中需要用到的所有UI对象 ????typedef?std::map<std::string?,?UIObject*>::iterator?MapIter; ????std::set<UIObject*>?_curUIList;????????????/**////?当前场景中使用的UI对象列表 ????CEGUI::DirectX9Renderer*?_pCEGUIRender;????????/**////?CEGUI?Render ????CEGUI::Window*?_pGameGUI;????????????/**////?顶层UI private: ????/**//**?载入所有UI对象?*/ ????void?LoadAllUI(); ????/**//**?从脚本中读入场景UI?*/ ????void?ReadFromScript(const?std::string&?id);? public: ????/**//**?初始化GUI系统?**/ ????bool?Initialize(LPDIRECT3DDEVICE9?pD3DDevice); ????/**//**?得到当前需要的UI对象?*/ ????void?LoadCurUI(int?sceneId); ????/**//**?得到当前场景所需的UI对象?*/ ????std::set<UIObject*>&?GetCurUIList(); ????/**//**?得到UI对象?*/ ????UIObject*?GetUIObject(const?std::string?id); } ; ??????? 这里需要说明一下,_pGameGUI的作用。CEGUI是以树形结构来管理每个UI部件的,所以在游戏场景中,我们需要这么一个根节点,_pGameGUI就是这个根的指针,也可以理解为顶层容器。如果你对CEGUI::DirectX9Render的使用有疑问,请参考在DirectX 3D中使用CEGUI一文,在此就不再迭述。下面是GUISystem.cpp代码:
#include?
"
GUISystem.h
"
#include? " ChatUI.h " #include? " SystemUI.h " #include? " SmallMapUI.h " #include? < CEGUIDefaultResourceProvider.h > #include? " LuaScriptSystem.h " bool ?GUISystem::Initialize(LPDIRECT3DDEVICE9?pD3DDevice) ... { ????_pCEGUIRender?=?new?CEGUI::DirectX9Renderer(pD3DDevice?,?0);???? ????new?CEGUI::System(_pCEGUIRender); ????/**////?初始化GUI资源的缺省路径 ????CEGUI::DefaultResourceProvider*?rp?=?static_cast<CEGUI::DefaultResourceProvider*> ????????(CEGUI::System::getSingleton().getResourceProvider()); ????rp->setResourceGroupDirectory("schemes",?"../datafiles/schemes/"); ????rp->setResourceGroupDirectory("imagesets",?"../datafiles/imagesets/"); ????rp->setResourceGroupDirectory("fonts",?"../datafiles/fonts/"); ????rp->setResourceGroupDirectory("layouts",?"../datafiles/layouts/"); ????rp->setResourceGroupDirectory("looknfeels",?"../datafiles/looknfeel/"); ????/**////?设置使用的缺省资源 ????CEGUI::Imageset::setDefaultResourceGroup("imagesets"); ????CEGUI::Font::setDefaultResourceGroup("fonts"); ????CEGUI::Scheme::setDefaultResourceGroup("schemes"); ????CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels"); ????CEGUI::WindowManager::setDefaultResourceGroup("layouts"); ????/**////?设置GUI ????///?得到GUI样式的图片集 ????CEGUI::Imageset*?taharezlookImage; ????try...{ ????????taharezlookImage?=?CEGUI::ImagesetManager::getSingleton().createImageset("Vanilla.imageset"); ????}catch?(CEGUI::Exception&?exc) ????...{ ????????AfxMessageBox(exc.getMessage().c_str()); ????} ????/**////?设置鼠标图标 ????CEGUI::System::getSingleton().setDefaultMouseCursor(&taharezlookImage->getImage("MouseArrow")); ????/**////?设置字体 ????CEGUI::FontManager::getSingleton().createFont("simfang.font"); ????/**////?设置GUI皮肤 ????CEGUI::WidgetLookManager::getSingleton().parseLookNFeelSpecification("Vanilla.looknfeel"); ????/**////?载入GUI规划 ????CEGUI::SchemeManager::getSingleton().loadScheme("VanillaSkin.scheme"); ????/**////?得到窗口管理单件 ????CEGUI::WindowManager&?winMgr?=?CEGUI::WindowManager::getSingleton(); ????/**////?创建顶层UI ????_pGameGUI?=?winMgr.createWindow("DefaultWindow",?"root_ui"); ????/**////?设置GUI的Sheet(Sheet是CEGUI中窗口的容器) ????CEGUI::System::getSingleton().setGUISheet(_pGameGUI); ???? ????/**////?从GUISystem中载入所有场景UI ????LoadAllUI(); ????return?true; } void ?GUISystem::LoadAllUI() ... { ????/**////?生成所有的UI对象,并放入映射表中 ????UIObject*?pUIObject?=?new?ChatUI; ????_UIMap.insert(make_pair(pUIObject->GetID()?,?pUIObject)); ????pUIObject?=?new?SystemUI; ????_UIMap.insert(make_pair(pUIObject->GetID()?,?pUIObject)); ????pUIObject?=?new?SmallMapUI; ????_UIMap.insert(make_pair(pUIObject->GetID()?,?pUIObject)); } void ?GUISystem::LoadCurUI( int ?sceneId) ... { ????/**////?从顶层UI中移除所有UI?先清空当前UI列表 ????typedef?std::set<UIObject*>::iterator?Iter; ????std::set<?UIObject*?>::iterator?iter?=?_curUIList.begin(); ????for(?;?iter?!=?_curUIList.end()?;?++iter) ????????_pGameGUI->removeChildWindow((*iter)->GetWnd()); ????/**////?从脚本中载入场景UI数据 ????std::ostringstream?sid; ????sid?<<?"sui"?<<?sceneId; ????ReadFromScript(sid.str()); ????/**////?加入场景UI ????for(iter?=?_curUIList.begin()?;?iter?!=?_curUIList.end()?;?++iter) ????????_pGameGUI->addChildWindow((*iter)->InitUI()); } void ?GUISystem::ReadFromScript( const ?std:: string & ?id) ... { ????/**////?从Lua脚本中载入当前场景需要的UI,存入_curUIList中 ????LuaScriptSystem::GetSingleton().LoadScript("./script/sui.lua"); ????const?char*?pStr?=?NULL; ????int?i?=?1; ????pStr?=?LuaScriptSystem::GetSingleton().GetValue(id.c_str()?,?i++); ????while(pStr) ????...{ ????????_curUIList.insert(_UIMap[pStr]); ????????pStr?=?LuaScriptSystem::GetSingleton().GetValue("sui1"?,?i++); ????} } std:: set < UIObject *>& ?GUISystem::GetCurUIList() ... { ????return?_curUIList; } UIObject * ?GUISystem::GetUIObject( const ?std:: string ?id) ... { ????MapIter?iter?=?_UIMap.find(id); ????if(iter?!=?_UIMap.end()) ????????return?iter->second; ????else? ????????return?NULL; } ??????? 其中,GUISystem::ReadFromScript作用是从Lua脚本中读取当前场景对应的UI组件名。之所以采用Lua作为数据脚本,是因为其自身就为实现数据脚本提供了很好的支持,需要编写的解析代码与采用xml、ini相比会少很多。本例利用了Lua中的数组来存储UI组建名,是Lua作为数据脚本一个不错的示例:
--?Scene?GUI
sui1? = ?{ " SystemUI " , " SmallMapUI " , " ChatUI " } sui2? = ?{ " ChatUI " } sui3? = ?{ " SmallMapUI " } ??????? 下面是Lua脚本解析类,也是一个Singleton:
#pragma
?once
#include? " Singleton.h " #include? < lua.hpp > class ?LuaScriptSystem?:? public ?Singleton < ?LuaScriptSystem? > ... { ????SINGLETON2(LuaScriptSystem) private: ????LuaScriptSystem(); ????~LuaScriptSystem(); public: ????bool?LoadScript(char*?filename); ????const?char*?GetValue(const?char*?id?,?int?index); private: ????lua_State*?_pLuaVM;????????/**////?Lua状态对象指针 } ; ?
/**/
///?LuaScriptSystem.cpp
#include? " LuaScriptSystem.h " LuaScriptSystem::LuaScriptSystem() ... { ????/**////?初始化lua ????_pLuaVM?=?lua_open(); } bool ?LuaScriptSystem::LoadScript( char * ?filename) ... { ????if(luaL_dofile(_pLuaVM?,?filename)) ????????return?false; ????return?true; } const ? char * ?LuaScriptSystem::GetValue( const ? char * ?id?,? int ?index) ... { ????const?char*?pstr?=?NULL; ????lua_getglobal(_pLuaVM?,?id);????/**////?得到配置实体 ????lua_rawgeti(_pLuaVM?,?-1?,?index); ????if(lua_isstring(_pLuaVM?,?-1)) ????????pstr?=?lua_tostring(_pLuaVM?,?-1); ????lua_pop(_pLuaVM?,?2); ????return?pstr; } LuaScriptSystem:: ~ LuaScriptSystem() ... { ????/**////?关闭lua ????lua_close(_pLuaVM); } ??????? Lua与外界的交流需要依靠解释器维护的栈来实现,这一点对于使用Lua的开发者应该铭记于心。在GetValue中,利用lua_getglobal来得到lua脚本中全局变量,如"sui1",此时,栈顶(用索引-1来表示)就应该保存着该全局变量。利用lua_rawgeti传入数组位于栈的索引(-1),以及数组索引(index从1开始),就能够得到对应索引的值,结果自然也是放在栈中,想想push一下,现在栈顶应该保存着结果了,最后用lua_tostring来得到。 ??????? 在这个示例中,我们引入了三个UI组件,分别是ChatUI、SmallMapUI和SystemUI,对应聊天框、小地图、系统按钮条。为了演示它们之间的交互,我们规定ChatUI受SystemUI中Chat按钮的影响,可以让其显示或者隐藏,同时,SmallMapUI能够接受鼠标点击,并在ChatUI的文本框中显示一些点击信息。当然,这三个UI组件还必须对应着CEGUI的三个layout脚本文件。下面是它们的实现代码:
/**/
///?UIObject.h
#pragma ?once #include? < CEGUI.h > class ?UIObject ... { protected: ????std::string?_id; ????CEGUI::Window*?_pWnd; public: ????UIObject(void)?:?_pWnd(NULL)?...{} ????virtual?~UIObject(void)?...{} ????const?std::string&?GetID()?const?...{return?_id;} ????CEGUI::Window*?GetWnd()?const?...{return?_pWnd;} ????virtual?CEGUI::Window*?InitUI()?=?0; } ; /**/ ///?ChatUI.h #pragma ?once #include? " uiobject.h " class ?ChatUI?:? public ?UIObject ... { public: ????ChatUI(void); ????~ChatUI(void); ????CEGUI::Window*?InitUI(); } ; /**/ ///?ChatUI.cpp #include? " chatui.h " using ? namespace ?CEGUI; ChatUI::ChatUI( void ) ... { ????_id?=?"ChatUI"; } ChatUI:: ~ ChatUI( void ) ... { } Window * ?ChatUI::InitUI() ... { ????/**////?简单载入,没有消息处理 ????if(?NULL?==?_pWnd) ????????_pWnd?=??WindowManager::getSingleton().loadWindowLayout("ChatUI.layout"); ????/**////?先隐藏聊天框 ????_pWnd->hide(); ????return?_pWnd; } /**/ ///?SmallMapUI.h #pragma ?once #include? " uiobject.h " class ?SmallMapUI?:? public ?UIObject ... { public: ????SmallMapUI(void); ????~SmallMapUI(void); ????CEGUI::Window*?InitUI(); ????/**//**?在小地图上点击的消息响应函数?*/ ????bool?Click(const?CEGUI::EventArgs&?e); } ; /**/ ///?SmallMapUI.cpp #include? " smallmapui.h " #include? " GUISystem.h " using ? namespace ?CEGUI; SmallMapUI::SmallMapUI( void ) ... { ????_id?=?"SmallMapUI"; } SmallMapUI:: ~ SmallMapUI( void ) ... { } Window * ?SmallMapUI::InitUI() ... { ????/**////?简单载入,只处理在静态二维地图上点击左键 ????if(?NULL?==?_pWnd?) ????...{ ????????_pWnd?=?WindowManager::getSingleton().loadWindowLayout("SmallMapUI.layout"); ????????/**////?载入一幅静态地图 ????????ImagesetManager::getSingleton().createImagesetFromImageFile("SmallMap",?"ZoneMap.jpg"); ????????_pWnd->getChild("SmallMapUI/StaticImage")->setProperty("Image",?"set:SmallMap?image:full_image"); ????????/**////?处理鼠标点击事件 ????????_pWnd->getChild("SmallMapUI/StaticImage")->subscribeEvent( ????????????CEGUI::Window::EventMouseButtonDown,? ????????????CEGUI::Event::Subscriber(&SmallMapUI::Click?,?this)); ????} ????return?_pWnd; } bool ?SmallMapUI::Click( const ?CEGUI::EventArgs & ?e) ... { ????char?text[100]; ????sprintf(text?,?"你点击了地图,坐标为(%.1f?,?%.1f)"?,?static_cast<const?MouseEventArgs&>(e).position.d_x?,?static_cast<const?MouseEventArgs&>(e).position.d_x); ????/**////?通过CEGUI直接访问聊天框 ????WindowManager::getSingleton().getWindow("ChatUI/MsgBox")->setText((utf8*)text); ????return?true; } /**/ ///?SystemUI.h #pragma ?once #include? " uiobject.h " class ?SystemUI?:? public ?UIObject ... { public: ????SystemUI(void); ????~SystemUI(void); ????CEGUI::Window*?InitUI(); ????bool?SystemUI::OnChatBtn(const?CEGUI::EventArgs&?e); ????bool?SystemUI::OnExitBtn(const?CEGUI::EventArgs&?e); } ; /**/ ///?SystemUI.cpp #include? " SystemUI.h " #include? " GUISystem.h " SystemUI::SystemUI( void ) ... { ????_id?=?"SystemUI"; } SystemUI:: ~ SystemUI( void ) ... { } CEGUI::Window * ?SystemUI::InitUI() ... { ????if(?NULL?==?_pWnd) ????...{ ????????_pWnd?=??CEGUI::WindowManager::getSingleton().loadWindowLayout("SystemUI.layout"); ????????/**////?处理ChatBtn消息 ????????_pWnd->getChild("SystemUI/ChatBtn")->subscribeEvent( ????????????CEGUI::Window::EventMouseButtonDown,? ????????????CEGUI::Event::Subscriber(&SystemUI::OnChatBtn?,?this)); ????????/**////?处理ExitBtn消息 ????????_pWnd->getChild("SystemUI/ExitBtn")->subscribeEvent( ????????????CEGUI::Window::EventMouseButtonDown,? ????????????CEGUI::Event::Subscriber(&SystemUI::OnExitBtn?,?this)); ????} ????return?_pWnd; } bool ?SystemUI::OnChatBtn( const ?CEGUI::EventArgs & ?e) ... { ????/**////?显示聊天框 ????UIObject*?pUIObj?=?GUISystem::GetSingleton().GetUIObject("ChatUI"); ????if(!pUIObj) ????????return?false; ????CEGUI::Window*?pWnd?=?pUIObj->GetWnd(); ????if(pWnd) ????...{ ????????pWnd->isVisible()???pWnd->hide()?:?pWnd->show(); ????} ????return?true; } bool ?SystemUI::OnExitBtn( const ?CEGUI::EventArgs & ?e) ... { ????/**////?简单地退出 ????::exit(0); ????return?true; } ??????? 在使用CEGUILayoutEditor创建layout脚本时,你不能创建一个满屏的DefaultWindow,那样会让造成不能相应其他窗口的问题。但通常Editor会为我们默认创建它,这不要紧,你只需要在保存的layout文件中删除那个顶层的满屏window就可以了。 ??????? 下面是程序的运行结果: (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 使用Perl和GtkBuilder连接信号
- [VB]发三个近期学习Windows Socket API之后写的作品~~~~
- Delphi XE5 Android.如何使用PowerManager.WakeLock?
- 用perl生成带格式的excel(跨平台)
- delphi 中 image 控件加载bmp、JPG、GIF、PNG等图片的办法
- delphi 回调函数与函数指针
- ANTS Memory Profiler - .NET内存泄漏分析工具
- 最佳IDE for Grails 1.1.X开发
- 云计算与大数据处理技术
- [示例] Firemonkey TGridLayout & TGridPanelLayout 布