选中物体
这次对于所有的鼠标事件,我创建了封闭函数来处理它们。当用户按下鼠标左键,"onLeftPressed"函数被调用,当按下右键时"onRightReleased"函数被调用,等等。
一如既往,首先给出框架代码:
#include <CEGUI/CEGUISystem.h>
#include <CEGUI/CEGUISchemeManager.h>
#include <OgreCEGUIRenderer.h>
#include "ExampleApplication.h"
class MouseQueryListener : public ExampleFrameListener, public OIS::MouseListener
{
public:
MouseQueryListener(RenderWindow* win, Camera* cam, SceneManager *sceneManager, CEGUI::Renderer *renderer)
: ExampleFrameListener(win, cam, false, true), mGUIRenderer(renderer)
{
// Setup default variables
mCount = 0;
mCurrentObject = NULL;
mLMouseDown = false;
mRMouseDown = false;
mSceneMgr = sceneManager;
// Reduce move speed
mMoveSpeed = 50;
mRotateSpeed /= 500;
// Register this so that we get mouse events.
mMouse->setEventCallback(this);
// Create RaySceneQuery
mRaySceneQuery = mSceneMgr->createRayQuery(Ray());
} // MouseQueryListener
~MouseQueryListener()
{
mSceneMgr->destroyQuery(mRaySceneQuery);
}
bool frameStarted(const FrameEvent &evt)
{
// Process the base frame listener code. Since we are going to be
// manipulating the translate vector, we need this to happen first.
if (!ExampleFrameListener::frameStarted(evt))
return false;
// Setup the scene query
Vector3 camPos = mCamera->getPosition();
Ray cameraRay(Vector3(camPos.x, 5000.0f, camPos.z), Vector3::NEGATIVE_UNIT_Y);
mRaySceneQuery->setRay(cameraRay);
// Perform the scene query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();
// Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{
Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );
}
return true;
}
bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
// Left mouse button up
if (id == OIS::MB_Left)
{
onLeftReleased(arg);
mLMouseDown = false;
} // if
// Right mouse button up
else if (id == OIS::MB_Right)
{
onRightReleased(arg);
mRMouseDown = false;
} // else if
return true;
}
bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
// Left mouse button down
if (id == OIS::MB_Left)
{
onLeftPressed(arg);
mLMouseDown = true;
} // if
// Right mouse button down
else if (id == OIS::MB_Right)
{
onRightPressed(arg);
mRMouseDown = true;
} // else if
return true;
}
bool mouseMoved(const OIS::MouseEvent &arg)
{
// Update CEGUI with the mouse motion
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
// If we are dragging the left mouse button.
if (mLMouseDown)
{
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width),mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();
if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
} // if
// If we are dragging the right mouse button.
else if (mRMouseDown)
{
mCamera->yaw(Degree(-arg.state.X.rel * mRotateSpeed));
mCamera->pitch(Degree(-arg.state.Y.rel * mRotateSpeed));
} // else if
return true;
}
// Specific handlers
void onLeftPressed(const OIS::MouseEvent &arg)
{
// Setup the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
// Execute query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin( );
// Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
char name[16];
sprintf(name, "Robot%d", mCount++);
Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
}
void onLeftReleased(const OIS::MouseEvent &arg)
{
}
void onRightPressed(const OIS::MouseEvent &arg)
{
CEGUI::MouseCursor::getSingleton().hide();
}
virtual void onRightReleased(const OIS::MouseEvent &arg)
{
CEGUI::MouseCursor::getSingleton().show();
}
protected:
RaySceneQuery *mRaySceneQuery; // The ray scene query pointer
bool mLMouseDown, mRMouseDown; // True if the mouse buttons are down
int mCount; // The number of robots on the screen
SceneManager *mSceneMgr; // A pointer to the scene manager
SceneNode *mCurrentObject; // The newly created object
CEGUI::Renderer *mGUIRenderer; // CEGUI renderer
};
class MouseQueryApplication : public ExampleApplication
{
protected:
CEGUI::OgreCEGUIRenderer *mGUIRenderer;
CEGUI::System *mGUISystem; // CEGUI system
public:
MouseQueryApplication()
{
}
~MouseQueryApplication()
{
}
protected:
void chooseSceneManager(void)
{
// Use the terrain scene manager.
mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE);
}
void createScene(void)
{
// Set ambient light
mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
// World geometry
mSceneMgr->setWorldGeometry("terrain.cfg");
// Set camera look point
mCamera->setPosition(40, 100, 580);
mCamera->pitch(Degree(-30));
mCamera->yaw(Degree(-45));
// CEGUI setup
mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
mGUISystem = new CEGUI::System(mGUIRenderer);
// Mouse
CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");
}
void createFrameListener(void)
{
mFrameListener = new MouseQueryListener(mWindow, mCamera, mSceneMgr, mGUIRenderer);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};
#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
MouseQueryApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s/n",
e.getFullDescription().c_str());
#endif
}
return 0;
}
运行程序会发现程序运行的结果和手札19给出的代码,即手札18的程序的最终代码运行结果是一样的,只不过正如前面说的将相应实践放到了封闭的函数中了,你应该花一些时间来消化这些不同之处!
一、实现选中物体
最终能够做到当你放置物体后,能“拾取”并移动它。我们希望用户知道他目前正在操纵哪一个物体。在游戏里,我们可能以某种特殊的方式来高亮这个物体。而在这里我们用showBoundingBox方法来创建一个围绕该物体的方盒。当鼠标首次按下时,取消旧的选择物体上的包围盒,然后当选择了一新物体时,给新物体加上包围盒。为此,我们
在onLeftPressed函数的开头添加如下代码:
// 打开包围盒
if (mCurrentObject)
mCurrentObject->showBoundingBox(false);
并在onLeftPressed末尾添加代码:
// Show the bounding box to highlight the selected object
if (mCurrentObject)
mCurrentObject->showBoundingBox(true);
运行你的程序发现单击创建的机器人会有一圈的白框
————————————————————————————————————————————
二、实现添加不同种类的物体
实现代码不仅能够添加机器人还能够放置和移动忍者。
我们需要一个“机器人模式”和一个“忍者模式”,来决定在屏幕上放置的物体。
我们把空格键设置成切换按钮,并且显示信息提示用户目前处于哪一种模式。
首先,我们把MouseQueryListener设置成机器人模式。我们添加一个变量来保存物体状态
在MouseQueryListener类的protected变量区域添加这个变量:
bool mRobotMode; //变量用于标明当前状态
在MouseQueryListener类的构造函数进行初始化:
// 设置文本、缺省状态
mRobotMode = true;
mDebugText = "Robot Mode Enabled - Press Space to Toggle";
下面有修改单击响应代码,单击时根据mRobotMode的状态来断定应当添加什么模型:
在onLeftPressed里修改代码:
将这三句
char name[16];
sprintf(name, "Robot%d", mCount++);
Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
修改为:
Entity *ent;
char name[16];
if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else这些代码大家一定能够看懂我就不解释了
下面绑定空格键,来改变状态。
在frameStarted里(第一个if语句之后)添加:
// 切换模式
if(mKeyboard->isKeyDown(OIS::KC_SPACE) && mTimeUntilNextToggle <= 0)
{
mRobotMode = !mRobotMode;
mTimeUntilNextToggle = 1;
mDebugText = (mRobotMode ? String("Robot") : String("Ninja")) + " Mode Enabled - Press Space to Toggle";
}
看看效果吧!
——————————————————————————————————————————
三、选中物体
首先介绍一下下面这个结构体,你可以通过连接查看的类结构
RaySceneQueryResult返回一个RaySceneQueryResultEntry结构体的iterator。
这个结构体包含三个变量。
distance变量告诉你这个物体沿着射线有多远。另外两个变量的其中一个将是null(movable或者worldFramegment)。
movable变量包含一个MovableObject对象(MovableObject基本上可以是任何你能绑在SceneNode上的对象(像实体、光源,等))你可以看一下后面那个继承关系图
如果与射线相交的话。如果射线接触到一个地形片段,worldFragment将保存这个worldFragment对象(比如地形)。
大多数RaySceneQueries的应用包括选取和操纵MovableObject对象,以及它们所绑定到的SceneNodes 。调用getName方法获取MovableObject的名称。调用getParentSceneNode(或getParentNode)获取它们所绑定到的SceneNode。如果RaySceneQueryResultEntry的结果不是一个MovableObject,movable变量则为null。
WorldFragment是完全另一种怪物。当RaySceneQueryResult中的worldFragment成员被设置时,就意味着返回结果是SceneManager创建的世界几何(world geometry)的一部分。返回的world fragment的类型是基于SceneManager的。它是这样实现的,WorldFragment结构体包含一个fragmentType变量,以指明world fragment的类型。基于这个fragmentType变量,设置其它成员变量(singleIntersection, planes, geometry, 或者renderOp)。一般来说,RaySceneQueries只返回WFT_SINGLE_INTERSECTION类型的WorldFragments。singleIntersection变量只是一个Vector3,用来报告交点的坐标。
介绍到这里,书归正传
我们要做的另一件事情是“拾起”并拖拽已经被放置的物体。当前你若点击一个已经放置的物体,程序会忽略它,并在它后面放置另一个机器人。我们现在来修正它。
首先要保证当我们点击鼠标,我们能得到沿射线上的第一个东西。为此,我们需要设置RaySceneQuery按深度排序。找到onLeftPressed函数里的如下代码:
// Setup the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
// Execute query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin( );
修改成这样(两处变化):
// Setup the ray scene query
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
mRaySceneQuery->setSortByDistance(true);//按顺序返回结果
// Execute query RaySceneQueryResult &result = mRaySceneQuery->execute(); RaySceneQueryResult::iterator itr;
以下这段代码是我们要重写的,因为我们不能在单击时在创建实体
// Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
Entity *ent;
char name[16];
if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else这些代码大家一定能够看懂我就不解释了
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
}
实现能够选取已经放置在屏幕上的物体分为两步。
首先,如果用户点击一个物体,则使mCurrentObject等于它的父节点。
如果用户没有点击在物体上(而是点在地型上)时,就像以前一样放置一个新的机器人。
第一个要做的修改就是,使用一个for循环来代替if语句:
for ( itr = result.begin(); itr != result.end(); itr++ )
{
if (itr->movable && itr->movable->getName().substr(0, 5) != "tile[")
{
mCurrentObject = itr->movable->getParentSceneNode();
break;
// 首先我们要检查第一个交点的是不是一个MovableObject
//如果是,我们把它的父节点赋给mCurrentObject。还要做另一个判断
//TerrainSceneManager会为地型本身创建MovableObject
//所以我们可能实际上会与他们相交。为了修正这个问题,我通过检查对象的名称来保证
//它们的名称不类似于地型名称。一个典型的地形名称比如"tile[0][0,2]"。
//最后,注意这个break语句。我们只需要在第一个物体上做操作,一旦我们找到一个合法的,
//我们就应该跳出循环。
} // if
else if (itr->worldFragment)
{
Entity *ent;
char name[16];
if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
break;
} // else if
} // for
运行你的程序可以实现选中了