Oh!Coder

Coding Life

Box2D C++ 教程-传感器

| Comments

声明:本教程翻译自: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)

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

1
2
3
4
5
6
7
8
9
enum _entityCategory {
    BOUNDARY =          0x0001,
    FRIENDLY_SHIP =     0x0002,
    ENEMY_SHIP =        0x0004,
    FRIENDLY_AIRCRAFT = 0x0008,
    ENEMY_AIRCRAFT =    0x0010,
    FRIENDLY_TOWER =    0x0020,
    RADAR_SENSOR =      0x0040,
};

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

1
2
3
4
5
6
7
8
9
10
//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)设置成静态物体的呢?我们完全可以那么做,但是如果真的设置成静态物体,那么灯塔就不能做旋转。记住,我们可是希望它成为一个可旋转的雷达。看起来应该是这个样子:

pic

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

1
2
3
4
5
6
7
8
//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);

pic

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

1
2
3
4
5
6
7
8
9
10
11
12
13
//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);

pic

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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’类型的名字不太恰当了…或许一开始我们把它称为实体可能更好一点 :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//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):

1
2
3
4
5
6
//in Ball constructor
m_body->SetUserData( this );
//at global scope
MyContactListener myContactListenerInstance;
//in FooTest constructor
m_world->SetContactListener(&myContactListenerInstance);

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

pig

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

1
2
3
4
5
6
7
8
9
10
11
12
13
/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);

pic

Comments