基础教程七(CEGUI和OGRE II)

先决条件

本教程是在假设你已经拥有c++编程基础并能够成功建立和编译Ogre程序(如果你设置程序方面还存在问题,请参阅SettingUpAnApplication来获取详细信息)。 本教程建立在之前的初学者教程的基础上,并且假设你已经学习了它们。

[编辑]介绍

在这一课里,我们将一起来探索如何在Ogre里使用CEGUI(一个嵌入的GUI系统)。学完本课以后,你应该能够往你的Ogre应用程序里添加基本的CEGUI功能了。注意:本课并不会全面地教你如何使用CEGUI,本课只是一个入门。若有更深入的问题需要帮助,请直接去它们的主页。

你能在这里找到本课的代码。在你学习本课时,你应该逐个地往你的工程里添加代码,编译后观察它的效果。

[编辑]从这开始

[编辑]最初的代码

在这节课里,你将用预定义的基础代码作为起点。只要你学过之前的课程,你应该对它们已经很熟悉了。用你喜欢的编译器创建一个工程,然后新建一个源文件包含如下代码:

#include "ExampleApplication.h"
#include 
#include 
#include 

class TutorialListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener
{
public:
   TutorialListener(RenderWindow* win, Camera* cam)  : ExampleFrameListener(win, cam, true, true)
   {
      mContinue=true;
      mMouse->setEventCallback(this);
      mKeyboard->setEventCallback(this);
   } // CEGUIDemoListener

   bool frameStarted(const FrameEvent &evt)
   {
       mKeyboard->capture();
       mMouse->capture();

       return mContinue && !mKeyboard->isKeyDown(OIS::KC_ESCAPE);
   }

   bool quit(const CEGUI::EventArgs &e)
   {
       mContinue = false;
       return true;
   }

   // MouseListener
   bool mouseMoved(const OIS::MouseEvent &arg)
   {
       return true;
   }

   bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
   {
       return true;
   }

   bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
   {
       return true;
   }

   // KeyListener
   bool keyPressed(const OIS::KeyEvent &arg)
   {
       return true;
   }

   bool keyReleased(const OIS::KeyEvent &arg)
   {
       return true;
   }

private:
   bool mContinue;
};


class CEGUIDemoApplication : public ExampleApplication
{
public:
   CEGUIDemoApplication()  : mSystem(0), mRenderer(0)
   {
   }

   ~CEGUIDemoApplication() 
   {
       if (mSystem)
           delete mSystem;

       if (mRenderer)
           delete mRenderer;
   }
protected:
  CEGUI::System *mSystem;
  CEGUI::OgreCEGUIRenderer *mRenderer;

   void createScene(void)
   {
   }

   void createFrameListener(void)
   {
       mFrameListener= new TutorialListener(mWindow, mCamera);
       mFrameListener->showDebugOverlay(true);
       mRoot->addFrameListener(mFrameListener);
   }

};

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"


INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
   // Create application object
   CEGUIDemoApplication app;

   try {
       app.go();
   } catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
       MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!",
           MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
       fprintf(stderr, "An exception has occurred: %s/n",
           e.getFullDescription().c_str());
#endif
   }


   return 0;
}

确保你能编译并运行这些代码。目前这个程序除了显示一个黑屏(按ESC退出)什么都不能做。如果你在编译时遇到连接器错误,请保证CEGUIBase_d.lib、OgreGUIRenderer_d.lib已经添加到了连接器的输入里(debug模式的,如果是release模式,去掉_d后缀)。

[编辑]简单介绍

CEGUI是一个功能全面的GUI库,它能够植入像Ogre这样的3D应用程序(当然也支持纯DirectX和OpenGL)。就像Ogre只是一个图像库一样(不做声音、物理等其它的事情),CEGUI只是一个GUI库,意味着既不自己做渲染,也不与任何鼠标键盘事件挂钩。实际上,为了让CEGUI能渲染,你必须提供一个渲染器给它(包含在SDK的OgreGUIRenderer库)。而为了让它能够理解鼠标键盘事件,你必须手工地把它们注入系统。这也许一开始看起来觉得痛苦,但其实只需要一丁点代码就能实现。这样,你就可以对渲染和输入进行全面控制,CEGUI根本不会防碍你。

