遮罩 ogre

文档:教程:中级教程:中级教程三

出自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。如果你的屏幕里有很多物体,由于你只要地形的交点而不想浪费时间循环遍历所有的东西,这样做是很有用的。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值