上一章我们处理监听的方案是,每一帧只处理一次。
这一次,当鼠标键盘的事件发生时,我们会立即处理它。
这里只是对缓冲输入的一个简单介绍,而不是完整的如何使用OIS的教程。
若想了解更多内容,请查阅相关的OIS使用教程。
初始的项目源码如下:
#include "ExampleApplication.h" class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener { public: TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr) : ExampleFrameListener(win, cam, true, true) { } bool frameStarted(const FrameEvent &evt) { if(mMouse) mMouse->capture(); if(mKeyboard) mKeyboard->capture(); return mContinue; } // MouseListener bool mouseMoved(const OIS::MouseEvent &e) { return true; } bool mousePressed(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; } bool mouseReleased(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; } // KeyListener bool keyPressed(const OIS::KeyEvent &e) { return true; } bool keyReleased(const OIS::KeyEvent &e) { return true; } protected: Real mRotate; // The rotate constant Real mMove; // The movement constant SceneManager *mSceneMgr; // The current SceneManager SceneNode *mCamNode; // The SceneNode the camera is currently attached to bool mContinue; // Whether to continue rendering or not Vector3 mDirection; // Value to move in the correct direction }; class TutorialApplication : public ExampleApplication { public: void createCamera(void) { // create camera, but leave at default position mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setNearClipDistance(5); } void createScene(void) { mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25)); // add the ninja Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh"); SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode"); node->attachObject(ent); // create the light Light *light = mSceneMgr->createLight("Light1"); light->setType(Light::LT_POINT); light->setPosition(Vector3(250, 150, 250)); light->setDiffuseColour(ColourValue::White); light->setSpecularColour(ColourValue::White); // Create the scene node node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400)); // Make it look towards the ninja node->yaw(Degree(-45)); // Create the pitch node node = node->createChildSceneNode("PitchNode1"); node->attachObject(mCamera); // create the second camera node/pitch node node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400)); node = node->createChildSceneNode("PitchNode2"); } void createFrameListener(void) { // Create the FrameListener mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr); mRoot->addFrameListener(mFrameListener); // Show the frame stats overlay mFrameListener->showDebugOverlay(true); } }; #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 { // Create application object TutorialApplication app; try { app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s ", e.getFullDescription().c_str()); #endif } return 0; }
上一次我们使用的是无缓冲的输入,也就是说,在每一帧里我们查询OIS::Keyboard和OIS::Mouse实例的状态,以判断它们是否被按下。
而缓冲输入使用了一个listener接口,以便在事件发生时通知你的程序。
比如,当一个键被按下时,会触发一个 KeyListener::keyPressed 事件,
而当这个键被释放(不再按下)时,KeyListener::keyReleased 事件被触发给所有已注册的KeyListener类。
这些能用在追踪按键的时间,或判断按键在上一帧中是否没有被按下。
关于OIS的监听系统有一点要注意的是,对于每一个Keyboard,Mouse,Joystick对象只能有一个监听器,这样是为了简单(也为了速度)。
多次调用setEventCallback函数的结果是只有最后一次注册的监听器才得到事件消息。
如果你有多个对象需要获得Key,Mouse事件,你只有自己写一个消息分发。
还有,千万记得在frameStarted方法里调用Keyboard::capture和Mouse::capture。
OIS不会使用线程来确定键盘鼠标的状态,所以你必须指明什么时候去获取输入。
OIS的KeyListener接口提供了两个纯虚函数。
第一个是keyPressed函数,每次按下某个键时调用它,
还一个是keyReleased,每次离开某个键时调用它,
传入这些函数的参数是一个KeyEvent,它包含被按下/释放的按键的键码。
鼠标监听界面MouseListener接口比KeyListener接口要稍微复杂一些。
它包含查看何时鼠标键被按下/释放的函数: MouseListener::mousePressed 和 MouseListener::mouseReleased。
它还包含一个mouseMoved函数,当鼠标移动时调用它。
这些函数都接收一个MouseEvent对象,在state变量里保存着当前鼠标的状态。
需要注意的是,MouseState对象即包含了鼠标移动的相对XY坐标(即,从上一次调用MouseListener::mouseMoved开始,它所移动的距离),
还包含了绝对XY坐标(即,屏幕上的准确位置)。
在我们开始修改TutorialFrameListener之前,请注意先对TutorialFrameListener类做两处大的改变:
class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener
我们继承了OIS的MouseListener和KeyListener类,这样我们才能从它们那里接收事件。
同样,调用ExampleFrameListener构造器也有变化:
TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr ):ExampleFrameListener(win, cam, true, true)
将后面两个参数改为true
指明了我们将要使用带缓冲的键盘鼠标输入。
再对类中的变量稍作修改:
protected: Real mRotate; // 旋转常量 Real mMove; // 运动常量 SceneManager *mSceneMgr; // 当前的场景管理器 SceneNode *mCamNode; // 当前摄像机附着的场景节点 bool mContinue; // 是否要继续渲染 Vector3 mDirection; // 指向正确的移动方向
mContinue变量是frameStarted方法的返回值。
当mContinue为false的时候,程序退出。
mDirection变量指定了在每一个帧里我们如何移动摄像机节点。
在构造器里,我们像在上次那样初始化一些变量,并把mContinue设成true。添加如下代码到TutorialFrameListener的构造器里:
TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr ) :ExampleFrameListener(win, cam, true, true) { // Populate the camera and scene manager containers mCamNode = cam->getParentSceneNode(); mSceneMgr = sceneMgr; // 设置旋转和移动速度 mRotate = 0.13; mMove = 250; // 继续渲染 mContinue = true; }
在ExampleFrameListener的构造器里已经取得了OIS的mMouse和mKeyboard对象。
我们调用这些输入对象的setEventCallback方法,把TutorialFrameListener注册成一个监听器。
//注册监听器 mMouse->setEventCallback(this); mKeyboard->setEventCallback(this);
最后,我们还要把mDirection初始化成零向量(因为我们最开始不需要它动):
TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr ) :ExampleFrameListener(win, cam, true, true) { // Populate the camera and scene manager containers mCamNode = cam->getParentSceneNode(); mSceneMgr = sceneMgr; // 设置旋转和移动速度 mRotate = 0.13; mMove = 250; // 继续渲染 mContinue = true; //注册监听器 mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); //零向量 mDirection = Vector3::ZERO; }
在我们深入之前,我们应该设置
Escape
键用来退出程序。
找到TutorialFrameListener::keyPressed方法,每当键盘上一个键被按下时,都会调用这个方法并传入一个KeyEvent对象。
我们能够通过这个对象的key变量来获取按键的键码(KC_*)。基于这个值,我们构造一个switch,为绑定所有程序里用到的按钮。
我们需要在switch语句里为其它按钮做绑定。首先我们要让用户按1、2键进行视口的切换。
代码基本上与上次相同:
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;
接下来我们要添加键盘移动。每次用户按下移动按键,我们都要朝正确的方向加上或者减去mMove:
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;
当按键被释放时,我们要立即取消mDirection向量上的移动。找到keyReleased方法,添加如下代码:
switch (e.key) { 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; } // switch return true;
好了,我们能根据按键输入对mDirection进行更新了。下面的代码与上次是一样的,添加到frameStarted函数里:
mCamNode->translate(mDirection * evt.timeSinceLastFrame, Node::TS_LOCAL);
接下来轮到鼠标了。我们从点击鼠标左键来控制灯的开关开始。
找到mousePressed函数并看看它的参数。用OIS,我们可以访问MouseEvent和MouseButtonID。
我们用MouseButtonID作为switch条件,来确定按下的是哪个按钮。用下面的代码替换掉mousePressed函数里的:
Light *light = mSceneMgr->getLight("Light1"); switch (id) { case OIS::MB_Left: light->setVisible(! light->isVisible()); break; } return true;
剩下来的事情就是绑定鼠标右键来进入鼠标观察模式。
每当鼠标移动时我们都检查右键是否按下。如果是,我们基于相对运动来转动摄像机。
通过传入函数的MouseEvent对象,我们能获取相对运动。它包含一个switch变量,里面有鼠标的状态(是关于鼠标的详细信息)。
MouseState::buttonDown告诉我们是否一个特定的按钮被按下,而“X”和“Y”变量告诉我们鼠标的相对运动。找到mouseMoved方法,用以下代码替换掉原来的:
if (e.state.buttonDown(OIS::MB_Right)) { mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_WORLD); mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL); } return true;
至此,完整的代码如下:
#include "ExampleApplication.h" class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener { public: TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr) : ExampleFrameListener(win, cam, true, true) { // Populate the camera and scene manager containers mCamNode = cam->getParentSceneNode(); mSceneMgr = sceneMgr; // set the rotation and move speed mRotate = 0.13; mMove = 250; // continue rendering mContinue = true; mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); mDirection = Vector3::ZERO; } bool frameStarted(const FrameEvent &evt) { if(mMouse) mMouse->capture(); if(mKeyboard) mKeyboard->capture(); mCamNode->translate(mDirection * evt.timeSinceLastFrame, Node::TS_LOCAL); return mContinue; } // MouseListener bool mouseMoved(const OIS::MouseEvent &e) { if (e.state.buttonDown(OIS::MB_Right)) { mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_WORLD); mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL); } return true; } bool mousePressed(const OIS::MouseEvent &e, OIS::MouseButtonID id) { Light *light = mSceneMgr->getLight("Light1"); switch (id) { case OIS::MB_Left: light->setVisible(! light->isVisible()); break; } return true; } bool mouseReleased(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; } // KeyListener bool keyPressed(const OIS::KeyEvent &e) { switch (e.key) { case OIS::KC_ESCAPE: mContinue = false; break; 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; 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; } return true; } bool keyReleased(const OIS::KeyEvent &e) { switch (e.key) { 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; } // switch return true; } protected: Real mRotate; // The rotate constant Real mMove; // The movement constant SceneManager *mSceneMgr; // The current SceneManager SceneNode *mCamNode; // The SceneNode the camera is currently attached to bool mContinue; // Whether to continue rendering or not Vector3 mDirection; // Value to move in the correct direction }; class TutorialApplication : public ExampleApplication { public: void createCamera(void) { // create camera, but leave at default position mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setNearClipDistance(5); } void createScene(void) { mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25)); // add the ninja Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh"); SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode"); node->attachObject(ent); // create the light Light *light = mSceneMgr->createLight("Light1"); light->setType(Light::LT_POINT); light->setPosition(Vector3(250, 150, 250)); light->setDiffuseColour(ColourValue::White); light->setSpecularColour(ColourValue::White); // Create the scene node node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400)); node->yaw(Degree(-45)); node->attachObject(mCamera); // create the second camera node/pitch node node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400)); } void createFrameListener(void) { // Create the FrameListener mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr); mRoot->addFrameListener(mFrameListener); // Show the frame stats overlay mFrameListener->showDebugOverlay(true); } }; #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 { // Create application object TutorialApplication app; try { app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s ", e.getFullDescription().c_str()); #endif } return 0; }
运行效果如图: