目录

我的学习分享

记录精彩的程序人生

【Box2D】20.Box2D C++ 教程-传感器

https://blog.csdn.net/Const_Gong/article/details/51464593

声明:本教程翻译自:Box2D C++ tutorials-Sensors ,仅供学习参考。

  • 传感器(Sensors)

在上一个话题中,我们看到,可以通过一些设置防止两个定制器发生碰撞。它们肆无忌惮的互相穿透,好像压根儿就没有看到对方,即便它们在穿透的过程中停止运动,也会保持着重叠的状态。也许这可能就是你想要的特性,但这么做有一个缺陷:既然互相之间没有碰撞,那么我们也就永远都不会获得BeginContact/EndContact信息。

如果我们让两个实体不发生碰撞,就像上一个话题例子中的船和飞机一样,但是我们依然想知道它们之间什么时候发生重叠。比如说,当一架敌军飞机飞到我方友军船只头顶的时候投下一枚炸弹,或者是其它类似的游戏逻辑。如果简单的关闭船和飞机之间的碰撞特性,那么我们就得不到重叠的响应事件,不过我们还有另外一个办法。

当你创建定制器(fixture)的时候,可以设置成员变量isSensor为true,设置此定制器为’传感器(sensor)’的一员,或者如果定制器已经创建了,在运行过程中,也可以根据需要通过调用SetSensor(bool)方法,将其设置成传感器(sensor)。传感器(sensor)的特点有点类似于把遮罩标志位(maskBits)设置成零-它们之间永远都不会发生碰撞。但是当两个定制器开始或者结束碰撞时,依然会触发相关事件,对BeginContact/EndContact进行方法回调。

所有传感定制器(sensor fixtures)的其它特性依然和普通定制器一样。它们依然可以添加到任何类型的物体上。传感器自身的质量依然会添加到所附加的物体上。记住!你可以为同一个物体同时附加传感器和定制器,依此可以延伸出许多其它方面的效果。这里有几个小想法:

  • 可以检测某个实体进入或离开某个活动区域
  • 触发开关事件
  • 检测玩家脚下的特性
  • 某个实体的可见区域

至于代码方面,只需要设置定制器的isSensor属性为true就可以了。呃,如果话题就停留在这儿,似乎这次话题的篇幅有点太短了,:)。让我们接着上一个话题中所做的例子,在testbed框架下做一个简单的范例。还是以从上到下(top-down)竖屏战斗场景为基础,我们会使用传感器的特性来表现我们所看到的实体,我们还会把最近几次话题中所讲到的知识点逐步融入近来-用户数据(User data)碰撞过滤(Collision filtering)以及回调(Collision callback)

在此实例中,场景中会有一个友军的船只以及一些敌军的飞机,我们通过改变它们的颜色来检查代码是否在正确的工作。我们也会进一步跟踪每一个友军可以看到的敌军的动向。这样我们就可以在安全范围内高效的查看敌军,这就类似于AI一样,是一个非常实用的功能,类似于其它类型的游戏逻辑。

我的狗身上有只跳骚。哈哈…只是想测试你一下脑子是否还处于清醒,毕竟到现在为止一直在罗列文字,没有代码,没有截图。貌似讲了很多枯燥的东西,让我们来点新鲜的吧。

  • 用法示例(Example usage)

既然目前这个场景和上一个话题中所举例子的场景非常相像,那我们就以上一个例子为起点。记住大圈代表船只,小圈代表飞机。这次我们将只创建一艘船作为友军,创建三架飞机作为敌军。既然我们创建了一对新实体,那么我们需要为碰撞过滤创建一些新的标志位来进行分类:

  enum _entityCategory {
    BOUNDARY =          0x0001,    // 边界
    FRIENDLY_SHIP =     0x0002,    // 友军船只
    ENEMY_SHIP =        0x0004,    // 敌军船只
    FRIENDLY_AIRCRAFT = 0x0008,    // 友军飞机
    ENEMY_AIRCRAFT =    0x0010,    // 敌军飞机
    FRIENDLY_TOWER =    0x0020,    // 友军灯塔
    RADAR_SENSOR =      0x0040,    // 雷达传感器
  };

