Ogre基础教程5:有缓冲输入
2015-06-18 09:05
281 查看
教程介绍
这篇教程将介绍利用OIS的有缓冲输入。不同于在每一帧都要求输入信息,我们将拥有:被定义为每次输入事件发生时被调用的回调方法。预备知识
略目录
预备知识设置场景
对有缓冲输入的介绍
键盘监听器以及交互
鼠标监听器交互
为摄像机定位设置场景节点
注册监听器
捕获输入状态
处理输入事件
改变视点
摄像机运动
用有缓冲输入切换光照
使用鼠标进行摄像机运动
其它输入系统
总结
设置场景
在Ogre的Samples目录中,包含了一个’tudorhouse.mesh’;但其纹理并不包含在目录中。这里是对应的纹理:fw12b.jpg:http://www.ogre3d.org/tikiwiki/tiki-download_wiki_attachment.php?attId=219&download=y
在添加了mesh和纹理到你的项目中后,你需要确保这个材质条目在你的某个材质脚本中:
material Examples/TudorHouse { technique { pass { texture_unit { texture fw12b.jpg tex_address_mode clamp } } } }
最后,你将需要为这篇教程设置一个基本的场景。编译并运行这里的代码。你会看到一座都铎式房屋现于眼前。
TutorialApplication.h
#ifndef TUTORIALAPPLICATION_H #define TUTORIALAPPLICATION_H #include "BaseApplication.h" class TutorialApplication : public BaseApplication { public: TutorialApplication(); virtual ~TutorialApplication(); private: virtual void createScene(); virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe); Ogre::Real mRotate; Ogre::Real mMove; Ogre::SceneNode* mCamNode; Ogre::Vector3 mDirection; }; #endif
TutorialApplication.cpp
#include "TutorialApplication.h" TutorialApplication::TutorialApplication() : mRotate(.13), mMove(250), mCamNode(0), mDirection(Ogre::Vector3::ZERO) { } TutorialApplication::~TutorialApplication() { } void TutorialApplication::createScene() { mSceneMgr->setAmbientLight(Ogre::ColourValue(.2, .2, .2)); Ogre::Entity* tudorEntity = mSceneMgr->createEntity("tudorhouse.mesh"); Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "Node"); node->attachObject(tudorEntity); Ogre::Light* light = mSceneMgr->createLight("Light1"); light->setType(Ogre::Light::LT_POINT); light->setPosition(Ogre::Vector3(250, 150, 250)); light->setDiffuseColour(Ogre::ColourValue::White); light->setSpecularColour(Ogre::ColourValue::White); mCamera->setPosition(0, -370, 1000); } bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe) { bool ret = BaseApplication::frameRenderingQueued(fe); return ret; } // MAIN FUNCTION OMITTED FOR SPACE
对有缓冲输入的介绍
在之前的教程中,我们使用了无缓冲输入。例如,我们在每一帧检查OIS::Keyboard实例,来查看是否有任何按键正在被按下。本篇教程将使用有缓冲输入。这个方法包括注册一个监听器类来直接报告输入事件。被称为有缓冲输入的原因是事件被注入到一个缓冲中,并且随后通过回调方法被发送。不必为这看起来太过抽象而担心,我们很快将给出具体的例子帮助阐明。有了有缓冲输入,我们不再一定要设计一个系统来持续追踪,前一帧是否有一个按钮被按下;替代的途径是,存在两个分开的输入事件被产生;一个键按下事件和一个键释放事件。并且,每个事件携带了如哪个键被按下或释放这样的信息。
OIS对每个键盘、鼠标或者手柄对象,都仅允许存在一个监听器;这是出于性能的考虑。如果你尝试注册第二个监听器,它将会取代第一个。如果你需要多个被通知输入事件的对象,那么你应当实现你自己的消息发送系统。
键盘监听器及交互
OIS为键盘提供的监听器类被称为KeyListener。它提供了两个纯虚回调方法:keyPressed和keyReleased。这两个方法都以一个键盘事件为参数。这个对象包含了哪个键被使用的信息。You should add overrides of the KeyListener methods to your header and cpp file. Add the declaration to the private section.
你应当在你的头文件和源代码文件中,添加对键盘监听器方法的重写。在private区添加这些声明。
TutorialApplication.h
virtual bool keyPressed(const OIS::KeyEvent& ke); virtual bool keyReleased(const OIS::KeyEvent& ke); TutorialApplication.cpp bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke) { return true; } bool TutorialApplication::keyReleased(const OIS::KeyEvent& ke) { return true; }
鼠标监听器交互
OIS为鼠标提供的监听器类别称为鼠标监听器。它比键盘监听器要略微复杂一点。它包含两个方法来检查鼠标按钮的状态:mousePressed和mouseReleased。它也有第三个被称为mouseMoved的方法;无论何时它监测到一个通过移动鼠标而产生的事件,mouseMoved被调用。被传递给这些函数的鼠标事件包含关于鼠标的相关运动信息(自从上一帧鼠标移动了多远的距离),以及关于在屏幕坐标中鼠标的位置信息。你将会找到这些信息都有利用价值的情形。继续:在你的头文件和源代码文件中,为鼠标监听器添加重写方法。同样在私有成员区添加声明。先不要编译你的应用。我们已经从BaseApplication中继承一些输入管理,并且你会因为没有办法进行移动或退出而阻塞。我们将迅速修复这些。
TutorialApplication.h
virtual bool mouseMoved(const OIS::MouseEvent& me); virtual bool mousePressed(const OIS::MouseEvent& me, OIS::MouseButtonID id); virtual bool mouseReleased(const OIS::MouseEvent& me, OIS::MouseButtonID id); TutorialApplication.cpp bool TutorialApplication::mouseMoved(const OIS::MouseEvent& me) { return true; } bool TutorialApplication::mousePressed( const OIS::MouseEvent& me, OIS::MouseButtonID id) { return true; } bool TutorialApplication::mouseReleased( const OIS::MouseEvent& me, OIS::MouseButtonID id) { return true; }
为摄像机定位设置场景节点
现在我们要创建两个能让我们将摄像机依附的场景节点。这将允许我们使用有缓冲输入在两个视点之间来回切换。我们应做的第一件事是移除createScene中的这一行:mCamera->setPosition(0, -370, 1000);
我们通过将摄像机依附于一个场景节点上来对其定位,所以上面的代码不再被需要。现在我们将要构建场景节点。在createScene中创建光照之后,添加以下内容:
node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "CamNode1", Ogre::Vector3(1200, -370, 0)); node->yaw(Ogre::Degree(90));
这里我们已经创建了一个新的场景节点,并且再用这个节点变量绕y轴旋转了90°。下一步是将我们的mCamNode指针分配给我们的新场景节点并且将我们的摄像机依附到它。mCameNode将作为我们当前的场景节点被使用。我们将基于键盘输入而将指针移动到另一个场景节点。
mCamNode = node; node->attachObject(mCamera);
这将意味着,我们的摄像机现在将以它刚刚依附到的场景节点位置处作为其实。现在我们希望创建另一个我们能将摄像机移动过去的场景节点。
node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "CamNode2", Ogre:: d0ad Vector3(-500, -370, 1000)); node->yaw(Ogre::Degree(-30));
现在我们有两个可以将摄像机依附的“锚点”。记住,摄像机不会仅仅被放置在场景节点的位置,它也将继承应用到节点上的任何变换,比如旋转。
注册监听器
如大多数监听器类一样,为了正确地工作,我们需要用KeyListener和MouseListener对应的对象注册。这些在BaseApplication中已经为我们完成了。如果查看BaseApplication::createFrameListener,你会看到以下两个调用:mMouse->setEventCallback(this); mKeyboard->setEventCallback(this);
它们将我们的类(从KeyListener和MouseListener继承)注册为回到方法的源。因为我们没有重写BaseA::createFrameListener,所以在BaseApplication中这些注册行为仍然发生。
捕获输入状态
OIS并不自动地在每一帧捕获键盘和鼠标的输入。我们需要明确地调用捕获方法来做这件事。这是教程框架已经完成的另一件事。如果你查看BaseApplication::frameRenderingQueued,你将看到如下两行:mKeyboard->capture(); mMouse->capture();
这两个调用将“缓冲”用输入信息填满。这是方法被称为有缓冲输入的原因。你也应当注意到以下这行:确定mShutDown为真,会导致应用退出。
if (mShutdown) return false;
如果BaseApplication没有为我们考虑,这些是我们需要放到自己的类中的所有东西。Intermediate教程不再使用BaseApplication框架来帮助移动你脱离对“场景背后”的代码的依赖。目前,只需记住我们的大部分函数仍然来自BaseApplication。我们在此指出这一点,希望让一切看起来开始显得不那么神秘。同样,记住如果我们重写了来自BaseApplication的某个方法,那么我们必须记住调用父方法,这样我们继续得到来自BaseApplication的功能(像我们正在对frameRenderingQueued所做的)。
处理输入事件
现在我们已准备好,开发我们实际的输入代码。要做的第一件事,是允许用户通过按Esc键来退出应用。添加以下到TutorialApplication::keyPressed:
switch (ke.key) { case OIS::KC_ESCAPE: mShutDown = true; break; default: break; }
当keyPressed方法被监听器所调用,我们已经储存在ke中的键盘事件引用将会携带一个名为key的成员。我们可以用它的值来决定,哪个按键被按下。我们使用一个switch语法,它测试关联到Esc键的按键代码 。OIS命名空间定义了一系列按键代码常量,所有都“KC_”为前缀。当我们发现Esc键被按下,那么我们仅需设置终止标志为真。BaseApplication::frameRenderingQueued会随后在下一帧返回false,应用会如我们所愿地退出。
如果你此时编译应用,你没办法移动摄像机,但你应该可以通过按下Esc来退出。
改变视点
下面我们将要允许用户通过按1键或2键,在场景节点之间跳转。添加以下内容到我们刚刚在keyPressed中建立的switch语法中:case OIS::KC_1: mCamera->getParentSceneNode()->detachObject(mCamera); mCamNode = mSceneMgr->getSceneNode("CamNode1"); mCamNode->attachObject(mCamera); break; case OIS::KC_2: mCamera->getParentSceneNode()->detachObject(mCamera); mCamNode = mSceneMgr->getSceneNode("CamNode2"); mCamNode->attachObject(mCamera); break;
这些将分离摄像机,并重新将其依附,无论按键何时被按下。编译你的应用。你将可以变更视点。你仅有能做的其它事情是按Esc退出。
摄像机运动
现在我们在应用中添加摄像机运动。我们通过使用mDirection作为一个速率向量来完成。每当用户按下W键,我们会设置速率向量的z元素等于-mMove。这是因为摄像机默认朝向是面向z轴负方向。我们对所有用户可以运动的不同方向,应用相同的逻辑。添加以下到switch语法中:case OIS::KC_UP: case OIS::KC_W: mDirection.z = -mMove; break; case OIS::KC_DOWN: case OIS::KC_S: mDirection.z = mMove; break; case OIS::KC_LEFT: case OIS::KC_A: mDirection.x = -mMove; break; case OIS::KC_RIGHT: case OIS::KC_D: mDirection.x = mMove; break; case OIS::KC_PGDOWN: case OIS::KC_E: mDirection.y = -mMove; break; case OIS::KC_PGUP: case OIS::KC_Q: mDirection.y = mMove; break;
你可能希望花点时间说服自己,这些向量在正确的方向对准了。下一件事我们需要做的是:如果按键被释放,返回一个为0的分量。添加以下代码:
switch (ke.key) { case OIS::KC_UP: case OIS::KC_W: mDirection.z = 0; break; case OIS::KC_DOWN: case OIS::KC_S: mDirection.z = 0; break; case OIS::KC_LEFT: case OIS::KC_A: mDirection.x = 0; break; case OIS::KC_RIGHT: case OIS::KC_D: mDirection.x = 0; break; case OIS::KC_PGDOWN: case OIS::KC_E: mDirection.y = 0; break; case OIS::KC_PGUP: case OIS::KC_Q: mDirection.y = 0; break; default: break; } return true;
这个switch看起来像我们在keyPressed中使用的。所有这些代码仅构建了我们需要的速率向量。现在我们需要基于这些向量,调动摄像机的当前场景节点,从而让摄像机事实上运动。frameRenderingQueued中调用BaseApplication方法后,随即添加以下代码:
mCamNode->translate(mDirection * fe.timeSinceLastFrame, Ogre::Node::TS_LOCAL);
你大概注意到,我们利用了来自前面教程的局部变换空间。这个坐标帧保持依附于mCamNode,因此,在这个变换空间中,负z轴将一直指向前方。通过基于从上一帧到当前帧已经经过的时间来缩放它,这是平滑摄像机移动的关键。如果这里的任何内容不清楚,请复习基础教程4。
编译并运行你的应用。我们现在又可以使用键盘移动摄像机啦,但我们使用的是有缓冲输入系统。尝试移动摄像机,然后跳转到另一个场景节点,再跳转回来。注意,你将回到被你移动的场景节点的位置。它不会被“重置”回去,是因为当我们实际调动摄像机时,是通过使用摄像机依附的场景节点。场景节点不是一个静止占位对象。
用有缓冲输入切换灯光
现在我们添加一些鼠标功能。像上一教程那样做的那样开关场景光照,我们从这里开始。这些处理将与我们已对键盘进行的工作非常类似。看起来可能显得奇怪,但鼠标事件并不包含哪个按键被按下的信息,而是我们有第二个参数,保存了一个MouseButtonID。这将在switch中,被用来决定哪个按钮被按下。添加以下内容到mousePressed:switch (id) { case OIS::MB_Left: Ogre::Light* light = mSceneMgr->getLight("Light1"); light->setVisible(!light->isVisible()); break; default: break; }
我们做的第一件事,是得到我们为场景创建的光照的指针。然后,当鼠标左键被按下是,切换灯光为可见。编译并运行你的应用。
使用鼠标进行摄像机运动
最后要做的,是添加用鼠标旋转摄像机的功能。我们将把鼠标右键绑定到这个“鼠标观看模式”上。每次鼠标被移动,我们将检查右键是否被按下。如果是,我们将基于鼠标的相对运动,旋转摄像机。相对运动的数值被放在鼠标事件中称为state的成员里;这个成员包含了成员X和Y;这些成员包含了一个名为rel的值,包含相对运动信息;一个名为abs的值,包含光标的绝对位置。添加以下内容到mouseMoved:
if (me.state.buttonDown(OIS::MB_Right)) { mCamNode->yaw(Ogre::Degree(-mRotate * me.state.X.rel), Ogre::Node::TS_WORLD); mCamNode->pitch(Ogre::Degree(-mRotate * me.state.Y.rel), Ogre::Node::TS_LOCAL); }
编译并运行。现在当按下鼠标右键时,你可以自由地查看周围。对yaw和pitch的调用稍显复杂。你应当查看它们,确保自己明白它们是如何工作的。特别是,为什么对这两个调用,mRotate都是负的;以及,为什么我们对yaw使用全局变换,而对pitch使用局部变换?
其它的输入系统
还有其它能够与Ogre良好工作的可用输入系统。一个更流行的选择是SDL。SDL提供了跨平台的窗口/输入系统以及游戏控制器输入。为了开始与SDL的控制杆系统一起工作,我们应当使用SDL_Init和SDL_Quit的调用,围绕我们应用的go方法。SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE); SDL_JoystickEventState(SDL_ENABLE); app.go(); SDL_Quit();
为了设置一个操纵杆,我们将用操纵杆数量作为参数(0,1,2,…),调用SDL_JoystickOpen。
SDL_Joystick* mJoystick; mJoystick = SDL_JoystickOpen(0); if (mJoystick == NULL) // No joystick found
如果SDL_JoystickOpen返回NULL,那么在加载操纵杆时存在错误。这通常意味着,所请求的操纵杆不存在。你可以检查SDL_NumJoysticks来查看有多少操纵杆被识别了。当你正在处理操纵杆,你也需要将其关闭。
SDL_JoystickClose(mJoystick);
为了使用操纵杆,我们调用SDL_JoystickGetButton以及SDL_JoystickGetAxis。这里是使用了这些SDL方法的运动代码的片段。
SDL_JoystickUpdate(); mTrans.z += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 1) / 32767; mTrans.x += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 0) / 32767; xRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 3) / 32767; yRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 2) / 32767;
mTrans变量与SceneNode::translate方法,被用来在稍后的代码中移动摄像机。xRot和yRot被相似地使用。SDL_JoystickGetAxis返回在-32767和32767之间的一个值。在这个片段中,这个值被缩小至-1与1之间,来匹配Ogre的坐标系统。
总结
略相关文章推荐
- Extjs4.0 最新最全视频教程
- OpenERP 的XML-RPC的实例+many2many,one2many,many2one...
- CSS3属性教程与案例分享
- jquery教程靠边站,一分钱不花让你免费学会jquery
- autoit入门教程小结第1/5页
- 用Photoshop 制作草地效果简明教程
- 比较完整简洁的Flash处理XML文档数据教程 上篇第1/3页
- VBS基础编程教程 (第1篇)
- SQLite教程(十一):临时文件
- VBS基础编程教程 (第3篇)
- VBS教程:运算符-运算符(+)
- PostgreSQL教程(十):性能提升技巧
- PostgreSQL教程(二):模式Schema详解
- PostgreSQL教程(十三):数据库管理详解
- PostgreSQL教程(八):索引详解
- PostgreSQL教程(三):表的继承和分区表详解
- XML简易教程之三
- ruby 数组使用教程
- PostgreSQL教程(十九):SQL语言函数
- PostgreSQL教程(四):数据类型详解