OGRE基础教程七 CEGUI and Ogre

本文介绍了如何将CEGUI GUI库集成到Ogre 3D应用程序中,包括配置资源组、初始化CEGUI、处理键盘鼠标事件及创建GUI窗口等关键步骤。

英语水平有限,欢迎大家批评指正微笑

本文并没有将原文全部翻译,只是将其中的一些知识点翻译总结了一下,想要查看详细讲解的话,可以到原文处看一下,附上英文原文地址:http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Basic+Tutorial+7&structure=Tutorials

A Brief Introduction To CEGUI

   CEGUI是一款可以嵌入像OGRE(还有OpenGL,DirectX,Irrlicht等)这样的3D应用程序中的全功能GUI库。正如OGRE只是一款图形库一样(不对声音、物理等做处理),CEGUI也只是一款GUI库,它既不渲染自己也不处理任何鼠标键盘事件。实际上,为了能渲染CEGUI,你得给它提供一个渲染器(CEGUI提供的CEGUIOgreRenderer库就是这样一个渲染器)。为了让它也可以理解鼠标键盘事件,你还要把它们手动的加到系统中。刚开始这可能让你有点痛苦,但做到这些只需要很少的代码就可以了。CEGUI不会处理渲染和输入,这些还是由你全权处理。

Integrating with Ogre

Defining CEGUI resource groups

   CEGUIOGRE一样需要不同种类的资源,它有几种需要找到各自资源位置的资源管理器,所以你需要在resources.cfg中定义所需要的资源组和他们的位置。

   CEGUI的资源位置根据不同的平台可以在不同位置找到,如在Linux中,他们通常被安装在/usr/local/share/CEGUI or /usr/share/CEGUIframeRenderingQueued这里。把下面的内容添加到resources.cfg中,用CEGUI的数据文件夹的路径代替"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

Initializing 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");

   如你看到的,我们用了我们在resources.cfg中定义的资源组。CEGIO是高度可定制的,允许你通过改变他的皮肤(方案,依据CEGUI)来定义你的程序的外表和感觉。下面的代码选择了皮肤:

   CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme");

   下面要做的就是设置默认的鼠标光标:

   CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");

   第一个参数设置了Imageset,第二个参数设置了ImagesetImage的名字。本教程中即使我们不使用CEGUI的其他库,我们也会使用CEGUI来显示鼠标光标。也可能使用其他的GUI库来渲染鼠标或简单的使用OGRE来直接创建鼠标光标。如果你只使用CEGUI来渲染鼠标光标并且比较注重内存使用或游戏所占用的硬盘空间,你可以参考这些方法来代替CEGUI

   最后注意在最后的代码片段我们设置了鼠标光标,但我们没有直接使用MouseCursor :: setImage函数来设置鼠标光标。这是因为本教程中我们将学校几种CEGUI窗口(虽然可能是不可见的),所有这种默认的光标会使鼠标光标变成我们所选择的图片。如果我们直接设置鼠标光标并且没有默认设置,那么每当它经过一个CEGUI窗口时(本教程中是所有时刻),鼠标光标就会不可见。另一方面,如果没有任何CEGUI窗口显示,设置默认的鼠标图片没有任何用(dose nothing)。如果是这样,那么调用MouseCursor::setImage()函数将显示光标:

   // Do not add this to the program

CEGUI::MouseCursor::getSingleton().setImage( CEGUI::System::getSingleton().getDefaultMouseCursor());

Removing SDKTrays

   继续之前,我们需要移除程序中的OgreBites SDKTrays通过重载两个函数:createFrameListener()frameRenderingQueued()

createFrameListener

   BaseApplication::createFrameListener函数的内容复制到 BasicTutorial7::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;

}

   我们只是移除了mTrayMgrmDetailsPanel部分。

