OGRE 3D教程七:CEGUI和OGRE (Ogre1.7.1版本)(zhuan)
简介 (本篇教程对应Ogre1.7.1 + CEGUI 0.7.1)本文的原文地址: http://www.ogre3d.org/tikiwiki/Basic+Tutorial+7&structure=Tutorials 在这篇教程中,我们将介绍如何在Ogre项目中使用 CEGUI。 NOTE: 这篇教程在CEGUI方面只是一个开始,我们并没有尝试去深入CEGUI,如果你有需求,可以去他们的主页 更多更生如的介绍CEGUI的文档和教程,请去http://www.cegui.org.uk/api_reference/ 前提条件 1.这篇教程将假设你有C++编程经验,并且能够构建和编译一个Ogre程序。 2.这篇教程也假设你已经使用过 OGRE Wiki Tutorial Framework,或者手动使用CMake和 Ogre AppWizard创建过项目。 3.这篇教程需要前面几篇教程的知识基础,我们假设你已经学习过前面的教程。 你可以在 这里找到本篇教程的代码,请跟着教程,把它们加进你自己的项目中,查看编译后的结果。 入门 初始化代码段:请按照下面的代码段,来编辑你的头文件。 BasicTutorial7 头文件 #include <CEGUI/CEGUI.h> #include <CEGUI/RendererModules/Ogre/CEGUIOgreRenderer.h> class BasicTutorial7 : public BaseApplication { public: BasicTutorial7(void); virtual ~BasicTutorial7(void); protected: CEGUI::OgreRenderer* mRenderer; virtual void createScene(void); virtual void createFrameListener(void); // Ogre::FrameListener virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt); // OIS::KeyListener virtual bool keyPressed( const OIS::KeyEvent &arg ); virtual bool keyReleased( const OIS::KeyEvent &arg ); // OIS::MouseListener virtual bool mouseMoved( const OIS::MouseEvent &arg ); virtual bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id ); virtual bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ); bool quit(const CEGUI::EventArgs &e); }; 请按照下面的代码段,来编辑你的实现文件。 BasicTutorial7 实现文件 //------------------------------------------------------------------------------------- BasicTutorial7::BasicTutorial7(void) { } //------------------------------------------------------------------------------------- BasicTutorial7::~BasicTutorial7(void) { } //------------------------------------------------------------------------------------- void BasicTutorial7::createScene(void) { } //------------------------------------------------------------------------------------- void BasicTutorial7::createFrameListener(void) { BaseApplication::createFrameListener(); } //------------------------------------------------------------------------------------- bool BasicTutorial7::frameRenderingQueued(const Ogre::FrameEvent& evt) { return BaseApplication::frameRenderingQueued(evt); } //------------------------------------------------------------------------------------- bool BasicTutorial7::keyPressed( const OIS::KeyEvent &arg ) { return BaseApplication::keyPressed(arg); } //------------------------------------------------------------------------------------- bool BasicTutorial7::keyReleased( const OIS::KeyEvent &arg ) { return BaseApplication::keyReleased(arg); } //------------------------------------------------------------------------------------- bool BasicTutorial7::mouseMoved( const OIS::MouseEvent &arg ) { return BaseApplication::mouseMoved(arg); } //------------------------------------------------------------------------------------- bool BasicTutorial7::mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) { return BaseApplication::mousePressed(arg, id); } //------------------------------------------------------------------------------------- bool BasicTutorial7::mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) { return BaseApplication::mouseReleased(arg, id); } //------------------------------------------------------------------------------------- bool BasicTutorial7::quit(const CEGUI::EventArgs &e) { return true; } 项目设置 你必须使用你现在正在使用的Ogre版本,来编译生成 CEGUI。请查阅 构建CEGUI,然后你需要把CEGUI的头文件路径添加进你的项目的include目录中。 编译好CEGUI后,你可以把编译好的文件拷贝到其他地方,作为一个SDK来使用。如果你的CEGUI头文件的路径上,没有‘CEGUI’,那么请在你的头文件的include里,删去‘CEGUI’字段。 项目包含目录:请加入下面的两行,到你的include目录。 CEGUI_HOME/include CEGUI_HOME/include/CEGUI 外加库目录 CEGUI_HOME/lib 依赖库(windows下) Debug CEGUIBase_d.lib CEGUIOgreRenderer_d.lib Release CEGUIBase.lib CEGUIOgreRenderer.lib 移动 DLL 在Windows系统下,你需要拷贝以下Dll到你的工作目录中 Debug CEGUIBase_d.dll CEGUIFalagardWRBase_d.dll CEGUIOgreRenderer_d.dll CEGUIExpatParser_d.dll Release CEGUIBase.dll CEGUIFalagardWRBase.dll CEGUIOgreRenderer.dll CEGUIExpatParser.dll 最后一个DLL,假定你为CEGUI使用了默认的Expat XML解析器。 编译代码 程序现在只是显示一个黑色的屏幕,并不做任何其他的事情(知道你按下 Escape键退出。)如果出现了编译或者运行错误,请检查你的设置。请确定你的代码在全部编译和运行通过之后再继续下面的教程。 CEGUI简介 CEGUI是一个全功能的GUI库。它可以支持 Ogre (也支持 OpenGL,DirectX 和 Lrrlicht)。 Ogre只是一个图形库(它没有声音,物理引擎等),同样的,CEGUI只是一个GUI库,这意味着它没有自己的渲染库,并且无法hook 鼠标和键盘事件。实际上,为了让CEGUI可以渲染,你必选提供一个渲染器给它(这也是 CEGUIOgreRenderer库所提供的功能)为了让系统监听到鼠标和键盘事件,你也必须手动的去把这些事件传递给系统,这看起来比较麻烦,但实际上很少的代码就可以完成这些功能。它也可以让你在渲染和输入方面拥有完全控制权,CEGUI在这里非常方便。 CEGUI还有很多方面会让你感到奇怪(即使你使用过其他GUI系统),在这篇教程里,我将慢慢的带你体验。 定义CEGUI的资源组 CEGUI和Ogre 一样,也需要使用一些类型的资源,它有一些资源管理器(和Ogre相似),用来找到它们各自的资源路径。所以,你需要去定义一些重要的资源组,并在 resources.cfg 中写好他们的路径。 增加以下内容到你的 resources.cfg (用指向CEGUI datafiles文件夹的路径替换掉 ‘path_to_cegui’) [Imagesets] FileSystem=path_to_cegui/imagesets [Fonts] FileSystem=path_to_cegui/fonts [Schemes] FileSystem=path_to_cegui/schemes [LookNFeel] FileSystem=path_to_cegui/looknfeel [Layouts] FileSystem=path_to_cegui/layouts 初始化 CEGUI 找到 createScene函数,并增加如下代码 mRenderer = &CEGUI::OgreRenderer::bootstrapSystem(); 现在 CEGUI 已经初始化了,我们需要为每一个CEGUI资源管理器设置默认的资源组:请添加以下代码 CEGUI::Imageset::setDefaultResourceGroup("Imagesets"); CEGUI::Font::setDefaultResourceGroup("Fonts"); CEGUI::Scheme::setDefaultResourceGroup("Schemes"); CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel"); CEGUI::WindowManager::setDefaultResourceGroup("Layouts"); 如你所见,我们使用了在 resource.cfg文件中定义的资源组。 CEGUI是可高度定制的,而且允许你自己定义程序的皮肤(在scheme文件中定义)这篇教程并没有涉及到如何去给CEGUI库换肤,若你需要这方面的知识,请去CEGUI官网。 以下代码选择皮肤: CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme"); 接下来我们设置默认的鼠标图标: CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow"); 第一个参数指向了所要使用的 Imageset,第二个参数则指向 Imageset中的图片。 在本系列的教程中,我们将使用CEGUI来显示一个鼠标的光标。你也可以使用其他GUI库赖渲染一个鼠标光标,或者直接简单的使用OGRE创建一个你自己的鼠标光标(后者会有一点复杂)。如果你只是使用CEGUI来显示鼠标光标,并且你很关注你的游戏的硬盘开销,那么你也可以寻则其他的选项来替换掉CEGUI。 最后请注意: 在上面的代码中,我们设置了默认的鼠标光标,但此时鼠标是不可见的,我们并可以使用MouseCursor::setImage 来让鼠标可见。 CEGUI::MouseCursor::getSingleton().setImage( CEGUI::System::getSingleton().getDefaultMouseCursor()); 移除 SDKTrays 在继续我们的教程前,我们先从我们的程序中移除掉 OgreBites SDKTrays。我们需要重写以下两个函数: createFrameListener frameRenderingQueued createFrameListener 拷贝 BaseApplication::createFrameListener 到TutorialApplication7::createFrameListener,并删除 SDKTrays的相关代码,修改后将是如下代码: void BasicTutorial7::createFrameListener(void) { Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***"); OIS::ParamList pl; size_t windowHnd = 0; std::ostringstream windowHndStr; mWindow->getCustomAttribute("WINDOW", &windowHnd); windowHndStr << windowHnd; pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); mInputManager = OIS::InputManager::createInputSystem( pl ); mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, true )); mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, true )); mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); //Set initial mouse clipping size windowResized(mWindow); //Register as a Window listener Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this); mRoot->addFrameListener(this); } frameRenderingQueued 我们还需要移除掉BaseApplication::frameRenderingQueued 移除后代码如下: bool BasicTutorial7::frameRenderingQueued(const Ogre::FrameEvent& evt) { if(mWindow->isClosed()) return false; if(mShutDown) return false; //Need to capture/update each device mKeyboard->capture(); mMouse->capture(); return true; } 我们只是移除了mTrayMgr 和 mDetailsPanel。 注入 键盘事件 CEGUI 不处理任何输入,它不会去读入 鼠标事件或者键盘输入。所以兄弟们,我们必须自己手动把键盘事件和鼠标事件注入的系统中去,让系统来处理这些事件。 如果你在使用CEGUI,你将会需要鼠标和键盘事件的缓冲模式,这样你可以直接把接收到的事件注入系统。 找到 keyPressed 函数,并把下面的代码加入进去: CEGUI::System &sys = CEGUI::System::getSingleton(); sys.injectKeyDown(arg.key); sys.injectChar(arg.text); 在得到CEGUI::System的对象后,我们需要做两件事: 1.注入key down 事件到CEGUI 2.注入 实际上按下的键 有一个很重要的问题,在你注入 key down 事件时,注入的字符,并不一样会得到你想要的结果,如果在使用非 英语字符的键盘时,我们必须考虑在设计时支持Unicode。 现在我们需要注入 key up 事件到system中去。找到 keyReleased 函数并添加以下代码: CEGUI::System::getSingleton().injectKeyUp(arg.key); 注意: 我们并不需要注入字符,在 key up事件注入时。 注入鼠标事件 现在我们已经完成了对键盘输入的操作,我们还需要去操作鼠标输入。当我们输入一个 key up 或者 key down 事件到CEGUI中去时,我们对于 OIS和CEGUI中,使用的键值是不需要转换的,但是在鼠标事件里,我们需要转换一下,所以我们需要写一个函数来转换 OIS 和 CEGUI 的键 ID 加入以下的代码到 BasicApplication7.Cpp CEGUI::MouseButton convertButton(OIS::MouseButtonID buttonID) { switch (buttonID) { case OIS::MB_Left: return CEGUI::LeftButton; case OIS::MB_Right: return CEGUI::RightButton; case OIS::MB_Middle: return CEGUI::MiddleButton; default: return CEGUI::LeftButton; } } 它可以作为一个 static方法,所以不需要再到头文件中去声明它。 现在我们可以把鼠标事件注入到系统中去了。找到 mousePressed 函数,并把下面的代码加入进去。 CEGUI::System::getSingleton().injectMouseButtonDown(convertButton(id)); 这句代码是很明显的,我们把OIS得到的鼠标键的ID,转换成 CEGUI的 button ID。 然后我们找到 mouseReleased 函数,并把下面的代码添加进去。 CEGUI::System::getSingleton().injectMouseButtonUp(convertButton(id)); 最后,我们需要注入 鼠标的运动 (mouse motion)到CEGUI。 CEGUI::System 对象 拥有一个 injectMouseMove 函数来预期相对的鼠标运动。 OIS::mouseMoved对象会把 相对的运动 放在 state.X.rel 和 state.Y.rel 这两个变量中。 找到 mouseMoved 函数,并且把下面的代码添加进去: CEGUI::System &sys = CEGUI::System::getSingleton(); sys.injectMouseMove(arg.state.X.rel, arg.state.Y.rel); // Scroll wheel. if (arg.state.Z.rel) sys.injectMouseWheelChange(arg.state.Z.rel / 120.0f); 120是一个‘神奇数字’,现在使用的更多。 OIS 使用了这个神奇数字,好了,现在 CEGUI 已经完整的得到了 鼠标和键盘事件。 Windows, Sheets, and Widgets CEGUI 和大多数 GUI系统不一样。在CEGUI中,所有的东西都是 CEGUI::Window 类的子类,一个 window 可以拥有多个子window。这就是说,当你创建一个 frame来容纳多个按钮时,这个frame,实际上就是一个window。这也会导致一些奇怪的结果。比如,你可以把一个button,放进另一个button中,这在其他实践中,是不可能发生的。这导致的结果是: 当你在寻找一个你放置在程序中的特定的部件,他们都被称为 Windows,并且你可以通过 函数来 找到并访问他们。 在绝大多数 CEGUI程序中,你并不需要自己在代码中去创建每一个对象,你只要使用 CEGUI Layout Editor来编辑一个GUI layout 即可,编辑完成后,Editor 会自己保存到一个 文本文件中。然后你可以通过读取这个文件到CEGUI中,来生成一个GUI sheet(同样,这也是一个 CEGUI::Window的子类。) 最后,CEGUI 包含了大量的部件,可以让你用在你的程序里。当然,我们不可能全部介绍一遍,如果你需要,可以去他们的网站得到更多的相关信息。 读取一个Sheet 在CEGUI中,读取一个sheet 是非常简单的。 WindowManager类提供了一个 ‘loadWindowLayout’方法来读取sheet,然后放进一个 CEGUI::Window的对象中。然后你可以通过CEGUI::System::setGUISheet 来显示它。我们不会在这个教程中来使用这个,不过我会给予一个例子。 不要把这些代码放进你的程序中(或izhezai你看完结果后删除它们) // Do not add this to the program CEGUI::Window *guiRoot = CEGUI::WindowManager::getSingleton().loadWindowLayout("TextDemo.layout"); CEGUI::System::getSingleton().setGUISheet(guiRoot); 这些代码让 一个 sheet显示出来。在程序的其他地方,你可以使用 System::getGUISheet,来找回这张sheet。你也可以使用 setGUISheet来无缝的替换成其他的 Sheet(请确保你保存好了当前sheet的指针,以保证你以后想要替换回来。) 手动创建一个Object 如我前面说过,绝大多数时间,你使用CEGUI时,会使用 editor创建的 GUI sheet。但不管怎样,你会需要学习手动的来创建一个部件,并把它放置到屏幕上。在这个例子中,我们将放置一个退出按钮,之后我们会来编写它的功能。 首先,我们需要创建一个默认的 CEGUI::Window 。请把以下代码,增加到 createScene 函数。 CEGUI::WindowManager &wmgr = CEGUI::WindowManager::getSingleton(); CEGUI::Window *sheet = wmgr.createWindow("DefaultWindow", "CEGUIDemo/Sheet"); 在代码中,我们使用了 WindowManager 来创建了一个名叫CEGUIDemo/Sheet 的 ‘DefaultWindow’。当然,我们可以给它随便取一个名字,但是,分级取名,比如‘SomeApp/MainMenu/Submenu3/CancelButton’这种形式,是非常普遍的。 然后,我们创建一个退出按钮,并给它设置 大小。 CEGUI::Window *quit = wmgr.createWindow("TaharezLook/Button", "CEGUIDemo/QuitButton"); quit->setText("Quit"); quit->setSize(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.05, 0))); 这有点难以理解。 CEGUI使用了统一的尺寸( "unified dimension")来定义大小和位置。我们设置尺寸时,你需要创建一个 UDim对象,并告知它大小。第一个参数是它在它的父窗口中的相对大小,第二个参数是这个对象的绝对大小。(单位:像素)重要的是,你只能设置UDim的一个参数,而另一个参数,必须为0。所以,在这里,我们设置这个button为宽为父窗口的 15%, 高为父窗口的 5%。如果我们想要指定它为 20*5像素,那么我们就需要设置第二个参数了。 最后,我们必须把这个 button 固定到 我们创建的sheet上,然后把这个sheet设为当前GUI sheet。 sheet->addChildWindow(quit); CEGUI::System::getSingleton().setGUISheet(sheet); 现在,如果你编译运行程序,你将看到一个推出按钮。但是你点击时,它不会做任何事。 Events Events的使用,在 CEGUI中,是很灵活的。它使用回调机制,把 事件的句柄,绑定在一些 public function 上。但是,这也导致了注册这些事件,会有一点复杂。我们现在就把 点击了退出按钮的这个事件的句柄,注册到退出程序这个功能上。这里,我们会需要使用到一个指向退出按钮的指针。 请添加以下代码到 BasicTutorial7::createScene中创建退出按钮的代码的后面: quit->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&BasicTutorial7::quit, this)); 现在我们得到了一个指向这个按钮的指针,我们需要预定一个点击事件。 CEGUI中的每个部件,都有一套事件,它们都以‘Event’打头,以下的代码,我们用来绑定事件到按钮上: quit->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&TutorialListener::quit, this)); 第一个参数是被预定的事件,是这事件本身。第二个参数是一个 Event::subscriber 的对象当我们创建一个 Subcriber的对象,我们首先需要一个能操作这个事件的函数的指针。然后,我们要指出是谁来操作这个事件,(在这里,就是这个按钮本身,我们填写: this)好了!我们的 TutorialListener::quit 函数(需要提前定义好) 将会来操作这次的鼠标点击事件,并且退出程序。 编译并运行你的程序,看看是否能有此效果。 注意: 我们可以创建大量的函数来操作CEGUI的事件。但是,唯一需要注意的是: 这些函数必须返回一个bool值,并且它们只能有一个类型为 “const CEGUI::EventArgs &”的参数。更多的关于 事件(events),并且如何注销它们,请阅读 CEGUI网站的相关文档。 渲染纹理 这是一件比较有趣的事,我们可以用 CEGUI 来创建一个渲染器来渲染一个window的纹理。 CEGUI允许我们创建第二个视口(Viewport)来直接渲染到CEGUI的部件。为了完成这个步骤,首先我们需要建立一个场景(scene)。 添加以下代码到 createScene函数的底部: mSceneMgr->setAmbientLight(Ogre::ColourValue(1, 1, 1)); mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8); Ogre::Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh"); Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0, 0, -300)); headNode->attachObject(ogreHead); 现在我们可以创建一个渲染纹理。 RenderSystem对象提供了功能来帮助我们渲染一个纹理。我们可以通过 TextureManager::createManual 函数来创建一个纹理。 我们首先创建一个 512*512像素的纹理: Ogre::TexturePtr tex = mRoot->getTextureManager()->createManual( "RTT", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, 512, 512, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); Ogre::RenderTexture *rtex = tex->getBuffer()->getRenderTarget(); 请查看API reference 来获取此函数的更多信息。 然后我们创建一个 Camera 和一个Viewport 来观察我们所创建的场景。请注意,我们已经改变了一些视口的选项,包括关闭了 Overlays。。。这是一个很重要的操作,当你将在我们的小 window中使用 CEGUI和Ogre 的overlays。 Ogre::Camera *cam = mSceneMgr->createCamera("RTTCam"); cam->setPosition(100, -100, -400); cam->lookAt(0, 0, -300); Ogre::Viewport *v = rtex->addViewport(cam); v->setOverlaysEnabled(false); v->setClearEveryFrame(true); v->setBackgroundColour(Ogre::ColourValue::Black); 请注意,我们增加了一个ViewPort到这个纹理上(我们并没有在 RenderWindow上,而是创建在纹理上,这是一个比较通用的做法) 现在我们已经创建了csene 和我们的texture,我们现在需要把他们嵌入到CEGUI中去。 你可以使用 CEGUI::OgreRenderer::createTexture 函数来从任何 Ogre的 texture,来创建一个 CEGUI::Texture。 CEGUI::Texture &guiTex = mRenderer->createTexture(tex); 不幸的是,这将使得我们的处理变得复杂。在CEGUI中,你永远不需要去处理一个单个的 Texture 或者一个单个的图片。 CEGUI在工作中使用了图片组,而不是单个的图片。当你定义一个皮肤的外观时,使用整个图片表,将是非常方便的。(你可以去看一下在CEGUI SDK里,datefiles/imagesets文件夹中的 TaharezLook.tga,将会了解到如何使用这个图片组) 不管怎样,当你试图去只创建一个单个的图片(image)时,你也必须创建一个整个的图片组(image set)。 以下代码可以实现这个功能: CEGUI::Imageset &imageSet = CEGUI::ImagesetManager::getSingleton().create("RTTImageset", guiTex); imageSet.defineImage("RTTImage", CEGUI::Point(0.0f, 0.0f), CEGUI::Size(guiTex.getSize().d_width, guiTex.getSize().d_height), CEGUI::Point(0.0f, 0.0f)); 第一行代码从 我们提供的 texture,创建了一个 image set (我们给它取名为 RTTImageset)。接下来一行代码(defineImage方法),指向了第一张,也是唯一的一张图片(我们取名为 RTTImage),它和我们提供的 整个 guiTex 一样大。 最后,我们需要创建创建一个 StaticImage 部件,来防止这个 渲染器的 texture。 以下这段代码和创建其他 window并无区别: CEGUI::Window *si = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/StaticImage", "RTTWindow"); si->setSize(CEGUI::UVector2(CEGUI::UDim(0.5f, 0), CEGUI::UDim(0.4f, 0))); si->setPosition(CEGUI::UVector2(CEGUI::UDim(0.5f, 0), CEGUI::UDim(0.0f, 0))); 现在我们需要指定这个 StaticImage 部件去指向哪张image 再一次的,大家已经知道了,CEGUI只操作整个 Image set,而不是操作单张的图片,所以我们需要找到这张图片在 Image set 中的name,然后把图片显示出来。 si->setProperty("Image", CEGUI::PropertyHelper::imageToString(&imageSet.getImage("RTTImage"))); 这种做法就好比是:我们先把一个纹理打包进 image set,然后在使用的时候,再去解包解出来。这种做法,在 CEGUI中,并不是最简单或者说是最能让人理解的。 最后,我们需要把这个 StaticImage 部件,放入我们前面创建出来的 GUI sheet中去,就OK了。 sheet->addChildWindow(si); 现在,我们大功告成了!编译并且运行你的程序吧! 结尾 除了 CEGUI,我们实际上还有其他的选择: • QuickGUI • MyGUI 本文转自:http://www.onlinegamediy.com/thread-88-1-1.html |