#include <Ogre.h> #include <OIS/OIS.h> #include <CEGUI/CEGUI.h> #include <OgreCEGUIRenderer.h> 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() { /************************************************************************ 1、创建Root对象。 2、定义Ogre将要使用的资源。 3、选择并设置渲染系统(即DirectX, OpenGL等)。 4、创建渲染窗口(Ogre所处的窗口)。 5、初始化你要使用的资源。 6、用这些资源来建立一个场景。 7、设置第三方库或插件。 8、创建一些帧监听器。 9、启动渲染循环 ************************************************************************/ createRoot(); defineResources(); setupRenderSystem(); createRenderWindow(); initializeResourceGroups(); setupScene(); setupInputSystem(); setupCEGUI(); createFrameListener(); startRenderLoop(); } ~Application() { /************************************************************************ 最后一件事是,当程序终止时,我们要对所创建的所有对象进行清理。为止,我们 将按照与创建时相反的顺序来删除或销毁这些对象。我们从OIS开始下手,它有一个 专门的方法来销毁它的对象。添加如下代码: ************************************************************************/ 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的启动过程比较熟悉了,可以抛开示例框架而使 用其它的了。 ************************************************************************/ } private: Root *mRoot; OIS::Keyboard *mKeyboard; OIS::InputManager *mInputManager; CEGUI::OgreCEGUIRenderer *mRenderer; CEGUI::System *mSystem; ExitListener *mListener; void createRoot() { /************************************************************************ 1、创建Root对象 Root的构造函数需要三个参数。第一个是插件配置文件的名称和路径。 第二个是Ogre配置文件的路径(它告诉Ogre关于显卡、显示设置等信息)。 最后一个是日志文件的名称和路径。因为我们不需要修改任何一个属性,所以用默认的。 ************************************************************************/ mRoot = new Root(); } void defineResources() { /************************************************************************ 2、资源 注意: 你最好在SDK的bin/release目录里找到resources.cfg,看看它的内容是很有帮助的。 下面我们要定义程序将要使用的资源,包括纹理、模型、脚本等等。请记住,你必须预先定 义好你的资源,在Ogre能使用它们之前,你还必须对它进行初始化。在这一步里,我们来定义 所有程序可能使用的资源。为止,我们把每一个资源所在的文件夹添加到ResourceGroupManager。 ************************************************************************/ 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); } } } void setupRenderSystem() { /************************************************************************ 3、创建渲染系统 接下来,我们需要选择一个渲染系统(在Windows机器上通常是DirectX或者OpenGL), 然后配置它。大多数Demo程序使用的是一个Ogre配置对话框,这是一个很好的一个东 东。Ogre提供了一种保存用户设置的方法,意味着除了第一次需要设置外,以后都不 需要了。并添加以下代码: ************************************************************************/ 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的配置对话框,你需要手动地设置渲染系统。以下是一个基本 的例子: ************************************************************************/ // Do not add this to the application RenderSystem *rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem"); // or use "OpenGL Rendering Subsystem" mRoot->setRenderSystem(rs); rs->setConfigOption("Full Screen", "No"); rs->setConfigOption("Video Mode", "1440 x 900 @ 32-bit colour"); } void createRenderWindow() { /************************************************************************ 4、创建渲染窗口 目前选择了一个渲染系统,还需要一个渲染Ogre的窗口。实际上有许多种方式来实现 ,但这里只介绍两种。 ************************************************************************/ mRoot->initialise(true, "Tutorial Render Window"); /************************************************************************ 第一个参数表示是否让Ogre为你创建一个渲染窗口。否则,你可以自己创建一个渲 染窗口,通过使用win32 API、wxWidgets或其它Windows/Linux的GUI系统。关于在 Windows下的一个简单例子是这样: ************************************************************************/ //// Do not add this to the application //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文 档。 ************************************************************************/ } void initializeResourceGroups() { /************************************************************************ 5、初始化资源 现在我们创建了Root对象、渲染系统、以及渲染窗口,继续。接下来是初始化将要使 用的资源。从mesh到脚本,所有的东西,在某一时刻,我们只用到这些资源其中的一 小部分。为了减少内存消耗,我们可以只加载正在使用的资源。为止,我们把资源分 解成各种部分,只在运行时初始化它们。在本课里,不将详细介绍。其它的地方有一 个专门介绍资源的教程。初始化资源之前,我们应该设置纹理mipmap的缺省值。添加 如下代码: ************************************************************************/ TextureManager::getSingleton().setDefaultNumMipmaps(5); ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); } void setupScene() { /************************************************************************ 6、创建场景 你应该了解在把各种东西添加到场景之前,你要做三件事:创建场景管理器 (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方法来获取它。 这三件事完了以后,你可以尽情地往你的场景里添加物体了。 ************************************************************************/ } void setupInputSystem() { /************************************************************************ 7、设置第三方库 OIS 虽然在OGRE里,OIS不是唯一的选择,但它是最好的之一。我来简单介绍一下OIS如何 在程序里启动。若真想要使用这个库,请参考这个教程,以及OIS自身的文档。 设置无缓冲输入 OIS使用一个统一的InputManager,它比较难配置,但一旦正确地创建之后,非常好使 用。实际上,它只是需要Ogre渲染窗口的句柄。幸好,由于我们使用的是自动创建的窗 口,Ogre使之简化了。添加如下代码: ************************************************************************/ 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); /************************************************************************ 这样InputManager就建好了,但为了从键盘、鼠标、或是手柄中获得输入,你还必 须创建这些对象: ************************************************************************/ try { mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, false)); //mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, false)); //mJoy = static_cast<OIS::JoyStick*>(mInputManager->createInputObject(OIS::OISJoyStick, false)); } catch (const OIS::Exception &e) { throw new Exception(42, e.eText, "Application::setupInputSystem"); } /************************************************************************ 我把Mouse和Joystick对象注释掉了,因为这里我们不使用,但它们就是这样创建的。 InputManager::createInputObject的第二个参数是指是否使用带缓冲输入(在以前的 教程里介绍过)。把第二个参数设置成false,创建了一个无缓冲的输入对象,我们这 里就使用这个。 ************************************************************************/ } void setupCEGUI() { /************************************************************************ 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就行了。 ************************************************************************/ // Other CEGUI setup here. } void createFrameListener() { /************************************************************************ 8、渲染循环以及最后的工作 帧监听 在我们开始渲染循环,并让程序运行之前,我们还需要添加帧监听器。请注意我已经 创建一个非常简单的帧监听器,名为ExitListener,它等待ESC键被按下,以退出程 序。在你的程序里,我可能需要更多的帧监听器,来做更复杂的事情。看一下这个 ExitListener,确保了解它的流程。 ************************************************************************/ mListener = new ExitListener(mKeyboard); mRoot->addFrameListener(mListener); } void startRenderLoop() { /************************************************************************ 9、渲染循环 最后我们要做的是启动Ogre的渲染循环。非常简单,添加如下代码: ************************************************************************/ mRoot->startRendering(); /************************************************************************ 这样程序就开始渲染,直到FrameListener返回false。你也可以提取单个帧,并在 每帧之间做一些事情。Root::renderOneFrame渲染一帧,如何任何一个FrameListener 返回false,它也返回false: ************************************************************************/ //// Do not add this to the application //while (mRoot->renderOneFrame()) //{ // // Do some things here, like sleep for x milliseconds or perform other actions. //} /************************************************************************ 然而,在我看来,你应该把所有while循环里的代码转移到FrameListener。我能想 到的这种模式的唯一用处就是,中途睡眠某些毫秒,从而人为地降低帧率到某一个 值。一般在FrameListener里不会这样做,因为它会与FrameEvent::timeSinceLastFrame 变量搞混淆。 ************************************************************************/ } }; #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; }