Injecting Key Events

   CEGUI不对输入做任何处理,它不读取鼠标动作和键盘输入。它依赖用户来讲鼠标和键盘事件加入系统中。下面我们要做的就是处理键盘事件。

   如果你使用的是CEGUI,你需要使用键盘鼠标的缓存模式,这样你就可以直接接收事件并处理(inject)当他们发生时。在keyPressed函数中添加如下代码:

   CEGUI::System &sys = CEGUI::System::getSingleton();

   sys.injectKeyDown(arg.key);

   sys.injectChar(arg.text);

   return true;

   得到系统对象后,我们需要做两件事。第一就是把键按下事件加到CEGUI中,第二就是添加被按下的按键字符。正确的添加字符是很重要的,因为当使用的是非英文键盘时,映射键按下时不会总是能够得到理想的结果。映射字符只支持Unicod

   现在需要把键松开事件加入系统,添加如下代码到keyReleased函数:

   CEGUI::System::getSingleton().injectKeyUp(arg.key);

   return true;

Converting and Injecting Mouse Events

   现在我们处理鼠标输入,我们有一个问题需要思考一下,当我们像CEGUI中添加键按下和松开事件时,我们从来都不需要转换键。键盘输入中,OISCEGUI使用相同的键码。但鼠标按键却是不同的,在我们可以吧鼠标按钮事件添加到CEGUI之前,我们需要写一个把OIS按钮ID转换为CEGUI按钮ID的函数。添加如下代码到BasicTutorial1.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;

    }

}

   它可以作为(stay)一个本地静态函数,所有不需要再头文件中声明它。现在可以添加鼠标事件了,在mousePressed函数中添加如下代码(代替原来的代码):

   CEGUI::System::getSingleton().injectMouseButtonDown(convertButton(id));

   return true;

   最后,我们需要把鼠标动作添加到CEGUI中。CEGUI::System 对象有一个处理相关鼠标动作的injectMouseMove函数。OIS::mouseMoved传给我们鼠标在state.X.relstate.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);

return true;

Windows, Sheets, and Widgets

Introduction

   CEGUI与其他大多GUI系统相比比较难,在CEGUI中,所有显示的东西都是CEGUI::Window类的一个子类,而且一个窗口可以有任意多个子窗口。即当你创建一个框架包含多个按钮,这个框架就是一个窗口。

   这会引起一些奇怪的事情发生,你可以吧一个按钮放到另一个按钮内,即使这在现实中不会发生。产生这些的原因是,当你正在找某一特定的你放在程序中的控件时,你要知道他们都被叫做窗口并通过适用于他们的函数来获取(access)他们。

   在对CEGUI的实际应用中,你不用通过代码来创建每一个单独的对象。而是在一个编辑器(如CEGUILayout编辑器)中为你的程序创建一个GUI布局(layout)。把你的窗口、按钮和其他控件按你所愿的放在屏幕上后,编辑器把布局保存为一个文本文件。后面你可以把它加载到CEGUI称为的GUI表单中(这也是CEGUI::Window的子类)。CEGUI包含了很多你可以再你的程序中使用的控件。

Loading a Sheet

   CEGUI中加载一张表单时很容易的,WindowManager类提供了一个加载表单并把它放入一个CEGUI::Window对象中的函数loadWindowLayout。然后调用CEGUI::System::setGUISheet来显示表单。

// Do not add this to the program

CEGUI::Window *guiRoot = CEGUI::WindowManager::getSingleton().loadWindowLayout("TextDemo.layout");

CEGUI::System::getSingleton().setGUISheet(guiRoot);

   它设置了现在显示的表单,通过调用System::getGUISheet函数你可以获得这张表单。你还可以通过调用setGUISheet函数把GUI表单和任何你想要转换的表单进行无缝转换(确保持有一个指向当前表单的指针,如果你想把它装换回来)。

