文档:教程:中级教程:中级教程三
出自Ogre3D开放资源地带
目录
|
[编辑]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。如果你的屏幕里有很多物体,由于你只要地形的交点而不想浪费时间循环遍历所有的东西,这样做是很有用的。