ogre鼠标点选

From OGRE 3D 中文

Jump to: navigation, search

目录

[ 编 辑]

Introduction

本课紧接着上一课,我们将介绍如何在屏幕里选择任意的物体,以及如何限制哪些是可选的。

你能在这里找到本课的代码。在你浏览这个Demo的时候,最好是逐个地往你自己的工 程里添加代码,并在编译后观察结果。

[ 编 辑]

先决条件

本课程认为你已经学习了前面的课程。我们将会使用STL的iterator来遍历SceneQueries的多个结果,所以关于它们的基本知识是有 帮助的。

 

[ 编 辑]

从这开始

尽管我们是在上一次的代码上进行编辑,但为了后面的教程更加可读,做了一些修改。对于所有的鼠标事件,我创建了封闭函数来处理它们。当用户按下鼠标 左键,"onLeftPressed"函数被调用,当按下右键时"onRightReleased"函数被调用,等等。在开始之前你应该花一些时间了解这 些改变。

创建一个new.cpp文件并添加到你的工程里,并加入下面的代码:

 #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;
}

/* MouseListener callbacks. */
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;
}

在你继续之前,请保证能正常编译并运行这些代码。尽管有所改动,但它的效果与上一课相同。

[ 编 辑]

标明选择的物体

在这一课里,我们能够做到当你放置物体后,能“拾取”并移动它。我们希望有一种途径,让用户知道她目前正在操纵哪一个物体。在游戏里,我们可能以某 种特殊的方式来高亮这个物体。而在这里(也可以在你的程序没有发布之前),你可以用showBoundingBox方法来创建一个围绕该物体的方盒。

我们的基本思想是,当鼠标首次按下时,取消旧的选择物体上的包围盒,然后当选择了一新物体时,给新物体加上包围盒。为此,我们在 onLeftPressed函数的开头添加如下代码:

 // 打开包围盒
if (mCurrentObject)
mCurrentObject->showBoundingBox(false);

然后在onLeftPressed函数的最末尾添加以下代码:

 // Show the bounding box to highlight the selected object
if (mCurrentObject)
mCurrentObject->showBoundingBox(true);

现在mCurrentObject总是在屏幕上高亮显示了。

[ 编 辑]

添加忍者

我们现在想要修改代码,使得不只支持机器人,而且还能够放置和移动忍者。我们需要一个“机器人模式”和一个“忍者模式”,来决定在屏幕上放置的物 体。我们把空格键设置成切换按钮,并且显示信息提示用户目前处于哪一种模式。

首先,我们把MouseQueryListener设置成机器人模式。我们添加一个变量来保存物体状态(即,我们放置的是机器人还是忍者)。找到 protected区域中的MouseQueryListener,并添加这个变量:

 bool mRobotMode; // 当前状态

然后,在MouseQueryListener构造器的末尾加上这些代码:

 // 设置文本、缺省状态
mRobotMode = true;
mDebugText = "Robot Mode Enabled - Press Space to Toggle";

这样我们处于忍者模式了!真有这么简单就好了。。我们还要基于mRobotMode变量来创建一个机器人mesh,或者一个忍者mesh。在 onLeftPressed里找到这段代码:

 char name[16];
sprintf(name, "Robot%d", mCount++);

Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");

用下面代码替换。意思很明显,依赖mRobotMode状态,我们添加机器人还是忍者,并取相应名称:

 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 (!ExampleFrameListener::frameStarted(evt))
return false;

在它后面加上这些代码:

 // 切换模式
if(mKeyboard->isKeyDown(OIS::KC_SPACE) && mTimeUntilNextToggle <= 0)
{
mRobotMode = !mRobotMode;
mTimeUntilNextToggle = 1;
mDebugText = (mRobotMode ? String("Robot") : String("Ninja")) + " Mode Enabled - Press Space to Toggle";
}

好了完成了!编译并运行这个Demo,你现在可以通过空格键来选择你要放置的物体。

[ 编 辑]

选择物体

现在我们进入本课最核心的部分:使用RaySceneQueries在屏幕上选取物体。在我们对代码进行修改之前,我想先详细介绍一下RaySceneQueryResultEntry。(请进入链接并浏览一下 这个结构体)

