区分box2d中的碰撞表面方向

时间:2011-09-18 02:56:49

标签: cocos2d-iphone collision-detection box2d

我一直在使用Cocos2D 1.0和Box2D开发iOS项目,但我遇到了一些问题。

我需要做的是确定我的玩家所遇到的表面的方向。例如,如果我们有一个矩形平台,并且玩家与它发生碰撞,我需要知道玩家是否击中了它的左,右,顶或底面。游戏中的所有物体都是方形的,唯一一个移动的是玩家。

我目前正在Box2D中使用b2ContactListener(好吧,我自己的一个子类,无论如何),并且一直在玩BeginContact中联系人的流形的局部法线。我遇到的主要问题是,正常情况似乎受到玩家身体旋转的影响(例如,玩家旋转了90度,或者玩家在撞击时疯狂旋转 - 两种情况都给我带来麻烦),而我似乎如果我试图允许这种情况,最终会产生歧义(即与不同面孔碰撞,给出相同的正常...),尽管我当然可能只是做了一些可怕的错误。现在,我不太了解歧管,所以我的问题可能源于此,或者我可能遗漏了一些明显的东西。

有什么建议吗? 我更愿意以最干净,最丑陋的方式做到这一点。请记住,我关心的主要分类是“玩家从上面登陆某些东西”与“其他所有东西”,但我可能最终需要确切的 如果您需要更多信息或澄清任何事情,请询问。

编辑:只是为了澄清,我知道在Box2D中按照惯例从A到B(在A和B之间的碰撞中)的正常点,我的代码确实检查哪一个是玩家并将其带入帐户在进行任何计算之前确定哪个面被击中。

1 个答案:

答案 0 :(得分:4)

所以,我对回答我自己的问题感到有点尴尬,但显然它是正式鼓励的。

无论如何,我接近事物的方式的问题是双重的。首先,我使用接触歧管的局部正常而不是世界正常。其次,我的用于反转对象转换的代码是错误的(如果我使用世界流形,我将永远不需要这样做。)

世界多样性考虑了对象的变换和大小,因此包含更容易适用于世界协调系统的数据。

按照惯例,在Box2d中,碰撞法线(对于世界流形和接触流形)指向从A到B - 这在某些用途中必须考虑到,因为从A到B的法线是从B到A的法线,所以你不能只假设一个身体永远是A。

因此,解决方案是使用每次碰撞获取世界流形,检查其正常情况,然后做出你想做出的任何决定。

例如,在b2ContactListener子类的BeginContact方法中(如果你不知道我在说什么,那么请查看part 2tutorial):

void ContactListener::BeginContact(b2Contact* contact)
{
    b2WorldManifold worldManifold;
    contact->GetWorldManifold(&worldManifold); // this method calls b2WorldManifold::Initialize with the appropriate transforms and radii so you don't have to worry about that
    b2Vec2 worldNormal = worldManifold.normal;
    // inspect it or do whatever you want based on that...  
}

由于您可能需要检查哪些物体碰撞,哪一个物体是A,哪一个物体是B,您可能需要保留包含碰撞的灯具的结构矢量(如tutorial )和法线,并在tick()方法或类似方法中迭代向量。 (您可以通过contact->GetFixtureA()contact->GetFixtureB()与<{1}}联系。

现在,你可以从世界各个方面获取点数据,并根据它做出你的决定,但是为什么你的法线已经可用,因为在这个特别< / em> case正常(与正常点之间的形状相结合)就是所需要的。


编辑(适用于@iBradApps):

首先,我假设您已经按照我链接的教程进行了设置并设置了联系人监听器。如果你还没有,请关注它,因为Ray深入地解释了它。

