基础教程九(Ogre的启动顺序)

介绍

在这一课里,你将学习如何启动OGRE,而不用示例框架。学习完之后,你将会创建你自己的Ogre应用程序,且不使用ExampleApplication或ExampleFrameListener。

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

准备开始

初始代码
   #include 
   #include 
   #include 
   #include 
   
   using namespace Ogre;
   
   class ExitListener : public FrameListener
   {
   public:
       ExitListener(OIS::Keyboard *keyboard)  : mKeyboard(keyboard)
       {
       }
   
       bool frameStarted(const FrameEvent& evt)
       {
           mKeyboard->capture();
           return !mKeyboard->isKeyDown(OIS::KC_ESCAPE);
       }
   
   private:
       OIS::Keyboard *mKeyboard;
   };
   
   class Application
   {
   public:
       void go()
       {
           createRoot();
           defineResources();
           setupRenderSystem();
           createRenderWindow();
           initializeResourceGroups();
           setupScene();
           setupInputSystem();
           setupCEGUI();
           createFrameListener();
           startRenderLoop();
       }
   
       ~Application()
       {
       }
   
   private:
       Root *mRoot;
       OIS::Keyboard *mKeyboard;
       OIS::InputManager *mInputManager;
       CEGUI::OgreCEGUIRenderer *mRenderer;
       CEGUI::System *mSystem;
       ExitListener *mListener;
   
       void createRoot()
       {
       }
       
       void defineResources()
       {
       }
       
       void setupRenderSystem()
       {
       }
       
       void createRenderWindow()
       {
       }
   
       void initializeResourceGroups()
       {
       }
   
       void setupScene()
       {
       }
   
       void setupInputSystem()
       {
       }
   
       void setupCEGUI()
       {
       }
   
       void createFrameListener()
       {
       }
   
       void startRenderLoop()
       {
       }
   };
   
   #if OGRE_PLATFORM == PLATFORM_WIN32 || 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
   {
       try
       {
           Application app;
           app.go();
       }
       catch(Exception& e)
       {
   #if OGRE_PLATFORM == PLATFORM_WIN32 || 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;
   }

从零开始

开始之前,让我们站在更高的层次上看一下启动过程是如何工作的:

  1. 创建Root对象。
  2. 定义Ogre将要使用的资源。
  3. 选择并设置渲染系统(即DirectX, OpenGL等)。
  4. 创建渲染窗口(Ogre所处的窗口)。
  5. 初始化你要使用的资源。
  6. 用这些资源来建立一个场景。
  7. 设置第三方库或插件。
  8. 创建一些帧监听器。
  9. 启动渲染循环

本课将逐一介绍这些步骤。请注意步骤1-4必须严格按顺序进行,而5(初始化资源)和6(创建场景)可以放在更后一点,如果你喜欢的话。但我这里不推荐,除非你真的很了解你的工作顺序。

启动Ogre

创建Root对象

这个很简单,Root对象是Ogre库里最核心的,在你用这个引擎做其它事情之前,你必须先创建它。找到Application::createRoot方法,添加如下代码:

           mRoot = new Root();

Root的构造函数需要三个参数。第一个是插件配置文件的名称和路径。第二个是Ogre配置文件的路径(它告诉Ogre关于显卡、显示设置等信息)。最后一个是日志文件的名称和路径。因为我们不需要修改任何一个属性,所以用默认的就好了。

资源

注意: 你最好在SDK的bin/release目录里找到resources.cfg,看看它的内容是很有帮助的。

下面我们要定义程序将要使用的资源,包括纹理、模型、脚本等等。请记住,你必须预先定义好你的资源,在Ogre能使用它们之前,你还必须对它进行初始化。在这一步里,我们来定义所有程序可能使用的资源。为止,我们把每一个资源所在的文件夹添加到ResourceGroupManager。找到defineResources方法,并添加如下代码:

   String secName, typeName, archName;
   ConfigFile cf;
   cf.load("resources.cfg");

这里使用了一个Ogre的ConfigFile类来解析"resources.cfg"里的所有的资源,但并不是把它们装入到Ogre(你必须手工添加)。记住,你可以自由地使用你自己的文件格式和解析器,只需要用你自己的解析器来替换ConfigFile的。装入资源的方式并不十分重要,只要你能把资源添加到ResourceGroupManager。现在我们解析好了cfg文件,还需要把各部分添加到ResourceGroupManager中。以下代码启动一个循环:

       ConfigFile::SectionIterator seci = cf.getSectionIterator();
      while (seci.hasMoreElements())
      {

每次循环里,我们再循环一次,提取它里面所有的内容:

            secName = seci.peekNextKey();
          ConfigFile::SettingsMultiMap *settings = seci.getNext();
          ConfigFile::SettingsMultiMap::iterator i;

最后,我们添加部件名称(那一组资源的),资源类型(zip,文件夹等等),以及资源本身的文件名,给ResourceGroupManager:

          for (i = settings->begin(); i != settings->end(); ++i)
          {
              typeName = i->first;
              archName = i->second;
              ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
          }
      }

这个方法从config文件添加所有的资源,但它只告诉Ogre它们在哪。但如果你打算使用它们,你还必须初始化它们。我们在后面“初始化资源”一节来介绍。

创建渲染系统

接下来,我们需要选择一个渲染系统(在Windows机器上通常是DirectX或者OpenGL),然后配置它。大多数Demo程序使用的是一个Ogre配置对话框,这是一个很好的一个东东。Ogre提供了一种保存用户设置的方法,意味着除了第一次需要设置外,以后都不需要了。找用setupRenderSystem方法,并添加以下代码:

      if (!mRoot->restoreConfig() && !mRoot->showConfigDialog())
          throw Exception(52, "User canceled the config dialog!", "Application::setupRenderSystem()");

在if语句里的第一部分,我们尝试恢复这个config文件。如果函数返回false,意味着文件不存在,则我们应该显示配置对话框,也就是if语句的第二部分。如果仍然返回false,意味着用户取消了配置对话框(也就是他们想退出程序)。在这个例子里,我们抛出了一个异常,但实际上简单地返回false且关闭应用程序,这样可能更好。由于 restoreConfig 和 showConfigDialog 也可能抛出异常,保存这些真正的异常可能更好。然而,这将增加了教程的不必要的复杂度,我在这里只使用了一个异常。

我还建议,如果你在Ogre的启动过程中捕获了一个异常,你最好在catch里删除这个ogre.cfg文件。因为他们在配置对话框里进行的设置可能导致了问题的发生,所以他们需要更改它。你也可以不使用它,关闭配置对话框可以节省开发时间,因为你不必每次程序运行时确认这些显示设置。

如果你不打算使用Ogre的配置对话框,你需要手动地设置渲染系统。以下是一个基本的例子:

      // 不要把这些添加到程序中
      RenderSystem *rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem");
      mRoot->setRenderSystem(rs);
      rs->setConfigOption("Full Screen", "No");
      rs->setConfigOption("Video Mode", "800 x 600 @ 32-bit colour");

你可以使用Root::getAvailableRenderers来了解有哪些渲染系统可供你的程序使用。一旦你得到了一个渲染系统,你能够使用RenderSystem::getConfigOptions来查看有哪些选项可以提供给用户。利用这两个方法,你可以创建你自己的配置对话框。

创建渲染窗口

目前我们选择了一个渲染系统,我们还需要一个渲染Ogre的窗口。实际上有许多种方式来实现,但这里只介绍两种。

如果你希望Ogre为你创建一个渲染窗口,这是相当容易的事情。找到createRenderWindow方法,这么写:

      mRoot->initialise(true, "Tutorial Render Window");

第一个参数表示是否让Ogre为你创建一个渲染窗口。否则,你可以自己创建一个渲染窗口,通过使用win32 API、wxWidgets或其它Windows/Linux的GUI系统。关于在Windows下的一个简单例子是这样:

      // 别把这些添加到程序里
      mRoot->initialise(false);
      HWND hWnd = 0;  // Get the hWnd of the application!
      NameValuePairList misc;
      misc["externalWindowHandle"] = StringConverter::toString((int)hWnd);
      RenderWindow *win = mRoot->createRenderWindow("Main RenderWindow", 800, 600, false, &misc);

在这里你仍然使用Root::initialise,第一个参数设置成了false。然后,你必须获取你希望Ogre渲染的窗口的句柄。你如何取得它,完全决定于你用来创建窗口的GUI工具箱(在Linux下我估计这有一点区别)。你拥有了它之后,你通过NameValuePairList handle把这个句柄赋予"externalWindowHandle"。Root::createRenderWindow方法被用来从你创建的窗口来创建RenderWindow对象。想了解更多,参考这个方法的API文档。

初始化资源

现在我们创建了Root对象、渲染系统、以及渲染窗口,继续。接下来是初始化我们将要使用的资源。从mesh到脚本,所有的东西,在某一时刻,我们只用到这些资源其中的一小部分。为了减少内存消耗,我们可以只加载正在使用的资源。为止,我们把资源分解成各种部分,只在运行时初始化它们。在本课里,我们不将详细介绍。其它的地方有一个专门介绍资源的教程。初始化资源之前,我们应该设置纹理mipmap的缺省值。找到initializeResourceGroups方法,添加如下代码:

      TextureManager::getSingleton().setDefaultNumMipmaps(5);
      ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

这样程序就拥有了所有已经被初始化的资源组以供使用。

创建场景

你应该了解在把各种东西添加到场景之前,你要做三件事:创建场景管理器(SceneManager)、创建摄像机(Camera)、创建视口(Viewport)。在setupScene方法里添加如下代码:

      SceneManager *mgr = mRoot->createSceneManager(ST_GENERIC, "Default SceneManager");
      Camera *cam = mgr->createCamera("Camera");
      Viewport *vp = mRoot->getAutoCreatedWindow()->addViewport(cam);

如果你需要的话,可以创建多个场景管理器、多个摄像机,但当你真正打算使用摄像机把事物渲染到屏幕上时,请确保你已经为它添加了视口中。为止,你要借助RenderWindow类,它在“创建渲染窗口”一节里被建立。由于我们没有这个对象的指针,所以我们通过Root::getAutoCreatedWindow方法来获取它。

这三件事完了以后,你可以尽情地往你的场景里添加物体了。

设置第三方库

OIS

虽然在OGRE里,OIS不是唯一的选择,但它是最好的之一。我来简单介绍一下OIS如何在程序里启动。若真想要使用这个库,请参考这个教程,以及OIS自身的文档。

设置无缓冲输入

OIS使用一个统一的InputManager,它比较难配置,但一旦正确地创建之后,非常好使用。实际上,它只是需要Ogre渲染窗口的句柄。幸好,由于我们使用的是自动创建的窗口,Ogre使之简化了。找到setupInputSystem方法,添加如下代码:

      size_t windowHnd = 0;
      std::ostringstream windowHndStr;
      OIS::ParamList pl;
      RenderWindow *win = mRoot->getAutoCreatedWindow();
      win->getCustomAttribute("WINDOW", &windowHnd);
      windowHndStr << windowHnd;
      pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
      mInputManager = OIS::InputManager::createInputSystem(pl, false);

这样InputManager就建好了,但为了从键盘、鼠标、或是手柄中获得输入,你还必须创建这些对象:

      try
      {
          mKeyboard = static_cast(mInputManager->createInputObject(OIS::OISKeyboard, false));
          //mMouse = static_cast(mInputManager->createInputObject(OIS::OISMouse, false));
          //mJoy = static_cast(mInputManager->createInputObject(OIS::OISJoyStick, false));
      }
      catch (const OIS::Exception &e)
      {
          throw Exception(42, e.eText, "Application::setupInputSystem");
      }

我把Mouse和Joystick对象注释掉了,因为这里我们不使用,但它们就是这样创建的。InputManager::createInputObject的第二个参数是指是否使用带缓冲输入(在以前的教程里介绍过)。把第二个参数设置成false,创建了一个无缓冲的输入对象,我们这里就使用这个。

建立帧监听器

不论你是否使用带缓冲的输入,每一帧之间你必须调用键盘、鼠标或者手柄对象的捕获方法。在本课的起始代码里已经做了这些。对于无缓冲的输入,你只需要做这些。每一帧之间,你都可以调用鼠标键盘的各种方法来查询这些对象的状态。而对于带缓冲的输入,我们还需稍微地做一些事情。

为了使用带缓冲的输入,你要添加一个类,以处理输入。两件事情要做,一是实现适合的监听接口,二是把你创建的这个类注册成为这个事件的回调。在前面的课程里,你能找到带缓冲国输入的例子,下面是这个类的代码:

   // 这别它添加到工程
   class BufferedInputHandler : public OIS::KeyListener, public OIS::MouseListener, public OIS::JoyStickListener
   {
   public:
       BufferedInputHandler(OIS::Keyboard *keyboard = 0, OIS::Mouse *mouse = 0, OIS::JoyStick *joystick = 0)
   {
       if (keyboard)
           keyboard->setEventCallback(this);
       if (mouse)
           mouse->setEventCallback(this);
       if (joystick)
           joystick->setEventCallback(this);
   }
   // KeyListener
   virtual bool keyPressed(const OIS::KeyEvent &arg) { return true; }
   virtual bool keyReleased(const OIS::KeyEvent &arg) { return true; }
   // MouseListener
   virtual bool mouseMoved(const OIS::MouseEvent &arg) { return true; }
   virtual bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; }
   virtual bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; }
   // JoystickListener
   virtual bool buttonPressed(const OIS::JoyStickEvent &arg, int button) { return true; }
   virtual bool buttonReleased(const OIS::JoyStickEvent &arg, int button) { return true; }
   virtual bool axisMoved(const OIS::JoyStickEvent &arg, int axis) { return true; }

};