CEGUI有很多的方面,有很多你不熟悉的诡异的地方(就算你以前使用过GUI系统)。在接下来的学习中,我将给你一一道来。

[编辑]与Ogre结合

[编辑]初始化 CEGUI

在前面的教程里,我们已经学习了如何启动CEGUI,所以在这第一个部分我们不会深究。找到createScene函数并添加如下代码:

      mRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
      mSystem = new CEGUI::System(mRenderer);

好,CEGUI已经初始化了,我们选择它将使用的皮肤。CEGUI是高度自定义的,它允许你通过更换皮肤来定义应用程序的"look and feel"。我们将不涉及如何给这个库换皮肤,所以如果你想了解得更多,咨询CEGUI的网站。以下的代码选择了一个皮肤:

      CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");

默认的Ogre安装不提供其它的皮肤给你,但如果你是从CEGUI网址上下载的,你就能找到几个其它的选择(或者你自己就可以制作)。接下来我们要做的事件就是设置默认鼠标指针图像和默认字体:

      mSystem->setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow");
      mSystem->setDefaultFont((CEGUI::utf8*)"BlueHighway-12");

在整个教程里,我们都会使用CEGUI来显示鼠标指针,就算我们没有使用这个GUI库的其它功能。用其它的GUI库来渲染鼠标,或者直接用Ogre来创建你自己的鼠标,也是可以的(后者要麻烦一点)。如果你只为了鼠标指针而使用CEGUI,并担心内存的使用以及游戏所占的硬盘空间,你可以从这些选择中找一个来代替CEGUI。

最后请注意,在最后一段代码里我们设置了默认的鼠标指针,但我们并没有像后面的教程那样直接使用MouseCursor::setImage函数来设置。这是因为在这一课里,我们总是在CEGUI窗口上面(虽然它可能是不可见的),而设置默认鼠标指针将会使得它是我们选择的图像。如果我们直接设置鼠标指针,而不设置默认的,则每当经过当CEGUI窗口时,鼠标指针都是不可见的(在本课里总是这种情况 )。另一方面,如果你不显示任何CEGUI窗口,设置默认的鼠标指标将不产生任何效果,后面的教程就是这种情况。那样的话,调用MouseCursor::setImage才能在应用程序里显示指针。

[编辑]注入键盘事件

CEGUI不会自己处理输入,它不读取鼠标的移动和键盘的输入。相反地,它依赖用户把按键鼠标事件注入到系统。接下来我们要做的就是处理这些事件。若你正使用CEGUI,你需要以缓冲模式运行鼠标键盘,这样你就能直接获取事件并在它们发生时注入。找到keyPressed函数,并添加代码:

      CEGUI::System *sys = CEGUI::System::getSingletonPtr();
      sys->injectKeyDown(arg.key);
      sys->injectChar(arg.text);

得到系统对象之后,我们做两件事。第一件事是把Key Down事件注入到CEGUI。第二件是注入一个实际按下的字符。正确地注入字符是非常重要的,因为如果使用的是非英语键盘,只凭注入KeyDown事件并不能总是取到想要的结果。记住injectChar是支持Unicode的。

现在我们给系统注入KeyUp事件。找到keyReleased函数,添加以下代码:

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

注意,我们这里不需要注入字符up事件,KeyUp使用就可以了。

[编辑]转变和注入鼠标事件

现在我们完成了键盘输入的处理,下面我们来看看鼠标输入。然而我们有个小问题需要说一下。当我们注入KepUp和KepDown事件到CEGUI里时,我们不必转换键码。OIS和CEGUI为键盘输入使用相同的键码。但鼠标按钮却不是这样。在按下鼠标按钮注入到CEGUI时,我们需要写一个函数来把OIS的按钮ID转换为CEGUI的按钮ID。在你代码顶部附近,TutorialListener类前面,添加以下函数:

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

现在我们准备注入鼠标事件。找到mousePressed函数,并添加以下代码:

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

这代码的意思应该比较清楚了。我们转换传入的ID,再把结果传给CEGUI。找到mouseReleased函数,并添加这么一行:

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