RaySceneQueryResult返回一个RaySceneQueryResultEntry结构体的iterator。这个结构体包含三个 变量。distance变量告诉你这个物体沿着射线有多远。另外两个变量的其中一个将是null。movable变量包含一个MovableObject对象,如果与射线相交的话。如果射线接触到一个地 形片段,worldFragment将保存这个WorldFragment对象(比如地形)。

MovableObject基本上可以是任何你能绑在SceneNode上的对象(像实体、光源,等)。 在这里查看继承树,看看将会返回什么类型的对象。大多数 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,用来报告交点的坐标。其它类型的world fragments超出了这课的范围。

下面我们来看一个例子,比如我们想要在RaySceneQuery之后打印一串返回结果。下面的代码做这些:(假设fout对象是ofstream 类型的,并且已经用open方法创建出来)

// Do not add this code to the program, just read along:

RaySceneQueryResult &result = mRaySceneQuery->execute(); RaySceneQueryResult::iterator itr;

 // 循环遍历所有结果
for ( itr = result.begin( ); itr != result.end(); itr++ )
{
// Is this result a WorldFragment?
if ( itr->worldFragment )
{
Vector3 location = itr->worldFragment->singleIntersection;
fout << "WorldFragment: (" << location.x << ", " << location.y << ", " << location.z << ")" << endl;
} // if

// Is this result a MovableObject?
else if ( itr->movable )
{
fout << "MovableObject: " << itr->movable->getName() << endl;
} // else if
} // for

这样就能把射线遇到的所有MovableObjects的名称打印出来,而且能够打印与世界几何相交的坐标(如果碰到的话)。注意,这可能会出现一 些奇怪的现象。比如,如果你正使用TerrainSceneManager,射线的发出点必须要在地型之上,否则相交查询不会把它注册为一个碰撞。不同的 场景管理器对RaySceneQueries有不同的实现。所以当你使用一个新的场景管理器,最好先试验一下。

现在,如果我们再看看我们的RaySceneQuery代码,你会发现:我们并不需要遍历所有的结果!实际上我们只需要查看第一个结果,即世界几何 (TerrainSceneManager的情况下)。但有点不妙,因为我们不能保证TerrainSceneManager总是最先返回世界几何。我们 需要循环所有的结果,以确保我们能找到我们想要的。我们要做的另一件事情是“拾起”并拖拽已经被放置的物体。当前你若点击一个已经放置的物体,程序会忽略 它,并在它后面放置另一个机器人。我们现在来修正它。

找到onLeftPressed函数。我们首先要保证当我们点击鼠标,我们能得到沿射线上的第一个东西。为此,我们需要设置 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语句:

 // Get results, create a node/entity on the position
for ( itr = result.begin(); itr != result.end(); itr++ )
{

首先我们要检查第一个交点的是不是一个MovableObject,如果是,我们把它的父节点赋给mCurrentObject。还要做另一个判 断,TerrainSceneManager会为地型本身创建MovableObject,所以我们可能实际上会与他们相交。为了修正这个问题,我通过检 查对象的名称来保证,它们的名称不类似于地型名称。一个典型的地形名称比如"tile[0][0,2]"。最后,注意这个break语句。我们只需要在第 一个物体上做操作,一旦我们找到一个合法的,我们就应该跳出循环。

 if (itr->movable && itr->movable->getName().substr(0, 5) != "tile[")
{
mCurrentObject = itr->movable->getParentSceneNode();
break;
} // if

下面,我们要检查交点是否返回了WorldFragment。

 else if (itr->worldFragment)
{
Entity *ent;
char name[16];

现在我们根据mRobotState来创建一个机器人实体或者一个忍者实体。创建实体之后,我们将创建场景节点,并跳出这个for循环。

 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

不管你信不信,以上就是全部要做的! 编译并玩转这个代码。现在我们点击地形的时候会创建正确类型的物体,当我们点击一个物体的时候,会看到一个包围盒(别拖拽它,这还是下一步)。一个有意思 的问题是,既然我们只需要第一个交点,而且我们已经按深度排序了,为什么不只用if语句呢?这主要是因为,如果第一个返回的物体是那些恼人的地砖,我们就 会得到一个错误。我们必须循环直到发现不是地砖的东西,或者走到了列表末尾。

现在我们步入主题了,我们还需要在别的地方更新RaySceneQuery代码。在frameStarted和onLeftDragged函数里, 我们只需要找到地形。因为地形总是在有序列表的最后面,所以没有必要对结果排序(所以我们想把排序关了)。但我们仍然想要循环遍历这些结果,只是为了防范 TerrainSceneManager日后某天可能会改变,而不首先返回地形。首先,在frameStarted里找到这段代码:

 // 进行场景查询
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();

// 获得结果,设置摄像机高度
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);
}