我建议您仅实现自己之后实际需要的监听函数.

带缓冲输入的疑难杂症

如果你没有如自己所希望那样在程序中获得输入缓冲,那么你需要检查以下这些事情:

  1. 你在调用InputManager::createInputObject()函数创建输入设备时是否设置第二个参数(开启输入缓冲)为true?
  2. 你是否为每个输入缓冲对象设置了一个setEventCallback()函数?
  3. 是否有别的类调用了setEventCallback()函数?(注意:OIS仅仅允许一个事件回调给一个对象,你不能把一个事件回调给两个类处理.)

如果你检查了这三个问题依然有错误的话,请将您的问题发表在Ogre帮助论坛.

CEGUI

CEGUI是直接整合到Ogre里的一个非常灵活的GUI库。虽然在这一课里,我们不使用CEGUI的任何功能,但我还是来简单介绍一下它的设置。CEGUI需要RenderWindow和SceneManager以供渲染。

      SceneManager *mgr = mRoot->getSceneManager("Default SceneManager");
      RenderWindow *win = mRoot->getAutoCreatedWindow();
      // CEGUI setup
      mRenderer = new CEGUI::OgreCEGUIRenderer(win, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mgr);
      mSystem = new CEGUI::System(mRenderer);

就这样,你就能使用CEGUI了。如果你程序中途你改变了SceneManager,你必须通知CEGUI应该渲染到一个新的SceneManager。为止,使用OgreCEGUIRenderer::setTargetSceneManager就行了。

