您的位置:首页 > 其它

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的坐标系统。

总结

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  教程