在查看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 解析 .