介绍
这个教程包含了如何使用一个Entity,移动它,并让它在预先定义的点间移动,也包含了四元数旋转的基础
通过如何让一个实体朝着他移动的方向。 你可以慢慢增加代码到工程并观看生成后的结果。
开始
首先,给这个demo创建一个新工程, 并增加代码到源文件。
#include "ExampleApplication.h"
#include <deque>
using namespace std;
class MoveDemoListener : public ExampleFrameListener
{
public:
MoveDemoListener(RenderWindow* win, Camera* cam, SceneNode *sn,
Entity *ent, deque<Vector3> &walk)
: ExampleFrameListener(win, cam, false, false), mNode(sn),
mEntity(ent), mWalkList( walk )
{
} // MoveDemoListener
/* This function is called to start the object moving to the next position
in mWalkList.
*/
bool nextLocation( )
{
return true;
} // nextLocation( )
bool frameStarted(const FrameEvent &evt)
{
return ExampleFrameListener::frameStarted(evt);
}
protected:
Real mDistance; // The distance the object has left to travel
Vector3 mDirection; // The direction the object is moving
Vector3 mDestination; // The destination the object is moving towards
AnimationState *mAnimationState; // The current animation state of the object
Entity *mEntity; // The Entity we are animating
SceneNode *mNode; // The SceneNode that the Entity is attached to
std::deque<Vector3> mWalkList; // The list of points we are walking to
Real mWalkSpeed; // The speed at which the object is moving
};
class MoveDemoApplication : public ExampleApplication
{
protected:
public:
MoveDemoApplication()
{
}
~MoveDemoApplication()
{
}
protected:
Entity *mEntity; // The entity of the object we are animating
SceneNode *mNode; // The SceneNode of the object we are moving
std::deque<Vector3> mWalkList; // A deque containing the waypoints
void createScene(void)
{
}
void createFrameListener(void)
{
mFrameListener= new MoveDemoListener(mWindow, mCamera,
mNode, mEntity, mWalkList);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};
#if 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
MoveDemoApplication app;
try {
app.go();
} catch( Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!",
MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occured: %s\n",
e.getFullDescription().c_str());
#endif
}
return 0;
}
对代码的分析:
设置这个场景
在开始前,注意我们已经在MoveDemoApplication里定义了三个变量。mEntity保存了我们创建的entity。mNode 保存了我们创建的节点 。mWalkList 包含了我们希望移动到的所有点。
接下来到 MoveDemoApplication::createScene 函数里并增加下面的代码, 我们先要设置环境光来让我们在屏幕上看到场景里的所有对象:
// Set the default lighting.
mSceneMgr->setAmbientLight( ColourValue( 1.0f, 1.0f, 1.0f ) );
接下来,我们创建一个机器人到屏幕,好让我们可以播放它。那你就需要创建一个机器人实体,然后创建一个SceneNode让它依附:
// Create the entity
mEntity = mSceneMgr->createEntity( "Robot", "robot.mesh" );
// Create the scene node
mNode = mSceneMgr->getRootSceneNode( )->
createChildSceneNode( "RobotNode", Vector3( 0.0f, 0.0f, 25.0f ) );
mNode->attachObject( mEntity );
这些都是非常基础的,因此我不再相继解释。在下个代码块,我们要要告诉机器人需要移动到哪里。
deque 对象是一个很高效的double ended queue .我们只使用他的一些方法。 push_front 和 push_back 方法把 项目 分别队列最前或结尾放入。front 和 back 方法返回queue的最前和结尾的值 pop_front 和 pop_back 为移除。 下面的代码我们存放两个向量, 为后面我们让机器人移动的:
// Create the walking list
mWalkList.push_back( Vector3( 550.0f, 0.0f, 50.0f ) );
mWalkList.push_back( Vector3(-100.0f, 0.0f, -200.0f ) );
下面,我们需要放置一些对象到场景,来显示机器人在向目标移动。这样我们就能在屏幕上看到机器人相对于其他物体移动。 注意他们位置的 -Y值 ,把对象放在机器人要移动位置的下面,这样当他到达正确位置时,就会站在那个对象的上面:
// Create objects so we can see movement
Entity *ent;
SceneNode *node;
ent = mSceneMgr->createEntity( "Knot1", "knot.mesh" );
node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot1Node",
Vector3( 0.0f, -10.0f, 25.0f ) );
node->attachObject( ent );
node->setScale( 0.1f, 0.1f, 0.1f );
ent = mSceneMgr->createEntity( "Knot2", "knot.mesh" );
node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot2Node",
Vector3( 550.0f, -10.0f, 50.0f ) );
node->attachObject( ent );
node->setScale( 0.1f, 0.1f, 0.1f );
ent = mSceneMgr->createEntity( "Knot3", "knot.mesh" );
node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot3Node",
Vector3(-100.0f, -10.0f,-200.0f ) );
node->attachObject( ent );
node->setScale( 0.1f, 0.1f, 0.1f );
最后,我们希望摄象机在一个好的视点观看。我们移动摄象机到一个好的位置:
// Set the camera to look at our handywork
mCamera->setPosition( 90.0f, 280.0f, 535.0f );
mCamera->pitch( Degree(-30.0f) );
mCamera->yaw( Degree(-15.0f) );
现在编译和运行,你应该看到这样:
在进入下一节前, MoveDemoListener的构造函数需要注释一下, 作为在MoveDemoApplication::createFrameListener里第一行调用方法, 我们通过构造参数传递SceneNode ,Entity和deque三个参数 。
动画
我们需要设置一些基本的动画。 动画在引擎里是非常简单的。 你需要从实体对象获得AnimationState 设置它的选项,并enable 它。 这就能让动画动起来,但你需要在每帧后增加time来让动画运行起来。
首先到 MoveDemoListener的构造函数,增加下面的代码:
// Set idle animation
mAnimationState = ent->getAnimationState( "Idle" );
mAnimationState->setLoop( true );
mAnimationState->setEnabled( true );
第2行是获得Entity的AnimationState ,第3行调用setLoop(true),可以让动画一遍一遍的循环。有些动画(比如死亡),你可能需要把它设置为false. 第4行实际上允许动画。
显然,如果我们编译和运行,我们会看到没有任何改变。这是因为我们需要在每帧开始都更新动画状态。
找到 MoveDemoListener::frameStarted 方法 并增加这行代码到函数里:
mAnimationState->addTime( evt.timeSinceLastFrame );
现在运行程序,你可以看到机器人执行他的IDLE状态站在空间里。
移动机器人
现在我们要执行让机器人从一个点走到另一个点的任务了。在开始前,我要描述一下保存在MoveDemoListener
类里的变量。我们使用4个变量来完成机器人移动的任务。首先,我们要保存机器人移动的朝向mDirection
我们要保存机器人移动的目的点mDestination, 机器人移动的剩余距离mDistance, 机器人的移动速度
mWalkSpeed
我们首先应该清除 MoveDemoListener 的构造函数, 用稍微不同的代码来替代以前的。 首先,需要设置类的
变量。我们设置行走速度为35单位每秒。这个是需要注意的。我们设置mDirection 为0向量,因为梢后我们
要使用它来判断我们是否移动机器人。
// Set default values for variables
mWalkSpeed = 35.0f;
mDirection = Vector3::ZERO;
现在我们需要设置机器人运动。如果有一个其他的位置,我们希望机器人开始移动。 因此,我们调用
nextLocation 函数。 把这个代码增加在 MoveDemoListener::frameStarted 方法里的最上面。
if ( mDirection == Vector3::ZERO )
{
if ( nextLocation() )
{
// Set walking animation
mAnimationState = mEntity->getAnimationState( "Walk" );
mAnimationState->setLoop( true );
mAnimationState->setEnabled( true );
}
}
如果你现在编译和运行代码正确,机器人将在空间里行走。 因为机器人是从ZERO方向开始行走,而
MoveDemoListener::nextLocation 的函数返回的一直是true. 下一步我们要增加一些智能
在MoveDemoListener::nextLocation 函数里
现在我们将实际的在场景里移动机器人。 为了实现它,我们需要他在每帧移动一些。
到MoveDemoListener::frameStarted方法里,我们在上次增加片段的后面增加下面的代码段
这个代码在 mDirection != Vector3::ZERO时让机器人实际移动。
else
{
Real move = mWalkSpeed * evt.timeSinceLastFrame;
mDistance -= move;
现在,我们需要检测我们的移动是否超过了目标位置。那就是,如果mDistance小于0,我们将
跳出这个目标点,并设置下一个点。注意我们将把mDirection 设置为ZERO 向量。如果
nextLocation 不在改变mDirection ,我们也就不再移动了。
if (mDistance <= 0.0f)
{
mNode->setPosition( mDestination );
mDirection = Vector3::ZERO;
现在我们已经移动到这个点,我们需要设置下一个点的运动,当我们知道是否需要移动到下一个点时,我们可以
设置合适的动画。如果要去另外一个点设为 walking , 如果没有了就设置为 idle.
// Set animation based on if the robot has another point to walk to.
if (! nextLocation( ) )
{
// Set Idle animation
mAnimationState = mEntity->getAnimationState( "Idle" );
mAnimationState->setLoop( true );
mAnimationState->setEnabled( true );
}
else
{
// Rotation Code will go here later
}
}
注意我们不需要再设置 walking 动画,如果还有点在 queue 里要移动。因为机器人已经是 walking
状态,没理由让他再次去做。然而,如果机器人需要移动到另外一个点,我们需要旋转他面对那个点。
因为一些其他原因,我们现在先放置注释符号。我们稍后会回来处理
当我们非常接近目标点时,就要注意了。我们当处理一个正常情况,当机器人正在前往位置点而还没到时。
我们需要平移机器人在我们移动的方向的,并通过移动变量移动它一定的距离:
else
{
mNode->translate( mDirection * move );
} // else
} // if
我们差不多完成了,我们的代码除了设置移动变量外,可以做所有的事了。如果我们能设置一个合理的
移动变量,我们的机器人会象我们希望的那样移动。找到 MoveDemoListener::nextLocation 函数
这个函数返回false,当我们已经走完所的点时。这将是我们函数一开始就要做的。
if ( mWalkList.empty() )
return false;
现在我们要设置移动变量,首先我们从队列里获得目标响亮,我们通过目标点减当前场景节点的方式获得
方向向量。而且我们需要方向向量为一个单位向量(长度等于1),normalise 为我们实现了这些。并返回了
原来向量的长度。 因为我们需要设置到目标的距离。
mDestination = mWalkList.front( ); // this gets the front of the deque
mWalkList.pop_front( ); // this removes the front of the deque
mDirection = mDestination - mNode->getPosition( );
mDistance = mDirection.normalise( );
现在编译和运行代码,他应该可以工作了。 机器人现在可以移动到所有的点,但他还是一直朝向
Vector3::UNIT_X 方向(默认的) 当移动到下一个点时,我们需要改变他的方向
我们需要做的是获得机器人的朝向,然后用rotate函数来旋转对象到正确的位置。把下面的代码放到前面
注释的地方。 第一行获得机器人的朝向,第2行建立一个四元数表现从当前方向到目标向量的旋转。
第3行是实际旋转机器人。
//UNIT_X是机器人的默认正面朝向,即面向正X轴
// mNode是挂接robot的结点
// src运算的结果就是,下一帧机器人的应该面对的方向.(是一个3维向量)
Vector3 src = mNode->getOrientation( ) * Vector3::UNIT_X; 【getOrientation函数返回一个四元数,一个四元数乘以一个向量,结果就是把这个向量进行对应旋转,得到旋转后的新向量。mNode->getOrientation( ) 返回的是这个节点从最开始到现在进行过的所有旋转操作,当然这些旋转操作包装在一个四元数对象中。然后用这个四元数乘以节点最初的方向就得到当前节点的朝向。】
Ogre::Quaternion quat = src.getRotationTo( mDirection ); 【即根据两个方向向量获取从其中一个向量(也叫源向量)转到另一个向量(也叫目的向量),对应的四元数】
mNode->rotate( quat ); 【对节点进行旋转,而且旋转的细节又一次存入到mNode具有的四元数对象中了,以后从mNode->getOrientation( )可以获取这个四元数】
【注意,在Ogre中,旋转是使用四元数来储存的,可以认为四元数是一堆旋转操作的包装,把这个包装作用在哪个节点头上,哪个节点就要运行这一堆旋转操作】
【关于四元数入门参见本博客 四元数入门(简单容易理解) 】
还有一些问题。四元数是什么? 基本上说,四元数表现为一个三维空间的旋转。它们用来追踪对象是如何在空间被
放置的。 第一行我们调用 getOrientation 方法,会返回一个四元数来表现机器人当前朝向,通过于UNIT_X向量
相乘, 我们获得当前机器人的朝向。我们保存在src中。第2行 getRotationTo 提供给我们一个四元数来表现
机器人从当前朝向转到我们需要的朝向的旋转。在第3行里,我们旋转节点面向新的方向。
我们上面创建的代码还有点问题。有一种情况下SceneNode::rotate 会失败。如果我们试图旋转189度,这个旋转
代码会因为被ZERO除而失败。为了修正他,我们需要测试是否执行180度旋转,如果是,我们将对机器人做简单的
180度yaw来代替旋转。因此,删除上面三行,用下面的的来覆盖:
Vector3 src = mNode->getOrientation( ) * Vector3::UNIT_X;
if ( (1.0f + src.dotProduct( mDirection )) < 0.0001f ) 【dotProduct是计算两个向量的点积的意思,点两个单位向量的点积的结果不能等于或接近-1】
{
mNode->yaw( Degree(180) );
}
else
{
Quaternion quat = src.getRotationTo( mDirection );
mNode->rotate( quat );
} // else
如果两个向量是彼此相对的(就是说,他们之间角度是180度)那么他们的点乘为 -1.如果我们点乘两个向量的
结果等于 -1,我们需要yaw 180度,否则就用rotate 来代替。 为了是小于0.0001f? 不要忘记浮点的取舍
错误。你不能直接比较两个浮点值。 最后要注意的,在这个例子里,两个向量点乘的值是在[-1, 1]之间的。
你需要一些基本的线性代数知识来实现图形程序。
我们的代码完成了,编译和运行Demo,来看看机器人沿我们所给的点移动吧!
所有的完整代码在下面,把下面的代码考到一个cpp中就可以直接运行了:
#include "ExampleApplication.h"
#include <deque>
#pragma comment(lib,"OgreMain_d.lib")
#pragma comment(lib,"OIS_d.lib")
using namespace std;
class MoveDemoListener : public ExampleFrameListener
{
public:
MoveDemoListener(RenderWindow* win, Camera* cam, SceneNode *sn,
Entity *ent, std::deque<Vector3> &walk)
: ExampleFrameListener(win, cam, false, false), mNode(sn),
mEntity(ent), mWalkList(walk)
{
//设置不走动时机器人的动画
mAnimationState = ent->getAnimationState("Idle");
mAnimationState->setLoop(true);
mAnimationState->setEnabled(true);
// Set default values for variables
mWalkSpeed = 35.0f;
mDirection = Vector3::ZERO;
} // MoveDemoListener
/* This function is called to start the object moving to the next position
in mWalkList.
*/
bool nextLocation()
{
if (mWalkList.empty())
return false;
mDestination = mWalkList.front(); // this gets the front of the deque
mWalkList.pop_front(); // this removes the front of the deque
mDirection = mDestination - mNode->getPosition();
mDistance = mDirection.normalise();
return true;
} // nextLocation()
bool frameStarted(const FrameEvent &evt)
{
if (mDirection == Vector3::ZERO)
{
if (nextLocation())
{
//设置行走状态的动画
mAnimationState = mEntity->getAnimationState("Walk");
mAnimationState->setLoop(true);
mAnimationState->setEnabled(true);
}
}
else
{
Real move = mWalkSpeed * evt.timeSinceLastFrame;
mDistance -= move;
if (mDistance <= 0.0f)
{
mNode->setPosition(mDestination);
mDirection = Vector3::ZERO;
//根据机器人下一步是否还有要到达的位置决定要用的动画状态
if (!nextLocation())
{
// Set Idle animation
mAnimationState = mEntity->getAnimationState("Idle");
mAnimationState->setLoop(true);
mAnimationState->setEnabled(true);
}
else
{
//机器人面朝向旋转的代码
Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
{
mNode->yaw(Degree(180));
}
else
{
Quaternion quat = src.getRotationTo(mDirection);
mNode->rotate(quat);
} // else
}
}
else
{
mNode->translate(mDirection * move);
} // else
} // if
//动画状态每一帧更新以便把动画显示出来(传一个时间间隔作为参数)
mAnimationState->addTime(evt.timeSinceLastFrame);
return ExampleFrameListener::frameStarted(evt);
}
protected:
Real mDistance; // The distance the object has left to travel
Vector3 mDirection; // The direction the object is moving
Vector3 mDestination; // The destination the object is moving towards
AnimationState *mAnimationState; // The current animation state of the object
Entity *mEntity; // The Entity we are animating
SceneNode *mNode; // The SceneNode that the Entity is attached to
std::deque<Vector3> mWalkList; // The list of points we are walking to
Real mWalkSpeed; // The speed at which the object is moving
};
class MoveDemoApplication : public ExampleApplication
{
protected:
public:
MoveDemoApplication()
{
}
~MoveDemoApplication()
{
}
protected:
Entity *mEntity; // The entity of the object we are animating
SceneNode *mNode; // The SceneNode of the object we are moving
std::deque<Vector3> mWalkList; // A deque containing the waypoints
void createScene(void)
{
//设置环境光,即全局光照
mSceneMgr->setAmbientLight(ColourValue(1.0f, 1.0f, 1.0f));
//建一个机器人实体
mEntity = mSceneMgr->createEntity("Robot", "robot.mesh");
//建一个场景节点
mNode = mSceneMgr->getRootSceneNode()->
createChildSceneNode("RobotNode", Vector3(0.0f, 0.0f, 25.0f));
mNode->attachObject(mEntity);
//建一个机器人行走路径点集的列表
mWalkList.push_back(Vector3(550.0f, 0.0f, 50.0f));
mWalkList.push_back(Vector3(-100.0f, 0.0f, -200.0f));
//建一些其他实体,作为机器人移动过程中的参照物
Entity *ent;
SceneNode *node;
ent = mSceneMgr->createEntity("Knot1", "knot.mesh");
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot1Node",
Vector3(0.0f, -10.0f, 25.0f));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
ent = mSceneMgr->createEntity("Knot2", "knot.mesh");
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot2Node",
Vector3(550.0f, -10.0f, 50.0f));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
ent = mSceneMgr->createEntity("Knot3", "knot.mesh");
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot3Node",
Vector3(-100.0f, -10.0f,-200.0f));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
//调整下摄像机以便于观看
mCamera->setPosition(90.0f, 280.0f, 535.0f);
mCamera->pitch(Degree(-30.0f));
mCamera->yaw(Degree(-15.0f));
}
void createFrameListener(void)
{
mFrameListener= new MoveDemoListener(mWindow, mCamera,
mNode, mEntity, mWalkList);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};
#if 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
MoveDemoApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occured!",
MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occured: %s\n",
e.getFullDescription().c_str());
#endif
}
return 0;
}