最后,我们往CEGUI里注入鼠标移动。The CEGUI::System对象有一个injectMouseMove方法,它需要鼠标的相对运行作为参数。OIS::mouseMoved处理者在state.X.rel和the state.Y.rel变量里给我们提供了这些相对运动。找到mouseMoved方法,并添加以下代码:

      CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);

好了!CEGUI设置完成了,可以接收鼠标和键盘事件了。

[编辑]窗口、表单、组件

[编辑]介绍

CEGUI与其它多数GUI系统有所不同。在CEGUI里,所有能显示出来的东西都是CEGUI::Window类的一个子类,而且一个window可以有任意数量的子window。这样会导致一些奇怪的事情发生。你能在一个按钮里面放置另外一个按钮,虽然实际上不会这么干。我提这些的原因是,当你正寻找放在应用程序里的某个特殊的小部件时,它们都被称作Windows,而且可以通过访问Windows的函数来访问。

CEGUI最常用的用法是,你不必在代码里创建每一个单独的对象。而你可以通过一个像CEGUI Layout Editor这样的编辑器,来为你的程序创建一个GUI布局。根据你的喜好,放置你的窗口、按钮以及其它部件到屏幕上之后,编辑器会把布局保存到一个文本文件里。你就可以之后加载这个布局到GUI sheet里面(它也是CEGUI::Window的一个子类)。

最后,要知道CEGUI包含大量的小部件供你的程序使用。我们在这里不去涉及,所以如果你决定使用CEGUI,最好还是去它们的网站了解更多的信息。

[编辑]载入表单

在CEGUI里载入一个表单(sheet)是非常容易的事。WindowManager类提供了一个"loadWindowLayout"函数来加载表单,并把它放入CEGUI::Window对象。然后你可以通过CEGUI::System::setGUISheet来显示它。我们在本课不会使用它,但如果不给你介绍一下使用它的例子,我觉得我很失职。别把这些添加到代码里(如果添加了,看见结果后删除掉):

      // Do not add this to the program
      CEGUI::Window* sheet = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)"ogregui.layout"); 
      mSystem->setGUISheet(sheet);

这样就把表单设置成显示了。你能在后面取回这个表单,用System::getGUISheet方法。通过调用setGUISheet,你能够无缝地交换GUI表单(但你必须要保证拥有指向当前表单的指针,如果你打算再换回来)。

[编辑]手动创建部件

就像我前面所说的,大多数你使用CEGUI的时候,你使用的是由编辑器生成的GUI表单。然而有时候,你需要手动地创建一个部件以呈现在屏幕上。在这个例子中,我们将添加一个Quit按钮,我们稍后会给它添加功能。因为当本课结束时,我们不只会添加一个Quit按钮到屏幕上,我们要创建一个默认的CEGUI::Window来包含所有我们要创建的部件。将以下代码添加到createScene函数末尾:

       CEGUI::WindowManager *win = CEGUI::WindowManager::getSingletonPtr();
      CEGUI::Window *sheet = win->createWindow("DefaultGUISheet", "CEGUIDemo/Sheet");

这里使用WindowManager创建了一个被称为"CEGUIDemo/Sheet"的"DefaultGUISheet"。尽管我们可能为表单取任何名称,但用层次结构的方法来命名它是非常常见的(推荐),比如“SomeApp/MainMenu/Submenu3/CancelButton”。然后我们要做的事情就是,创建一个Quit按钮,并设置它的大小:

      CEGUI::Window *quit = win->createWindow("TaharezLook/Button", "CEGUIDemo/QuitButton");
      quit->setText("Quit");
      quit->setSize(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.05, 0)));

这个的含义比较隐晦。CEGUI里的大小和方位使用了一种“统一化的尺度”。当设置大小的时候,你必须创建一个UDim对象来告诉它到底是什么尺寸。第一个参数是这个对象相比于其父亲的相对大小。第二个参数才是对象的绝对大小(像素表示)。必须注意的是,你只能给UDim设置两种参数的一种,另一种必须是0。所以在这里,我们创建了一个宽度为父亲的15%且高度为5%的按钮。如果我们想要把它设置成20×5像素,就要两个UDim的第二参数分别设置为20和5。

