区分box2d中的碰撞表面方向

Isk*_*rak 1 collision-detection box2d cocos2d-iphone

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

我需要做的就是确定玩家击中的表面的方向。例如,如果我们有一个矩形平台,并且玩家与之碰撞,我需要知道玩家是否已击中它的左,右,上或下表面。游戏中的所有物体都是正方形的,只有一个动作是玩家。

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

有什么建议么?我宁愿以最干净,最丑陋的方式进行此操作。请记住,我关心的主要分类是“玩家从上面着陆”还是“其他”,但是我可能最终需要确切的信息。如果您需要更多信息或澄清,请问一下。

编辑:为澄清起见,我知道Box2D中按惯例从A到B的法线点(在A和B之间的冲突),我的代码确实检查了看哪一个是玩家,并在进行操作之前考虑了这一点任何计算以确定被击中的脸。

Isk*_*rak 5

因此,我对回答自己的问题感到有点尴尬,但显然它受到官方的鼓励。

无论如何,我对待事物的方式有两个问题。首先,我使用的是接触歧管的局部法线,而不是世界法线。其次,我用于反转对象转换的代码是错误的(如果我一直在使用世界流形,则永远不需要这样做)。

世界流形考虑了对象的变换和大小,因此包含了更容易应用于世界坐标系的数据。

按照Box2d中的约定,碰撞法线(对于世界歧管和接触歧管)都从A指向B-在某些用途中必须考虑到这一点,因为从A到B的法线是从A到B的法线的逆。 B到A,因此您不能仅仅假设一个身体永远是A。

因此,解决方案是对每次碰撞使用“获取世界”流形,检查其法线,然后做出想要做出的任何决定。

例如,在BeginContactb2ContactListener子类的方法中(如果您不知道我在说什么,请查看本教程的第2部分):

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...  
}
Run Code Online (Sandbox Code Playgroud)

由于您可能需要检查正在碰撞的物体,哪个物体是A以及哪个物体是B,因此您可能需要保留一个结构向量,其中包含发生碰撞的固定装置(如该教程中所示)和法线,并进行迭代在您的tick()方法或类似方法中的向量之上。(您可以通过contact->GetFixtureA()和取消联系contact->GetFixtureB()。)

现在,您可以从世界流形中获取点数据,并据此做出决策,但是为什么在法线已经可用的情况下您会为什么呢,因为在这种特殊情况下,法线(结合了从和来往于法线点的形状) )就是所需要的。


编辑(对于@iBradApps):

首先,我假设您已按照我链接到的教程进行操作,并设置了联系人侦听器。如果您还没有这样做,请遵循它,因为Ray很好地解释了它。

其次,我想指出的是,不能绝对保证哪个对象是A和哪个对象是B(嗯,这取决于它们是哪种Box2D对象;足以说明它们是否都可以移动,不能保证排序,至少据我所知),因此在我的情况下,我想查看播放器对象是否碰到了东西,因此我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
Run Code Online (Sandbox Code Playgroud)

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

_contactListener->playerF = playerFixture; // or whatever you called the player body fixture
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

至于在您的tick()方法中执行此操作,可以的。实际上,我是PostSolve在联系侦听器中完成所有工作的,因为我需要知道播放器受到的打击程度,但除此之外,我关心的只是播放器是否受到足够的打击以杀死它们,所以我不需要或不想遍历我所有的联系人tick()-我只是在联系人侦听器中设置了一个标志,说播放器遭受了致命的伤害。

如果要在update方法中完成所有操作,则从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);
    }
};
Run Code Online (Sandbox Code Playgroud)

BeginContact方法:

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

    MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB(), worldManifold.normal };
    _contacts.push_back(myContact);
}
Run Code Online (Sandbox Code Playgroud)

这将为您提供进行检查时所需要的所有信息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
    }
}
Run Code Online (Sandbox Code Playgroud)