Ogre基础教程5:有缓冲输入

教程介绍

这篇教程将介绍利用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::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的坐标系统。

总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值