我们要做的最后一件事情是,把Quit按钮粘合到我们创建的表单上,并把它设成当前系统的GUI表单:

      sheet->addChildWindow(quit);
      mSystem->setGUISheet(sheet);

现在如果你编译并运行了你的程序,你将会在屏幕左上角看到一个Quit按钮,但当你点击它时不会有任何反应。

[编辑]事件

CEGUI里的事件是非常灵活的。它不是实现一个接收事件的接口,而是使用一种回调机制,把任何公共函数绑定成一个事件处理器(通过适当的方法签名)。不幸的是,这也意味着注册事件会比Ogre更麻烦。我们现在要为处理Quit按钮的点击事件进行注册。这样的话,我们首先需要一个指向Quit按钮的指针。找到TutorialListener的构造器,并添加以下代码:

      CEGUI::WindowManager *wmgr = CEGUI::WindowManager::getSingletonPtr();
      CEGUI::Window *quit = wmgr->getWindow((CEGUI::utf8*)"CEGUIDemo/QuitButton");

现在我们取得了指向按钮的指针,我们来订阅这个点击事件。CEGUI里的所有部件都有它们所支持的事件集,而且都是以“Event”开头。这里是订阅事件的代码:

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

subscribeEvent的第一参数是事件本身。第二个参数是一个Event::Subscriber对象。当创建Subcriber对象时,我们传入的第一个东西是指向处理事件函数的指针(注意&符号给出了函数的指针)。我们传经subscriber的第二个参数是处理这个事件的TutorialListener对象(就是“this”对象)。好了!我们的TutorialListener::quit函数(已经定义了)将会处理鼠标点击并终止程序。

编译并运行你的程序,调试一下看看。

注意,我们可以创建任意数量的函数来为CEGUI处理事件。对它们的唯一约束是,返回值必须是一个布尔型,而且必须带有类型为"const CEGUI::EventArgs &"的唯一参数。关于事件的更多信息(以及如何退订事件),请查阅CEGUI的网站。

[编辑]渲染到纹理

我们能用CEGUI来做更有趣的事,其中之一就是创建一个纹理窗口的渲染器。这让我们能够创建一个可以直接渲染到CEGUI部件的视口。这样的话,我们首先要创建一个让我们看见的场景。在createScene函数底部添加如下代码:

      mSceneMgr->setAmbientLight(ColourValue(1, 1, 1));
      mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
      Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh");
      SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Vector3(0, 0, -300));
      headNode->attachObject(ogreHead);

现在我们创建好了一个渲染纹理。RenderSystem对象提供了一种渲染到纹理的功能。我们用RenderSystem::createRenderTexture函数创建一个纹理。在这个程序里,我们创建一个512 x 512的纹理:

      RenderTexture *tex = mRoot->getRenderSystem()->createRenderTexture("RttTex", 512, 512, TEX_TYPE_2D, PF_R8G8B8);

UPDATE: The createRenderTexture() method appears to have been deprecated in the trunk. So instead of the above line use this:

      RenderTexture *tex= mRoot->getTextureManager()->createManual
                                 ("RttTex", "Default", TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET)
                                  ->getBuffer()->getRenderTarget();

关于这个函数,请查阅API参考来获取更多的信息。接下来我们还要创建一个摄像机和一个视口,以便察看我们创建的场景。注意,我们改变了一些视口设置,包括关闭Overlays。这是非常重要的,否则你会看见CEGUI与Ogre在小窗口里面重叠在一起。

      Camera *cam = mSceneMgr->createCamera("RttCam");
      cam->setPosition(100, -100, -400);
      cam->lookAt(0, 0, -300);
      Viewport *v = tex->addViewport(cam);
      v->setOverlaysEnabled(false);
      v->setClearEveryFrame(true);
      v->setBackgroundColour(ColourValue::Black);

注意,我们把视口添加给了纹理(而我们通常是把视口添加到渲染窗口)。好了,我们已经创建了我们的场景和纹理,现在我们需要把它们植入CEGUI。你能用任何Ogre纹理来创建CEGUI::Texture,通过调用OgreCEGUIRenderer::createTexture函数:

      CEGUI::Texture *cTex = mRenderer->createTexture((CEGUI::utf8*)"RttTex");