然后用这些代码替换:

 // 进行场景查询
mRaySceneQuery->setSortByDistance(false);
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr;

// 获得结果,设置摄像机高度
for (itr = result.begin(); itr != result.end(); itr++)
{
if (itr->worldFragment)
{
Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition(camPos.x, terrainHeight + 10.0f, camPos.z);
break;
} // if
} // for

不言而喻,我们添加了一行关闭了排序,然后把if语句转换成for循环,一旦我们找到我们想要的位置就跳出。在mouseMoved函数里,我们也 是做同样的事情。在mouseMoved里找到这段代码:

 mRaySceneQuery->setRay(mouseRay);

RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();

if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);

然后用这些代码替换:

 mRaySceneQuery->setRay(mouseRay);
mRaySceneQuery->setSortByDistance(false);

RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr;

for (itr = result.begin(); itr != result.end(); itr++)
if (itr->worldFragment)
{
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
break;
} // if

编译并测试代码。这应该不会与我们上次运行代码时有太大出入,但我们更正确地处理了它。以后TerrainSceneManager的任何更新都不 会影响到我们的代码。

[ 编 辑]

查询遮罩

注意,不论我们处于何种模式,我们总能选取任意的物体。我们的RaySceneQuery将会返回机器人或者忍者,只要它在最前面。但并不一定总是 要这样。所有的MovableObject允许你为它们设置一个遮罩,然后SceneQueries使你能够根据这个遮罩来过滤你的结果。所有的遮罩都是 通过二进制“AND”操作来实现的,所以如果你对它不熟悉,应该补充这方面知识再继续下去。

我们要做的第一件事就是创建这个遮罩值。找到MouseQueryListener类的最开始,将这些添加到public声明后面:

 enum QueryFlags
{
NINJA_MASK = 1<<0,
ROBOT_MASK = 1<<1
};

这样就创建了一个含有两个值的枚举类型,它们的二进制表示是0001和0010。现在,每当我们创建一个机器人实体,我们调用它 的"setMask"方法将这个查询标记设置为ROBOT_MASK。每当我们创建一个忍者实体时,我们调用它的"setMask"方法将这个查询标记设 置为NINJA_MASK。现在,当我们在忍者模式下,我们将使RaySceneQuery仅考虑带有NINJA_MASK标记的物体。而在机器人模式 里,我们将使它仅考虑ROBOT_MASK。

在onLeftPressed方法里找到这一段:

 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

我们将添加两行来为它们设置遮罩:

 if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
ent->setQueryFlags(ROBOT_MASK);
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
ent->setQueryFlags(NINJA_MASK);
} // else

我们还需要做到,当我们处于其之一种模式中时,我们只能点击和拖拽当前类型的物体。我们需要设置这个查询标记,使得只有正确的物体类型才被选择。为 此,在机器人模式中,我们设置RaySceneQuery的查询标记为ROBOT_MASK,而在忍者模式里设置为NINJA_MASK。在 onLeftPressed函数里,找到这个代码:

 mRaySceneQuery->setSortByDistance(true);

在这一行后面加上一行:

 mRaySceneQuery->setQueryMask(mRobotMode ? ROBOT_MASK : NINJA_MASK);

编译并运行这个教程。我们就只能选择我们所要的物体。所有的射线都穿过其它的物体,并碰撞到正确的物体。现在我们完成了代码,下一节将不会对它修 改。

[ 编 辑]

查询类型遮罩

当使用场景查询时还有一件事需要考虑。假设你在上面的场景里加入了一个公告板或者粒子系统,并且你想要移动它。我会发现查询不会返回你点击的公告 板。这是因为SceneQuery还存在另外一个遮罩,查询类型遮罩(QueryTypeMask),它限制了你只能选择这个标记指定的类型。默认情况 是,当你作一个查询时,它只返回实体类型的物体。