像这样在FooTest类型的构造函数中对实体进行设置…

  // one friendly ship
  Ball* ship = new Ball(m_world, 3, green, FRIENDLY_SHIP, BOUNDARY | FRIENDLY_TOWER );
  balls.push_back( ship );
  
  // three enemy aircraft
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 1, red, ENEMY_AIRCRAFT, BOUNDARY | RADAR_SENSOR ) );
  
  // a tower entity
  Ball* tower = new Ball(m_world, 1, green, FRIENDLY_TOWER, FRIENDLY_SHIP );
  tower->m_body->SetType(b2_kinematicBody);
  balls.push_back( tower );

创建船只和飞机这几行代码,我想你应该可以看明白了,创建灯塔(tower)这部分代码,除了不想把它设置成动态物体以外,几乎和创建船和飞机是一样的。我们并没有在Ball类型的构造函数中添加参数,而是简单的只对参数做了改变。那么为什么不把灯塔(tower)设置成静态物体的呢?我们完全可以那么做,但是如果真的设置成静态物体,那么灯塔就不能做旋转。记住,我们可是希望它成为一个可旋转的雷达。看起来应该是这个样子

sensorsinitial.png

现在让我们创建一些传感器(sensors),紧接着上面的代码,像下面这样,我们为船创建传感器:

  // add radar sensor to ship
  b2CircleShape circleShape;
  circleShape.m_radius = 8;
  myFixtureDef.shape = &circleShape;
  myFixtureDef.isSensor = true;
  myFixtureDef.filter.categoryBits = RADAR_SENSOR;
  myFixtureDef.filter.maskBits = ENEMY_AIRCRAFT;// radar only collides with aircraft
  ship->m_body->CreateFixture(&myFixtureDef);

sensorsshipradar.png

灯塔会拥有半圈的传感器,并且其角速度不为零:

  // add semicircle radar sensor to tower
  float radius = 8;
  b2Vec2 vertices[8];
  vertices[0].Set(0,0);
  for (int i = 0; i < 7; i++) {
      float angle = i / 6.0 * 90 * DEGTORAD;
      vertices[i+1].Set( radius * cosf(angle), radius * sinf(angle) );
  }
  polygonShape.Set(vertices, 8);
  myFixtureDef.shape = &polygonShape;
  tower->m_body->CreateFixture(&myFixtureDef);
  
  // make the tower rotate at 45 degrees per second
  tower->m_body->SetAngularVelocity(45 * DEGTORAD);

sensorstowerradar.png

现在只剩下碰撞回调的实现。记住,我们想把当前友军能够看到的敌军飞机都存储到一个链表当中。那就很显然,我们需要有一个链表,当Box2D告诉我们有碰撞发生的时候,我们可以方便的获取相应的方法来进行操作。

  // Ball class member
  std::vector<Ball*> visibleEnemies;
  
  // in Ball class
  void radarAcquiredEnemy(Ball* enemy) {
    visibleEnemies.push_back( enemy );
  }
  void radarLostEnemy(Ball* enemy) {
    visibleEnemies.erase( std::find(visibleEnemies.begin(), visibleEnemies.end(), enemy ) );
  }
  
  // in Ball::render  
  if ( visibleEnemies.size() > 0 )
      glColor3f(1,1,0); //yellow
  else
      glColor3f(m_color.r, m_color.g, m_color.b);

