交互
在讲输入之前,先理清几个概念。
无缓冲输入unbuffered
不断的响应按键信息,直到按键放开。例如,按下上下左右,使得角色持续移动。一般来讲适合于3D场景漫游过程,当在每帧渲染之前,系统捕获输入设备状态,并根据这些状态对场景中的物体和摄象机进行控制。
缓冲输入buffered
在一个按键放开之前,只处理一次输入信息。例如呼出主菜单。适合于GUI界面的情况(如设置菜单),输入设备状态可以被发送到各GUI元素进行处理(如按钮被按下)。
无缓冲输入
动画都是一帧一帧刷新的,故三维动画也不例外。在Ogre框架中,在帧刷新的地方加入了响应方法以便开发者调用。1
2
3
4
5
6
7
8
9
10bool Root::renderOneFrame(void)
{
if(!_fireFrameStarted())
return false;
if (!_updateAllRenderTargets())
return false;
return _fireFrameEnded();
}
以上分为三步,帧动画开始前,帧动画开始时,帧动画结束后。这些方法会在每次帧刷新时调用。对用的方法也分别为1
2
3fireFrameStarted();
updateAllRenderTargets();
fireFrameEnded();
而在updateAllRenderTargets()
方法中1
2
3
4
5
6
7
8
9
10
11bool Root::_updateAllRenderTargets(void)
{
mActiveRenderer->_updateAllRenderTargets(false);
bool ret = _fireFrameRenderingQueued();
// thread is blocked for final swap
mActiveRenderer->_swapAllRenderTargetBuffers(
mActiveRenderer->getWaitForVerticalBlank());
...
那么调用关系就清楚了,简单归纳为下列五步:
1.触发所有FrameListener的frameStarted
2.更新所有渲染目标(不翻转)
3.触发所有FrameListener的frameRenderingQueued
4.翻转所有渲染目标
5.触发所有FrameListener的frameEnded。
准备
TutorialApplication.h1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TutorialApplication : public BaseApplication
{
public:
TutorialApplication();
virtual ~TutorialApplication();
protected:
virtual void createScene();
virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);
private:
bool processUnbufferedInput(const Ogre::FrameEvent& fe);
};
TutorialApplication.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
TutorialApplication::TutorialApplication()
{
}
TutorialApplication::~TutorialApplication()
{
}
void TutorialApplication::createScene()
{
mSceneMgr->setAmbientLight(Ogre::ColourValue(.25, .25, .25));
Ogre::Light* pointLight = mSceneMgr->createLight("PointLight");
pointLight->setType(Ogre::Light::LT_POINT);
pointLight->setPosition(250, 150, 250);
pointLight->setDiffuseColour(Ogre::ColourValue::White);
pointLight->setSpecularColour(Ogre::ColourValue::White);
Ogre::Entity* ninjaEntity = mSceneMgr->createEntity("ninja.mesh");
Ogre::SceneNode* ninjaNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
ninjaNode->attachObject(ninjaEntity);
}
bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
bool ret = BaseApplication::frameRenderingQueued(fe);
return ret;
}
bool TutorialApplication::processUnbufferedInput(const Ogre::FrameEvent& fe)
{
return true;
}
在类中定义变量1
2
3
4static bool mouseDownLastFrame = false;
static Ogre::Real toggleTimer = 0.0;
static Ogre::Real rotate = .13;
static Ogre::Real move = 250;
监听鼠标左键
鼠标左键打开和关闭灯源,写入processUnbufferedInput
中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15bool TutorialApplication::processUnbufferedInput(const Ogre::FrameEvent& fe){
//监控鼠标左键,按下则为true
bool leftMouseDown = mMouse->getMouseState().buttonDown(OIS::MB_Left);
//保证鼠标为非长按状态
if (leftMouseDown && !mouseDownLastFrame)
{
Ogre::Light* light = mSceneMgr->getLight("PointLight");
light->setVisible(!light->isVisible());
}
mouseDownLastFrame = leftMouseDown;
return true;
}
监听鼠标右键
鼠标右键有个计时的功能,即只能在一定时差下才能改变灯源的状态1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
bool ret = BaseApplication::frameRenderingQueued(fe);
if(!processUnbufferedInput(fe)) return false;
toggleTimer -= fe.timeSinceLastFrame;
//计时器。在0.5秒内右键不能控制灯光的开关
if ((toggleTimer < 0) && mMouse->getMouseState().buttonDown(OIS::MB_Right))
{
toggleTimer = 0.5;
Ogre::Light* light = mSceneMgr->getLight("PointLight");
light->setVisible(!light->isVisible());
}
return ret;
}
键盘监听
继续在frameRenderingQueued
方法下添加方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32Ogre::Vector3 dirVec = Ogre::Vector3::ZERO;
//键盘I的监听
if (mKeyboard->isKeyDown(OIS::KC_I))
dirVec.z -= move;
//键盘U的监听
if (mKeyboard->isKeyDown(OIS::KC_U))
dirVec.y += move;
//键盘O的监听
if (mKeyboard->isKeyDown(OIS::KC_O))
dirVec.y -= move;
//键盘J的监听
if (mKeyboard->isKeyDown(OIS::KC_J))
{
//如果同时按下了左shift键,则顺时针旋转模型
if(mKeyboard->isKeyDown(OIS::KC_LSHIFT))
mSceneMgr->getSceneNode("NinjaNode")->yaw(Ogre::Degree(5 * rotate));
else
dirVec.x -= move;
}
//键盘L的监听
if (mKeyboard->isKeyDown(OIS::KC_L))
{
//如果同时按下了左shift键,则逆时针旋转模型
if(mKeyboard->isKeyDown(OIS::KC_LSHIFT))
mSceneMgr->getSceneNode("NinjaNode")->yaw(Ogre::Degree(-5 * rotate));
else
dirVec.x += move;
}
//移动模型
mSceneMgr->getSceneNode("NinjaNode")->translate(
dirVec * fe.timeSinceLastFrame,
Ogre::Node::TS_LOCAL);
缓冲输入
准备tudohouse.mesh
和fw12b.jpg的材质图片。在material Examples的脚本中写入1
2
3
4
5
6
7
8
9
10
11
12
13
14material Examples/TudorHouse
{
technique
{
pass
{
texture_unit
{
texture fw12b.jpg
tex_address_mode clamp
}
}
}
}
(Ogre1.9中已经包括这些东西)
TutorialApplication.h1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
};
TutorialApplication.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37TutorialApplication::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;
}
按键响应和接口
键盘有两个方法,按下和松开1
2
3//TutorialApplication.h
virtual bool keyPressed(const OIS::KeyEvent& ke);
virtual bool keyReleased(const OIS::KeyEvent& ke);
1 | //TutorialApplication.cpp |
鼠标有三个方法,鼠标移动,鼠标按下,鼠标松开1
2
3
4//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);
1 | //TutorialApplication.cpp |
键盘监听并处理
1 | bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke) |
1 | bool TutorialApplication::keyReleased(const OIS::KeyEvent& ke) |
最后在帧刷新处更新摄像头的位置。frameRenderingQueued
1
mCamNode->translate(mDirection * fe.timeSinceLastFrame, Ogre::Node::TS_LOCAL);
鼠标监听并处理
左键控制灯源的开关mousePressed()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15bool TutorialApplication::mousePressed(
const OIS::MouseEvent& me, OIS::MouseButtonID id)
{
Ogre::Light* light = mSceneMgr->getLight("Light1");
switch (id)
{
//左键控制灯光开关
case OIS::MB_Left:
light->setVisible(!light->isVisible());
break;
default:
break;
}
return true;
}
右键移动旋转1
2
3
4
5
6
7
8
9
10
11bool TutorialApplication::mouseMoved(const OIS::MouseEvent& me)
{
if (me.state.buttonDown(OIS::MB_Right))
{
//绕y轴旋转
mCamNode->yaw(Ogre::Degree(-mRotate * me.state.X.rel), Ogre::Node::TS_WORLD);
//绕x轴旋转
mCamNode->pitch(Ogre::Degree(-mRotate * me.state.Y.rel), Ogre::Node::TS_LOCAL);
}
return true;
}