其次,我想指出,没有绝对保证哪个对象是A,哪个是B(嗯,这取决于它们是什么类型的Box2D对象;足以说明它们是否可以移动,你可以' t保证顺序,至少据我所知,所以在我的情况下我想看看玩家对象是否有什么东西,所以我在我的联系人监听器中创建了一个类变量(b2Fixture *playerF),存储了一个参考玩家对象,以便我可以确定联系人A或联系人B是否是玩家。

你问过检测碰撞哪里有其他东西与B顶部发生碰撞。以下情况应该有效,尽管我没有机会为你测试:

在您的ContactListener.h中:

public:
    b2Fixture *playerF;
    // along with the vector etc mentioned in Ray's tutorial
    // and anything else you want

当您在init()中创建ContactListener时(假设您将其称为_contactListener):

_contactListener->playerF = playerFixture; // or whatever you called the player body fixture

BeginContact方法:

void ContactListener::BeginContact(b2Contact* contact)
{
    b2WorldManifold worldManifold;
    contact->GetWorldManifold(&worldManifold); // this method calls b2WorldManifold::Initialize with the appropriate transforms and radii so you don't have to worry about that
    b2Vec2 worldNormal = worldManifold.normal; // this points from A to B

    if (playerF == contact->GetFixtureA()) {

        // note that +ve y-axis is "up" in Box2D but down in OpenGL and Cocos2D
        if (worldNormal.y < -0.707) { // use a constant for performance reasons

            // if the y component is less than -1/sqrt(2) (approximately -0.707),
            // then the normal points more downwards than across, so A must be hitting B 
            // from roughly above. You could tune this more towards the top by increasing 
            // towards -1 if you want but it worked fine for me like this last time and 
            // you might run into issues with missing hits


            NSLog(@"Player (A) hit B roughly on the top side!");

            // here you can set any class variables you want to check in 
            // your update()/tick(), such as flags for whether the player has died from
            // falling or whatever
    }

    } else if (playerF == contact->GetFixtureB()) {

        if (worldNormal.y > 0.707) {

            NSLog(@"Player (B) hit A roughly on the top side!");
        }

    } else {
    // it's something else hitting something else and we don't care about it
    }
}

至于在你的tick()方法中这样做,是的,你可以。我实际上在联系人监听器PostSolve中完成了我所有的东西,因为我需要知道玩家有多难,但我所关心的只是玩家是否已经足够重击以杀死他们,所以我没有'我需要或想要迭代我tick()中的所有联系人 - 我只是在联系人监听器中设置了一个标志,表示该玩家遭受了致命的影响。

如果你想在更新方法中完成所有操作,那么从Ray开始,将b2Vec2添加到MyContact结构中,并在BeginContact中添加两个灯具(如Ray)确实)使碰撞正常(就像我一样)并添加它。

修改后的MyContact结构:

struct MyContact {
    b2Fixture *fixtureA;
    b2Fixture *fixtureB;
    b2Vec2 normal;
    bool operator==(const MyContact& other) const
    {
        return (fixtureA == other.fixtureA) && (fixtureB == other.fixtureB);
    }
};

新的BeginContact方法:

void MyContactListener::BeginContact(b2Contact* contact) {
    b2WorldManifold wordManifold;
    contact->GetWorldManifold(&worldManifold);

    MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB(), worldManifold.normal };
    _contacts.push_back(myContact);
}

这将为您提供我在tick()中最初描述的检查所需的所有信息。


再次修改 如果你想在那里进行处理,你的tick()方法可能包含这样的方法,假设你已经调用了玩家装备(或者球装置,就像在教程中,或者你感兴趣的任何东西)_playerFixture,您有一个与教程中名称相同的联系人侦听器,您已将b2Vec2法线添加到MyContact结构中,您要将联系人添加到BeginContact中的向量(如上所示),并且您要从中删除联系人EndContact中的向量(如教程中所示 - 它可能很好):

std::vector<MyContact>::iterator pos;
for(pos = _contactListener->_contacts.begin(); pos != _contactListener->_contacts.end(); ++pos) {
    MyContact contact = *pos;
    if (_playerFixture == contact.fixtureA && contact.normal.y < -0.707) {
            NSLog(@"Player (A) hit B roughly on the top side!");
    } else if (_playerFixture == contact.fixtureB && contact.normal.y > 0.707) {
            NSLog(@"Player (B) hit A roughly on the top side!");
    } else {
    // it's something else hitting something else and we don't care about it
    }
}