渲染循环以及最后的工作

帧监听

在我们开始渲染循环,并让程序运行之前,我们还需要添加帧监听器。请注意我已经创建一个非常简单的帧监听器,名为ExitListener,它等待ESC键被按下,以退出程序。在你的程序里,我可能需要更多的帧监听器,来做更复杂的事情。看一下这个ExitListener,确保了解它的流程。然后把如下代码添加到createFrameListener方法里:

      mListener = new ExitListener(mKeyboard);
      mRoot->addFrameListener(mListener);

渲染循环

最后我们要做的是启动Ogre的渲染循环。非常简单,找到startRenderLoop方法,并添加如下代码:

      mRoot->startRendering();

这样程序就开始渲染,直到FrameListener返回false。你也可以提取单个帧,并在每帧之间做一些事情。Root::renderOneFrame渲染一帧,如何任何一个FrameListener返回false,它也返回false:

      // 别把这段加到工程里
      while (mRoot->renderOneFrame())
      {
          // Do some things here, like sleep for x milliseconds or perform other actions.
      }

然而,在我看来,你应该把所有while循环里的代码转移到FrameListener。我能想到的这种模式的唯一用处就是,中途睡眠某些毫秒,从而人为地降低帧率到某一个值。一般在FrameListener里不会这样做,因为它会与FrameEvent::timeSinceLastFrame变量搞混淆。

清理

最后一件事是,当程序终止时,我们要对所创建的所有对象进行清理。为止,我们将按照与创建时相反的顺序来删除或销毁这些对象。我们从OIS开始下手,它有一个专门的方法来销毁它的对象。找到~Application方法,并添加如下代码:

      mInputManager->destroyInputObject(mKeyboard);
      OIS::InputManager::destroyInputSystem(mInputManager);

现在我们来清理CEGUI,只要删除对象即可:

      delete mRenderer;
      delete mSystem;

最后,我们要删除Root和FrameListener对象。当删除Root对象时,我们创建的其它对象(SceneManager, the RenderWindow等)也会一并删除。

      delete mListener;
      delete mRoot;

好了! 你现在可以编译并运行你的程序了,虽然你将只能看见一个黑屏,因为我们并没有往场景添加任何东西。如果在编译里遇到链接问题,确保CEGUIBase_d.lib和OgreGUIRenderer_d.lib添加到了链接器的输入里(这是debug模式的,如果是release模式,去掉_d)。现在你应该对Ogre的启动过程比较熟悉了,可以抛开示例框架而使用其它的了。然而,为了简单化,后面的课程将仍然使用那个示例框架。

如果你对示例框架中的其它部分感兴趣,请参考更深入的文章。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值