在你的代码里,如果想要查询返回公告板或者粒子系统,则你要在执行查询之前这么做:

mRaySceneQuery->setQueryTypeMask(SceneManager::FX_TYPE_MASK);

现在查询将只返回公告板或者粒子系统作为结果。

在SceneManager类里面,已经定义了6种类型的QueryTypeMask作为静态成员:

WORLD_GEOMETRY_TYPE_MASK // 返回世界几何
ENTITY_TYPE_MASK // 返回实体
FX_TYPE_MASK // 返回公告板/粒子系统
STATICGEOMETRY_TYPE_MASK // 返回静态几何
LIGHT_TYPE_MASK // 返回光源
USER_TYPE_MASK_LIMIT // 用户类型遮罩限制

没有手工设置这个属性时,QueryTypeMask的默认值是ENTITY_TYPE_MASK。

[ 编 辑]

关于遮罩更多内容

我们的遮罩例子非常简单,所以我想介绍一些更复杂的例子。

[ 编 辑]

设置MovableObject的遮罩

每当我们创建一个新的遮罩,它的二进制表示必须只包含一个“1”。即,这些是合法的遮罩:

00000001
00000010
00000100
00001000
00010000
00100000
01000000
10000000

等等。我们通过使用一个“1”并按位移动它,能够轻松地创建一个值。即:

00000001 = 1<<0
00000010 = 1<<1
00000100 = 1<<2
00001000 = 1<<3
00010000 = 1<<4
00100000 = 1<<5
01000000 = 1<<6
10000000 = 1<<7

直到1<<31。这样我们就有了32种不同的遮罩,可以用在MovableObject对象上。

[ 编 辑]

有多个遮罩的查询

我们能使用“OR”操作符,来为多个遮罩进行查询。比如说,在游戏里我们有三个不同组的对象:

enum QueryFlags
{
FRIENDLY_CHARACTERS = 1<<0,
ENEMY_CHARACTERS = 1<<1,
STATIONARY_OBJECTS = 1<<2
};

现在,如果我们只要查询friendly characters,可以这么做:

mRaySceneQuery->setQueryMask(FRIENDLY_CHARACTERS);

如果我们要同时查询enemy characters和stationary objects,可以这么做:

mRaySceneQuery->setQueryMask(ENEMY_CHARACTERS | STATIONARY_OBJECTS);

如果你有很多这一类的查询,你可以把它们定义到枚举类型里去:

OBJECTS_ENEMIES = ENEMY_CHARACTERS | STATIONARY_OBJECTS

然后只使用OBJECTS_ENEMIES作查询。

[ 编 辑]

查询遮罩以外的所有东西

你还可以使用按位反转操作,来查询除了遮罩的所有事物,就像这样:

mRaySceneQuery->setQueryMask(~FRIENDLY_CHARACTERS);

这样返回所有事物,除了friendly characters。对于多个遮罩,你也能这么做:

mRaySceneQuery->setQueryMask(~(FRIENDLY_CHARACTERS | STATIONARY_OBJECTS));

这样返回所有物体,除了friendly characters和stationary objects。

 

[ 编 辑]

选取所有物体或者不选取任何物体

你还以用遮罩干一些有趣的事情。请记住,如果你为场景查询把查询遮罩设置成QM,MovableObject的遮罩为OM,如果QM & OM 包含至少一个“1”,则表示符合。因此,为场景查询把查询遮罩设置成0,将会使它不返回任何MovableObject。把查询遮罩设置成1,则返回所有 查询遮罩不为0的MovableObject。

使用为0的查询遮罩有时是非常有用的。比如,当只需要返回worldFragment时,TerrainSceneManager不必使用 QueryMasks。可以这么做:

mRaySceneQuery->setQueryMask(0);

在你的场景管理器的RaySceneQueries里,你就只能得到worldFragment。如果你的屏幕里有很多物体,由于你只要地形的交点 而不想浪费时间循环遍历所有的东西,这样做是很有用的。

 --Xiao7cn 10:43 2008年3月1日 (CST)Xiao7cn(FriedChicken)翻译
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值