如果你还不知道这个是如何工作的,你必须在Ogre指南章节中读更多关于这个的知识:完全理解它确实很重要。简言之,它需要创建一个继承自ExampleApplication的类(class)。这个类能够执行函数createScene()及createFrameListener()。它大致上看上去如下:
#include "ExampleApplication.h"class MyClass: public ExampleApplication,{public:MyClass(void);~MyClass(void);protected:void createScene(void);void createFrameListener(void);};
你还会需要一个创建MyClass实例的.cpp文件并运行它。通常是这个样子(取自于“Guide To Setting Up Application Project Files”-tutorial):#include "Ogre.h"#include "MyClass.h"#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32#define WIN32_LEAN_AND_MEAN#include "windows.h"INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )#elseint main(int argc, char **argv)#endif{MyClass app;try{app.go();}catch( Exception& e ){#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!",MB_OK | MB_ICONERROR | MB_TASKMODAL);#elsefprintf(stderr, "An exception has occured: %s/n", e.getFullDescription().c_str());#endif}return 0;}
如何在场景(scene)中放置一个3D模型
一个3D模型是一个实体(Entity)并且它必须附属于一个场景结点(SceneNode)以被放置在场景中。场景结点能够由SceneManager中的rootScnenNode产生。创建一个实体和场景结点并把实体挂在场景结点上是这个样子:Entity* thisEntity = mSceneMgr->createEntity("nameInTheScene", "FileName.mesh"); SceneNode* thisSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();thisSceneNode->attachObject(thisEntity);
为了移除一个实体(Entity),你必须把实体从它的场景结点中分开,删除它如果需要的话还要销毁场景结点,这必须用SceneManager来完成。如果你有一个叫做myNode的SceneNode*,你可以用下面的代码完全地删除它的内容(MovableObjects和子场景结点)及结点本身:SceneNode* parent = entity->getParentSceneNode();parent->detachObject(entity);mSceneMgr->destroyEntity(entity->getName());// entity is now destroyed, don't try to use the pointer anymore!// optionally destroy nodemSceneMgr->destroySceneNode(parent->getName());
如果你有一个连接有多个可移动对象(MovableObject),像实体、灯光及相机的场景结点,你可以用多个函数移动它,查看这些的API。下面的函数分别是移动(move)、重置(reposition)、缩放(scale)及绕它的X-,Y-,Z-轴进行旋转:thisSceneNode->translate(10, 20, 30);thisSceneNode->setPosition(1.8, 20.1, 10.5);thisSceneNode->scale(0.5, 0.8, 1.3);thisSceneNode->pitch(45);thisSceneNode->yaw(90);thisSceneNode->roll(180);
为了向场景中添加一个光源,你必须向SceneManager请求一个。然后你可以对它进行设置,下面给出一些设置的例子:Light* myLight = mSceneMgr->createLight("nameOfTheLight");myLight->setType(Light::LT_POINT);myLight->setPosition(200, 300, 400);myLight->setDiffuseColour(1, 1, 0.7);myLight->setSpecularColour(1, 1, 0.7);
你还可以把一个光源连接到一个场景结点上。下面的代码创建一个场景结点并把myLight连接到上面:SceneNode* thisSceneNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());thisSceneNode->attachObject(myLight);
环境光由SceneManager进行控制,因此你可以用SceneManager来对它进行设置:mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
ExampleApplication中的标准相机叫做mCamera并且它在继承自ExampleApplication的类中是可用的。下面的代码改变它的位置,改变它的朝向点,创建一个SceneNode并把相机连接到上面:mCamera->setPosition(0, 130, -400);mCamera->lookAt(0, 40, 0);SceneNode* camNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
camNode->attachObject(mCamera);用鼠标控制相机
这个代码使得为RTS(译者注:Realtime Strategy Game,即时战略类游戏,对应回合制战略游戏)风格的游戏使用标准相机移动接口变的容易。当按下左键时相机围绕着一个固定的点移动。拉伸(zooming)通过滚轮或点击中键及上下移动鼠标来完成。Real MoveFactor = 60.0 * evt.timeSinceLastFrame;// Move the camera around with the left buttonif (mInputDevice->getMouseButton(1)) {SceneNode *camNode = mCamera->getParentSceneNode();if (camNode == 0) {std::cerr << "mCamera isn't attached to any SceneNode !" << std::endl;}camNode->yaw(Degree(mInputDevice->getMouseRelativeX() * MoveFactor * -0.1));camNode->pitch(Degree(mInputDevice->getMouseRelativeY() * MoveFactor * -0.1));}// Zoom with the middle button...if (mInputDevice->getMouseButton(2)) {mCamera->moveRelative(Vector3(0.0, 0.0, -0.5)* mInputDevice->getMouseRelativeY() * MoveFactor);}// ... and the wheel ;-)mCamera->moveRelative(Vector3(0.0, 0.0, -0.1)* mInputDevice->getMouseRelativeZ() * MoveFactor);
布告板(Billboard)是一个总是面向相机的正方形。它被认为是一个精灵(sprite)。为产生一个布告板,你必须首先产生一个BillboardSet。然后billboard能被添加到上面一个给定的位置。BillboardSet是一个MovableObject,因此应该添加到SceneNode上。整个程序如下:SceneNode* myNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());BillboardSet* mySet = mSceneMgr->createBillboardSet("mySet");Billboard* myBillboard = mySet->createBillboard(Vector3(100, 0, 200));myNode->attachObject(mySet);
帧兼听器(FrameListener)给你在每帧开始和结束时做一些事情的机会。首先你必须创建一个继承自ExampleFrameListener的类并且在这个类中你可以执行frameStarted()和frameEnded()。看起来就像这样子:#include "ExampleFrameListener.h"class myFrameListener: public ExampleFrameListener{public:myFrameListener(RenderWindow* win, Camera* cam);~myFrameListener(void);bool frameStarted(const FrameEvent& evt);bool frameEnded(const FrameEvent& evt);};
构造函数应该调用它的父构造函数,像这样子:myFrameListener::myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam){}
你还必须向Root注册你的FrameListener,这可以在createFrameListener()中完成——继承自ExampleApplication的类的函数。你可以向root注册尽你希望多的FrameListener。这个过程如下:void createFrameListener(void){MyFrameListener listener = new MyFrameListener(mWindow, mCamera);mRoot->addFrameListener(listener);}
如果你想在FrameListener中控制场景中的一些对象,FrameListener有权访问它。一个简单方法是在构造FrameListener时提供一个指向它的指针。现在这个构造函数该是这样子:myFrameListener(RenderWindow* win, Camera* cam, Car* car);
在FrameListener的frameEnded()和frameStarted()函数中你可以用下面的方法得到以秒为单位的时间(这是一个浮点数):bool frameStarted(const FrameEvent& evt){float time = evt.timeSinceLastFrame;return true;}
现在你可以马上用每秒的速度乘以这个浮点数来得到自上一帧后的移动。这可以为游戏设定独立的帧率。
你可以在FrameListener的函数frameEnded()和frameStarted()中通过先让Ogre捕获然后再检查哪一个键被按下来响应按键。你只需要每帧捕获一次InputDevice。过程是这样子:mInputDevice->capture();if (mInputDevice->isKeyDown(Ogre::KC_DOWN)){//react however you like}if (mInputDevice->isKeyDown(Ogre::KC_UP)){//react however you like}
如果你用上面的方法响应按键,它们每帧都会响应。比如说你想每帧大约响应两次,你可以通过设置计时器(timer)来完成。为了能够使不同的函数调用来访问它,timer必须在它自己的类中而不是在函数中。执行这个是这样子:class myFrameListener: public ExampleFrameListener{protected:float buttonTimer;public:myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam){buttonTimer = 0;}bool frameStarted(const FrameEvent& evt){float time = evt.timeSinceLastFrame;buttonTimer -= time;mInputDevice->capture();if (mInputDevice->isKeyDown(Ogre::KC_DOWN) && buttonTimer <= 0){buttonTimer = 0.5;//react however you like}
return true;}};
你可以通过在FrameListener中frameStarted()或frameEnded()中返回false来退出你的程序。如果你想把这个关联在键盘上的escape按钮上,用下面的方法:bool frameStarted(const FrameEvent& evt){mInputDevice->capture();if (mInputDevice->isKeyDown(Ogre::KC_ESCAPE))return false;return true;}
在createScene()函数中你可以从一个文件中加载meshes,但是如果要在运行时添加一个新的对象这样做就太慢了(虽然在一个很简单的程序中看不出速度上的差别)。为了添加对象更快,你可以在createScene()函数中加载许多meshes然后在运行时从栈(stack)中把它们取出来。在对象不再被使用后,你可以把它们放回栈中为以后使用。这个的应用的一个好的例子是发射火箭:它们必须在开火的时候创建并在爆炸后被移除。
为了能够总是访问到这个栈,你可以使它是一个全局变量(global variable)或通过一个singleton访问它。后者更漂亮,但是要用更多的代码因此在这个例子中我不这样做。所以你在某处定义一个储存Entities的栈:stack rocketEntities;
在createScene()中用许多rockets填充这个栈。每一个Entity此时被设置为不可见并且必须有一个唯一的名字,这个可以用sprintf()函数来完成。这一切如下所示:for (unsigned int t = 0; t < 100; t++){char tmp[20];sprintf(tmp, "rocket_%d", t);Entity* newRocketEntity = mSceneMgr->createEntity(tmp, "Rocket.mesh");newRocketEntity->setVisible(false);rocketEntities.push(newRocketEntity);}
此刻当创建一个新的Rocket时我们可以从rocketEntities中取出一个mesh。在这个例子中我在Rocket构造函数中做这些,并在析构函数中把它压回栈中。在析构函数中为了能够从场景中取回Entity,我把它的名字存储在rocketEntityName中。而且我把rocket恰当地放在构造函数中。为了使这一切工作,SceneManager在整个程序中必须是可用的,为此通过让它是一个全局变量(在所有class的外面)来完成。我还在在构造函数和析构函数中执行创建和销毁SceneNode。至此这个Rocket-class看起来如此:class Rocket{protected:SceneNode* rocketNode;string rocketEntityName;public:Rocket(const Vector3& position, const Quaternion& direction){rocketNode = static_cast<SceneNode*>(sceneMgr->getRootSceneNode()->createChild());Entity* rocketEntity = rocketEntities.top();rocketEntities.pop();rocketEntity->setVisible(true);rocketEntityName = rocketEntity->getName();rocketNode->attachObject(rocketEntity);rocketNode->setOrientation(direction);rocketNode->setPosition(position);}~Rocket(){Entity* rocketEntity = static_cast<Entity*>(rocketNode->detachObject(rocketEntityName));rocketEntity->setVisible(false);rocketEntities.push(rocketEntity);sceneMgr->getRootSceneNode()->removeAndDestroyChild(rocketNode->getName());}};
如果你使用这个结构,你应该意识到这个事情:如果没有rockets剩余会发生错误。你可以通过检查这个Entities栈是否为空,如果为空就加载新的meshes到栈中来解决这个问题。
为了显示和隐藏一个Overlay,你首先必须用OverlayManager(这是一个singleton)得到一个指向它的指针。如果你定义了一个名字是“myOverlay”的.overlay脚本,你可以用下面的代码获取、显示并隐藏它:Overlay* thisOverlay = static_cast<Overlay*>(OverlayManager::getSingleton().getByName("myOverlay"));thisOverlay->show();thisOverlay->hide();
你可以在运行时间改变GUI中Containers和Elements中所有的参数(parameter)。为此,首先你必须得到一个你想要的Element或Container的指针,如果是基于你想做的东西的需要就把它设计成那种类型。得到一个指向在.overlay脚本中定义为“myTextArea”的TextArea的指针并改变其caption类似这样:OverlayElement* thisTextArea = OverlayManager::getSingleton().getOverlayElement("myTextArea");thisTextArea->setCaption("blaat");
这个例子中不需要casting,因为每个OverlayElement都有caption。如果你想设置一个OverlayElement类型的特殊设置,你必须把它设计为那种类型。改变一个textArea的font-name类似这样:TextAreaGuiElement* thisTextArea = static_cast<TextAreaOverlayElement*>(OverlayManager::getSingleton().getOverlayElement("myTextArea"));thisTextArea->setFontName("RealCoolFont");
这一条已经过时了。新的CEGUI方法的详述在这里:How To Show The Mouse Cursor, 以及这里: Intermediate Tutorial 3如果你想在屏幕上显示鼠标指针,你须做两件事:设置它为显示并告诉FrameListener追踪它。令它显示可以如下做:
GuiContainer* cursor = OverlayManager::getSingleton().getCursorGui();cursor->setMaterialName("Cursor/default");cursor->setDimensions(32.0/640.0, 32.0/480.0);cursor->show();
让FrameListener追踪它应在它的父构造函数中通过设置它的最后的boolean参数为true来做。至此这个构造函数看起来是这个样子:myFrameListener::myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam, false, true){}
注意这样做后,因为mInputDevice的capture()函数和isKeyPressed()函数不再正确地工作所以这个特殊的FrameListener对按下按钮操作不再反应。现在需要一个不同的FrameListener来执行键盘输入。
这一条已经过时并由cegui方案替代。你可以在Overlay脚本中用类似下面的句法定义一个button,这对设置所有的这些material很重要:container Button(myButton){metrics_mode relativehorz_align leftvert_align toptop 0.1left 0.1width 0.18height 0.1material NCG/GuiSession/RedMaterialbutton_down_material NCG/GuiSession/RedMaterialbutton_up_material NCG/GuiSession/RedMaterialbutton_hilite_down_material NCG/GuiSession/RedMaterialbutton_hilite_up_material NCG/GuiSession/RedMaterialbutton_disabled_material NCG/GuiSession/RedMaterial}
Buttons在Ogre中不是标准的,因此你须确保它们能在文件夹ogrenew/PlugIns/GuiElements/Include中被编译器(compiler)找到。现在你可以包含它:#include "OgreButtonGuiElement.h"
为使这个button工作,一个ActionListener必须被注册到它上面,这可以如下完成:ActionTarget* thisButton = static_cast<ButtonGuiElement*>(GuiManager::getSingleton().getGuiElement("myButton"));thisButton->addActionListener(this);
此例中假设‘this’是一个ActionListener(默认不是这样子的)。当然任何ActionListener都可以,不必是‘this’。你可以通过继承ActionListener来使一个类是一个ActionListener并执行actionPerformed()。这个过程类似这样:class Listener: public ActionListener{public:void actionPerformed(ActionEvent *e);};
这一条已经过时并由cegui方案来替代。如果你注册一个ActionListener给几个button,它们会调用同一个函数actionPerformed()。你可以通过比较它的名字与ActionEvent e的名字来找出是哪个button被按下。如下:#includevoid actionPerformed(ActionEvent *e){std::string action = e->getActionCommand();if (action == "myButton"){//handle the button-press}}
这个条目已经过时,能用cegui方案代替。退出应用程序可以在FrameListener中的frameStarted()函数和frameEnded()函数完成,而不是在actionPerformed()函数中。那么我们要怎么做呢?为此我们可以介绍一个简单的FrameListener,如果要求退出它只作退出而不做其他任务。这个FrameListener看起来如下(来自于Ogre中提供的Gui-demo):#include "ExampleFrameListener.h"class QuitListener: public ExampleFrameListener{public:QuitListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam, false, false){quit = false;}bool frameStarted(const FrameEvent& evt){if (quit)return false;return true;}void scheduleQuit(void){quit = true;}protected:bool quit;};
至此如果你有一个指向QuitListener的指针,你可以通过调用scheduleQuit()来确定退出时间并且QuitListener会在下一帧开始前执行它。
场景是通过一个指示是哪种场景类型的标志符来自动选择的。这个过程是在ExampleApplication中完成的,因此要改变它,你必须改变ExampleApplication。通常,ExampleApplication包含下面的代码:virtual void chooseSceneManager(void){// Get the SceneManager, in this case a generic onemSceneMgr = mRoot->getSceneManager(ST_GENERIC);}
你可以更改ST_GENERIC为下面的任意标志符来获得一个适应你的特殊场景的场景管理器(SceneManager):
ST_GENERIC
ST_EXTERIOR_CLOSE
ST_EXTERIOR_FAR
ST_EXTERIOR_REAL_FAR
ST_INTERIOR
Ogre可以为你提供一个可能碰撞的所有对象的列表。不在碰撞中的封闭的对象也在这个列表中,所以然后你须自己确定哪一个是碰撞对象。你可以用IntersectionSceneQuery来寻找这个碰撞列表,可以用下面的代码来得到:IntersectionSceneQuery* intersectionQuery = sceneMgr->createIntersectionQuery();
现在你可以找到一个所有可能碰撞的列表:IntersectionSceneQueryResult& queryResult = intersectionQuery->execute();
如果你想在更形场景前得到几次这个列表,你没必要让Ogre一次次地计算。你可以用下面的代码不用新的计算而再次得到相同的列表:IntersectionSceneQueryResult& queryResult = intersectionQuery->getLastResults();
使用后,你可以存储IntersectionSceneQuery为以后使用或移除它。如果移除它,你须用下面的代码告诉SceneManager来完成:mSceneMgr->destroyQuery(intersectionQuery);
IntersectionSceneQueryResult是一个可移动对象对(pairs of MovableObjects)的列表。它的应用的一个例子是获得发生第一次碰撞的两个对象的名字:queryResult.movables2movables.begin()->first->getName();
有关结果类型的详细资料参看Ogre-API。
IntersectionSceneQuery传递过来的碰撞对象列表是可移动对象(MovableObject)的,但是一般来说你可能想把这些关联到你自己的场景中的自定义对象。为此,你可以把指针连接到你自己的object中指向一个MovableObject,稍后取回指针。如果你有一个Entity(是一个MovableObject)和一个自定义object(就像下面的myRocket),你可以把你的自定义object挂到Entity上,如下:Entity* myEntity = sceneMgr->createEntity("myEntity", "Rocket.mesh");Rocket* myRocket = new Rocket();myEntity->setUserObject(myRocket);
现在你可以用下面的代码从Entity中得到一个指向myRocket的指针:myEntity->getUserobject();
为了使你自定义的类工作,它必须继承自UseDefinedObject。这还允许你找出返回的object是哪种class,这样你可以cast it back。现在Rocket的定义是这样子:class Rocket: public UserDefinedObject{public:const string& getTypeName(void) const{static const string typeName = "Rocket";return typeName;}};
在检测出哪个MovableObjects与碰撞相关后,你也可以找出你自己的哪些objects与碰撞相关。
你可以使用标记(flag)来从碰撞检测(collision detection)中排除某些物体。你可以给任何可移动物体添加标记,比如下面例子中的100:Entity* ground = mSceneMgr->createEntity("ground", "Ground.mesh");ground->setQueryFlags(100);
现在你可以告诉你的IntersectionSceneQuery来排除物体用这个标记从它的intersections列表中通过设置它的mask。如下所示:IntersectionSceneQuery* intersectionQuery = sceneMgr->createIntersectionQuery();intersectionQuery->setQueryMask(~100);