现在感觉’Ball’类型的名字不太恰当了…或许一开始我们把它称为实体可能更好一点 :)

  // helper function to figure out if the collision was between
  // a radar and an aircraft, and sort out which is which
  bool getRadarAndAircraft(b2Contact* contact, Ball*& radarEntity, Ball*& aircraftEntity)
  {
      b2Fixture* fixtureA = contact->GetFixtureA();
      b2Fixture* fixtureB = contact->GetFixtureB();
  
      // make sure only one of the fixtures was a sensor
      bool sensorA = fixtureA->IsSensor();
      bool sensorB = fixtureB->IsSensor();
      if ( ! (sensorA ^ sensorB) )
          return false;
  
      Ball* entityA = static_cast<Ball*>( fixtureA->GetBody()->GetUserData() );
      Ball* entityB = static_cast<Ball*>( fixtureB->GetBody()->GetUserData() );
  
      if ( sensorA ) { // fixtureB must be an enemy aircraft
          radarEntity = entityA;
          aircraftEntity = entityB;
      }
      else { // fixtureA must be an enemy aircraft
          radarEntity = entityB;
          aircraftEntity = entityA;
      }
      return true;
  }
  
  // main collision call back function
  class MyContactListener : public b2ContactListener
  {
    void BeginContact(b2Contact* contact) {
        Ball* radarEntity;
        Ball* aircraftEntity;
        if ( getRadarAndAircraft(contact, radarEntity, aircraftEntity) )
            radarEntity->radarAcquiredEnemy( aircraftEntity );
    }
  
    void EndContact(b2Contact* contact) {
        Ball* radarEntity;
        Ball* aircraftEntity;
        if ( getRadarAndAircraft(contact, radarEntity, aircraftEntity) )
            radarEntity->radarLostEnemy( aircraftEntity );
    }
  };

马上完工…还剩下一步,我们需要把物理实体的用户数据(user data)设置成物理实体自身。让物理引擎知道我们使用了哪个接触监听器(contact listener):

  // in Ball constructor
  m_body->SetUserData( this );
  
  // at global scope
  MyContactListener myContactListenerInstance;
  
  // in FooTest constructor
  m_world->SetContactListener(&myContactListenerInstance);

现在基本算是完工啦,现在只要飞机和雷达传感器发生接触的时候,你会看到船和灯塔会变成黄色。牛b!

sensorsenemydetected.png

作为最后一个小练习,别忘了我们希望能够为每一个友军实体更新其周围可见的敌军链表,以便让我们有效的进行调用。现在我们有了一种实现方式,然后实现自旋。然后在雷达和敌军之间的可见范围内连接一条虚线。

      // in Ball::renderAtBodyPosition
      b2Vec2 pos = m_body->GetPosition(); //(existing code)
      glColor3f(1,1,1);//white
      glLineStipple( 1, 0xF0F0 ); //evenly dashed line
      glEnable(GL_LINE_STIPPLE); 
      glBegin(GL_LINES);
      for (int i = 0; i < visibleEnemies.size(); i++) {
          b2Vec2 enemyPosition = visibleEnemies[i]->m_body->GetPosition();
          glVertex2f(pos.x, pos.y);
          glVertex2f(enemyPosition.x, enemyPosition.y);
      }
      glEnd();
      glDisable(GL_LINE_STIPPLE);

sensorsenemylocated.png


  1. Box2D C++ 教程-简介
  2. Box2D C++ 教程-环境设置
  3. Box2D C++ 教程-Testbed结构
  4. Box2D C++ 教程-创建测试
  5. Box2D C++ 教程-物体
  6. Box2D C++ 教程-定制器
  7. Box2D C++ 教程-设置世界
  8. Box2D C++ 教程-力和冲量
  9. Box2D C++ 教程-自定义重力
  10. Box2D C++ 教程-匀速运动
  11. Box2D C++ 教程-旋转到指定角度
  12. Box2D C++ 教程-跳跃
  13. Box2D C++ 教程-使用debug Draw
  14. Box2D C++ 教程-画自己的图像
  15. Box2D C++ 教程-用户数据
  16. Box2D C++ 教程-碰撞剖析
  17. Box2D C++ 教程-源代码
  18. Box2D C++ 教程-碰撞回调
  19. Box2D C++ 教程-碰撞过滤
  20. Box2D C++ 教程-传感器
  21. Box2D C++ 教程-射线投射
  22. Box2D C++ 教程-查询 World
  23. Box2D C++ 教程-安全地移除物体
  24. Box2D C++ 教程-跳跃问题
  25. Box2D C++ 教程-幽灵顶点
  26. Box2D C++ 教程-连接器-概述
  27. Box2D C++ 教程-连接器-旋转
  28. Box2D C++ 教程-连接器-平移
  29. Box2D C++ 教程-开发环境设置(iPhone)