不幸的是,这里比较复杂。在CEGUI里,你从来不处理单个的纹理或单个的图像。CEGUI处理的是图像集,而非单个的图像。当你试着定义皮肤的look and feel时,处理一整套图像是很有用的(比如,看一看SDK里media目录TaharezLook.tga的图像是什么样子)。然后,就算你只要定义单个图像,你必须为它创建一个图像集。下面我们就是做这个事情:

      CEGUI::Imageset *imageSet = CEGUI::ImagesetManager::getSingleton().createImageset((CEGUI::utf8*)"RttImageset", cTex);
      imageSet->defineImage((CEGUI::utf8*)"RttImage", 
          CEGUI::Point(0.0f, 0.0f),
          CEGUI::Size(cTex->getWidth(), cTex->getHeight()),
          CEGUI::Point(0.0f,0.0f));

第一行用我们提供给它的纹理,创建了一个图像集(称为"RttImageset")。第二行(调用defineImage),指定了第一个也是唯一一个图像,它被称为"RttImage",而且它的大小与我们提供的cTex纹理大小一样。最后我们还要创建一个StaticImage小部件,来收容这个渲染纹理。第一部分与创建其它窗口没有什么不同:

      CEGUI::Window *si = win->createWindow((CEGUI::utf8*)"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, 0)));

现在我们要指定StaticImage部件将要显示的图像。再一次,由于CEGUI总是处理图像集而不是单独的图像,我们必须从图像集里得到图像名称,并显示它:

      si->setProperty("Image", CEGUI::PropertyHelper::imageToString(&imageSet->getImage((CEGUI::utf8*)"RttImage")));

这似乎是我们把一个纹理包装进了图像集,然后再解包出来,我们实际上就是这么做的。在CEGUI里操作图像并不是这个库中最简单最直接的事件。最后,我们把StaticImage部件添加到先前创建的GUI表单里去:

      sheet->addChildWindow(si);

现在我们完成了。编译并运行这个应用程序。

[编辑]结论

[编辑]其它的选择

CEGUI并不是完美无缺的,它确定不是适用于每一个人。除了CEGUI,这里还有一些其它的选择:

Navi Right Brain Games GUI QuickGUI BetaGUI

[编辑]更多的信息

你还能够在其它的地方找到关于CEGUI的更多信息。

Practical Application - Something With A Bit More Meat - A more in-depth tutorial than the one here. CEGUI's Official Tutorials The CEGUI Website

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CEGUI(Crazy Eddie’s GUI http://www.cegui.org.uk)是一个自由免费的GUI库,基于LGPL协议,使用C++实现,完全面向对象设计。CEGUI开发者的目的是希望能够让游戏开发人员从繁琐的GUI实现细节中抽身出来,以便有更多的开发时间可以放在游戏性上。 CEGUI的渲染需要3D图形API的支持,如OpenGL或Direct3D。另外,使用更高级的图形库也是可以的,比如OGRE、Irrlicht和RenderWare等,关键需求可以简化为二点: 纹理(Texture)的支持直接写屏(RHW的顶点格式、正交投影、或者使用shader实现) 本书截止日时,CEGUI的最新版本是0.6.0(本书的讨论也是基于此版本),本书光盘提供了SDK和全部源码的下载。 除此之外,CEGUI还同步提供了官方界面编辑器LayoutEditor和ImageSet编辑器,以方便UI和图像集的制作。作为界面编辑器,它需要系统级界面以提供编辑器操作,0.3.0版是基于MFC实现的;而在0.4.0版本以后,改为基于wxWidgets(跨平台的本地UI框架,这里的UI指Window操作系统底层,如:Windows、Unix和Mac,详见http://www.wxwidgets.org)实现。 目前将CEGUI作为游戏界面库开发的游戏已经有好多种,国内的天龙八部,巨人等游戏就是很好的例子。 CEGUI的功能是非常强大的,而且使用也非常的灵活,可以和脚本配合。可以通过配置文件自定义窗口外观。通过布局文件实现窗口布局等等特性,使得游戏的界面开发更加方便。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值