Manually creating an Object

   如我之前所说,通常你使用CEGUI时,你都会使用用编辑器创建的GUI表单。偶尔你也需要手动创建一个控件放到屏幕上。本例中,我们将添加一个退出按钮。由于随着教程的推进我们不仅仅会只在屏幕上加一个退出按钮,我们需要首先创建一个默认的用来容纳我们创建的控件的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。本例中我们让一个按钮是他的父对象的宽的15%、高的5%。如果我们像把他设为20 pixel*5 pixel,我们就要相对的设置UDim的第二个参数为205,而且第一个参数为0

最后就是把退出按钮绑定到我们创建的表单上,然后设当前系统的GUI表单就是这个表单。

sheet->addChildWindow(quit);

CEGUI::System::getSingleton().setGUISheet(sheet);

现在编译运行程序,你将看到在屏幕左上角有一个退出按钮,但当你点击它是没人有任何事情发生。

Events

事件在CEGUI中时很灵活的,它使用一个绑定了所有公有函数的回调进程作为事件句柄,不幸的是这也使得注册事件变得更复杂。我们现在注册来处理退出按钮的点击事件,当它被按下时退出程序。因此我们首先需要一个指向我们之前创建的退出按钮的指针。创建好你的退出按钮窗口后,添加如下代码到BasicTutorial7::createScene函数中:

quit->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&BasicTutorial7::quit, this));

这将订阅(subscribe)点击事件。CEGUI中的每一个控件都有一个它支持的事件集,而且他们都从"Event"开始。subscribeEvent的第一个参数是事件本身,第二个参数是一个Event::Subscriber对象。当创建一个Subscriber对象时,我们第一个要传的参数就是一个指向处理事件的函数的指针。第二个要传给subscriber的参数就是处理事件的BasicTutorial7对象(就是"this"对象)。

BasicTutorial7::quit函数将处理鼠标点击和终止程序的事件。添加如下代码到BasicTutorial7::quit函数中:

mShutDown = true;

return true;

Render to Texture

我们可以使用CEGUI使用的另一件有趣的事就是创建一个渲染到纹理(render-to-texture)窗口。这允许我们创建一个可以直接在一个CEGUI控件中渲染的第二视口。为此我们需要从设置一个场景的外观开始。

添加如下代码到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);

现在我们必须创建RenderTexture(渲染纹理)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();

下面攒机一个Camera和一个视口来观察我们创建的场景。注意我们改变了两个视口的选项,包括关闭Overlays—如果没有关闭CEGUIOgre 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);

Note that we have added the Viewport to the texture itself (as opposed to the RenderWindow, which is where we usually add Viewports).

现在我们已经创建了场景和纹理,我们需要把它嵌入到CEGUI中。

你可以调用CEGUI::OgreRenderer::createTexture函数从任意的Ogre纹理中创建一个CEGUI::Texture

CEGUI::Texture &guiTex = mRenderer->createTexture(tex);

不幸的是,这里就是事情变得麻烦的地方。CEGUI中你永远不会只处理一个纹理或一张图片。CEGUI对图片集有效,而对独立的图片无效。当你要定义你要创建的皮肤的外观是,图片集是很有用的。但是即使你要只使用一张图片,你也必须为它创建一个图片集。

这就是我们下面要做的:

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));

第一行根据我们提供的纹理创建了图片集("RTTImageset")。下一行(调用了defineImage)图片名为"RTTImage"而且它和我们提供的guiTex纹理一样大。最后我们要创建StaticImage控件来存放渲染纹理。第一部分和创建任何其他窗口没什么不同:

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控件要显示那张图片。由于CEGUI只处理图片集而不是单独的图片,所有我们必须得到图片集中图片的名字并显示它:

si->setProperty("Image", CEGUI::PropertyHelper::imageToString(&imageSet.getImage("RTTImage")));

If it seems like we have packed a texture into an image set only to unpack it again, it's because that's exactly what we have done.

CEGUI中处理图片并不是一件简单的事情。最后把StaticImage控件添加到我们之前创建的GUI表单中:

sheet->addChildWindow(si);

编译并允许你的程序!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值