Ogre 编辑器一(MyGUI+Ogre整合与主界面)
在查看Ogre例子时,想看材质要里的纹理,着色器代码都需要每个去查找,非常麻烦.也想看更新每个Ogre里的对象后有什么效果.然后看到Compositor组件与粒子组件时,想到能实时编辑着色器代码实时更新渲染.
开始想着C++做界面麻烦,用C#的winForm做,后面发现首先结合层比较麻烦,然后C#与C++一起调试也会比较麻烦,还有一些比较奇怪的异常也会麻烦.好吧,不如全用C++做,在学习能用在Ogre中的UI时,主要了解了包括Ogre自己的Overlay, CEGUI, MyGUI等等,最终选择MyGUI,因为他小,功能全,代码容易理解,这样拓展起来也方便.因此最终选定Ogre1.9 + MyGUI3.2.2.这二个项目还都是跨平台的,如果有机会,后面可以尝试移值.开发平台暂时选用VS2013.
首先整合MyGUI到Ogre中,这部分主要是加载MyGUI的资源文件与初始化MyGUI的环境.其中初始化整个函数如下.
void initRoot() { String pluginsPath = fsLayer->getConfigFilePath("plugins.cfg"); String logPath = fsLayer->getWritablePath("ogre.log"); root = new Root(pluginsPath, fsLayer->getWritablePath("ogre.cfg"), "ogre.log"); //root->showConfigDialog(); bool foundit = false; for (auto rs : root->getAvailableRenderers()) { root->setRenderSystem(rs); String rname = root->getRenderSystem()->getName(); if (rname == "OpenGL Rendering Subsystem")//"OpenGL Rendering Subsystem" { foundit = true; break; } } if (!foundit) return; //we didn't find it... Raise exception? //we found it, we might as well use it! root->getRenderSystem()->setConfigOption("Full Screen", "No"); root->getRenderSystem()->setConfigOption("Video Mode", "1024 x 768 @ 32-bit colour"); window = root->initialise(true, "Ogre3DX"); Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5); allResListener = new AllResourceListener(); this->loadUIResources(); this->loadResources(); this->createSceneManager(); this->createView(); this->createGui(); this->createSceneRoot(); }
先初始化Root,选择渲染系统,初始化渲染窗口,加载UI资源等.我们单独把UI部分的资源拿出来,这样可以先加快界面出现过程,然后在界面里用进度条等显示加载游戏资源,这是大头部分,后面也会修改成这种,如下是UI资源部分代码.
void loadUIResources() { ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Core", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/OgreData", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Show", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Show/Main", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Show/Panel", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Show/Other", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Show/PanelView", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Show/PropertyField", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/Themes", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().addResourceLocation("../../Media/GUI/TreeControl", "FileSystem", "MyGUI"); ResourceGroupManager::getSingleton().initialiseResourceGroup("MyGUI"); }
其中GUI/Core里放的就是MyGUI中的Media/MyGUI_Media这个目录,这个是必需的,MyGUI下另外的几个文件夹都是非必要的.不过在这里,我设定的是黑色风格,MyGUI_Media默认提供的不是这种,所以把原MyGUI下的Media/Tools/LayoutEditor/Themes放入我们的资源文件GUI/Themes下,另外一些文件夹后面遇到再说.
在生成viewport与camera后,我们开始加载MyGUI,如下是初始MyGUI环境代码.
void createGui() { ogrePlatform = new OgrePlatform(); ogrePlatform->initialise(window, sceneMgr, "MyGUI"); gui = new Gui(); gui->initialise(); std::ostringstream handleStr; long handle = 0; window->getCustomAttribute("WINDOW", &handle); inputMgr = new OgreViewUI::InputManager(); inputMgr->createInput(handle); pointMgr = new OgreViewUI::PointerManager(); pointMgr->createPointerManager(handle); pointMgr->loadPointerResources(); MyGUI::ResourceManager::getInstance().load("FrameworkFonts.xml"); MyGUI::ResourceManager::getInstance().load("MyGUI_DarkSkin.xml"); MyGUI::ResourceManager::getInstance().load("MyGUI_DarkTemplate.xml"); MyGUI::ResourceManager::getInstance().load("TreeControlSkin.xml"); MyGUI::ResourceManager::getInstance().load("TreeControlTemplate.xml"); MyGUI::ResourceManager::getInstance().load("AutoComplete.xml"); MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); std::string widgetCategory = MyGUI::WidgetManager::getInstance().getCategoryName(); factory.registerFactory<MyGUI::TreeControl>(widgetCategory); factory.registerFactory<MyGUI::TreeControlItem>(widgetCategory); factory.registerFactory<MyGUI::AutoComplete>(widgetCategory); }
OgrePlatform的初始化就是把对应MyGUI的RenderManager关联在Ogre的渲染过程中,详细说明请看我上一篇 MyGUI 解析 里有详细介绍这个过程.而gui对象的初始化就是对内部的单例管理类初始化,初始化的过程大部分都在解析上面所说MyGUI中的Media/MyGUI_Media 中的文件.
然后我们初始化InputManager与PointerManager这二个类,这二个类会在MyGUI下的Common/Input中提供,一个截获鼠标与键盘事件,一个是管理鼠标显示状态.
前面所说,Media/MyGUI_Media下文件名都是固定的,MyGUI初始化时自动会去加载对应的固定名,而非那个文件夹下的文件,我们需要自己用MyGUI提供的ResourceManger进行load,前面我们用Ogre去load,但是Ogre不能处理这些文件,但是会记录对应文件路径,这样读出对应的文件流给MyGUI去处理.然后我们注册我们自定义的一些UI组件,上面的是树型控件和自动完成控件.
这样Ogre与MyGUI就整合在一起了.然后就是主界面大致设定,如下图所示.
暂时大致分成五个部分,上面是菜单区,左边是管理区,大致分成场景,资源等,中间上面是显示区域,中间下面暂时空出,右边部分是属性区.
在这里,我们要先定义一个主界面的Layout文件与菜单的Layout文件如下:
注意Align,在这,我们想让主界面占满整个窗口,则定义为Stretch,相当于winForm中的Fill,注意这也是第一层控件,我们需要定义他的name固定为Root或是_Main,因为MyGUI给我们提供的一个基本管理Layout文件类BaseLayout有字段mMainWidget,在初始化时,检测名为Root或_Main的Widget赋给mMainWidget,找不到则给出异常,这样有个好处,mMainWidget能代表当前Layout的大小,如果我们调整大小,修改这个就好,并且根据子Widget的Align来调整子Widget的大小,还有Layer,前文说过,同一层的UI定义的Layer最好在同一层,不然层之间会遮挡.
在主界面的Layout中定义划二个Widget控件在上面,一个name为MainMenuControl,Align为Top.一个name为MainControl,Align为Stretch.而在菜单界面对应的Layout中放入的是MenuBar控件在上面.
对于菜单界面,我们直接使用Layout editor生成的代码,如下代码.
ATTRIBUTE_CLASS_LAYOUT(MainMenu, "MainMenuControl.layout"); class MainMenu : public wraps::BaseLayout { public: MainMenu(MyGUI::Widget* _parent = nullptr); virtual ~MainMenu(); private: void mouseClick(MyGUI::Widget* sender); private: //%LE Widget_Declaration list start ATTRIBUTE_FIELD_WIDGET_NAME(MainMenu, mMenuMenuBar, "Menu"); MyGUI::MenuBar* mMenuMenuBar; ATTRIBUTE_FIELD_WIDGET_NAME(MainMenu, mLoadMenuItem, "load"); MyGUI::MenuItem* mLoadMenuItem; //%LE Widget_Declaration list end }; MainMenu::MainMenu(MyGUI::Widget* _parent) { initialiseByAttributes(this, _parent); mLoadMenuItem->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::mouseClick); } MainMenu::~MainMenu() { } void MainMenu::mouseClick(MyGUI::Widget* sender) { }
每个类名前面一个宏,控件字段上带一个宏,这样在初始化,调用initialiseByAttributes时,其实就是分析相应宏设置,类宏提供对应的Layout文件(需要注意放入Ogre加载的文件夹下,否则找不到), 控件字段提供控件对象与Layout中的子Widget对应.其实我们不要上面的宏,在初始化调用如下语句,是一个意思.
MainMenu::MainMenu(MyGUI::Widget* _parent) { initialise("MainPane.layout"); assignWidget(mMenuMenuBar, "Menu"); assignWidget(mLoadMenuItem, "load"); //initialiseByAttributes(this, _parent); mLoadMenuItem->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::mouseClick); }
我们还记的主控件里放入的是二个Widget,并没有实际对应某种控件,一般控件里放入Widget,表示实际对应是另一个我们自己定义的Layout,如这里,上面的Widget明显对应的是我们上面所说的MainMenu.我们看下,如下把这二者关联起来.
class MainPane : public wraps::BaseLayout { public: MainPane(MyGUI::Widget* _parent = nullptr); virtual ~MainPane(); private: MainMenu* mMainMenuControl = nullptr; tools::PropertiesPanelView* properyPanel2 = nullptr; }; MainPane::MainPane(MyGUI::Widget* _parent) { initialise("MainPane.layout"); assignBase(mMainMenuControl, "MainMenuControl"); assignBase(properyPanel2 , "MainControl"); }
在MainPane里,我们自己来写,没用生成的代码,我们也就不用那些宏了,自己来初始化,和前面一样,先initialise对应的layout文件,然后把layout对应控件名给某控件,和前面用assignWidget不同,这里我们用的是assignBase.assignWidget一般用于指定layout的template控件(也就是系统内定的控件),而assignBase一般用于把layout中的Type为Widget的控件定义成我们自定义的Layout对应类.
MyGUI说大了,再复杂的界面都是于assignWidget与assignBase来构成,对于其内部具体实现,请看相关代码与于我前篇 MyGUI 解析 .