区分box2d中的碰撞表面方向 [英] Distinguish between collision surface orientations in box2d

查看:500
本文介绍了区分box2d中的碰撞表面方向的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用Cocos2D 1.0和Box2D的iOS项目,我遇到了一个问题。



我需要能够做的是确定我的玩家击中的表面的方向。例如,如果我们有一个矩形平台,并且玩家碰到它,我需要知道玩家是否击中它的左,右,顶部或底部。所有在游戏中的对象是正方形,只有一个移动是播放器。



我目前正在使用b2ContactListener在Box2D(好,我自己的子类的一,反正),并且已经在与BeginContact中的联系人的地方正常的歧管玩耍。我的主要问题是,正常似乎受到玩家身体的旋转的影响(例如玩家旋转了90度,或者玩家在激烈的旋转 - 这两种情况都给我麻烦),我似乎最终与不明确(即与不同的面孔,给予相同的正常...)如果我试图允许 - 尽管我当然可以只是做一些可怕的错误。现在,我不明白多方面的问题,所以可能是我的问题源于这一点,或者我可能缺少一些明显的东西。



任何建议?
我宁愿这样做在最干净,最丑的方式可能。请记住,我关心的主要分类是玩家正在从上面的东西vs其他,但我可能最终需要确切的
如果你需要更多的信息或澄清什么,只是问。



编辑:为了说明,我知道在Box2D中的约定,从A到B的正常点(在A和B之间的碰撞中)

解决方案

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



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



世界歧管帐户对象变换和大小,因此包含更容易应用于世界坐标系统的数据。



按照惯例,在Box2d中,碰撞法线和接触歧管)指向从A到B - 这在一些用途中必须考虑,因为从A到B的法线是从B到A的法线的倒数,所以你不能假设一个物体将总是A。



因此,解决方案是使用获得世界歧管每个碰撞,检查其正常,然后做出任何你想做的决定。 / p>

例如,在b2ContactListener子类的 BeginContact 方法中(如果你不知道我在说什么然后查看第2部分 tutorial ):

  void ContactListener :: BeginContact b2Contact * contact)
{
b2WorldManifold worldManifold;
contact-> GetWorldManifold(& worldManifold); //这个方法调用b2WorldManifold :: Initialize与适当的变换和半径,所以你不必担心那
b2Vec2 worldNormal = worldManifold.normal;
//根据...检查它或做任何你想要的...
}

因为你可能需要检查正在碰撞的物体,哪个是A,哪个是B,你可能需要保留一个包含碰撞的fixture的结构向量(如教程)和正常,并在 tick()方法或类似的向量中重复。 (您可以从与 contact-> GetFixtureA() contact-> GetFixtureB()。)



现在,您可以从世界上获取点数据,并根据这些数据做出决定,当正常情况已经可用时,因为在这种特定情况下,正常(结合形成正常点和到)的所有需要​​。






编辑(适用于@iBradApps):



这里你遵循我链接到的教程并设置了联系人监听器。



其次,我想指出,没有绝对保证哪个对象是A,这是B(嗯,这取决于什么样的Box2D对象,他们是足够的说,如果他们都可以移动,你不能保证排序,至少就我所知),所以在我的情况下,我想要看看玩家对象是否命中了某个东西,所以我在我的联系人侦听器中创建了一个类变量( b2Fixture * playerF ),存储了对玩家对象的引用,因此我可以确定



您询问是否发现其他东西与B的顶部碰撞的碰撞事件。虽然我收藏了下面的内容,没有机会为您测试:



在您的ContactListener.h中:

  public:
b2Fixture * playerF;
//和Ray的教程中提到的向量等
//和任何你想要的东西

当你在 init()(假设你调用 _contactListener )时创建ContactListener: / p>

  _contactListener-> playerF = playerFixture; //或任何你叫的播放器主体夹具

BeginContact 方法:

  void ContactListener :: BeginContact(b2Contact * contact)
{
b2WorldManifold worldManifold ;
contact-> GetWorldManifold(& worldManifold); //这个方法调用b2WorldManifold :: Initialize与适当的变换和半径,所以你不必担心那
b2Vec2 worldNormal = worldManifold.normal; // this points from A to B

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

//注意+ ve y轴是up 在Box2D中,但是在OpenGL和Cocos2D
if(worldNormal.y< -0.707){//由于性能原因使用一个常数

//如果y分量小于 - 1 / sqrt(2)(约-0.707),
//那么正常点比向下更多,因此A必须大致从上面击中B
//。你可以通过增加
//向-1调整这个更加向上,如果你想,但它工作正常为我喜欢这最后一次和
//你可能遇到的问题与缺少的命中


NSLog(@玩家(A)大致在顶端击中B);

//这里你可以设置你想检查的任何类变量
//你的update()/ tick(),例如玩家是否从
//下降或任何
}

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

if(worldNormal.y> 0.707){

NSLog(@Player(B)命中A大致在顶部!
}

} else {
//它是别的东西,我们不关心它
}
}

至于在 tick()是的你可以。我实际上在联系人侦听器中的 PostSolve 中做了我所有的东西,因为我需要知道玩家击中的难度,但我所关心的是,无论玩家是否足够杀死他们,所以我不需要或不想迭代所有的联系人在我的 tick() - 我只是在联系人侦听器中设置一个标志说,



如果你想在update方法中这样做,那么从Ray开始,添加一个 b2Vec2 到MyContact结构,并在 BeginContact 中,添加两个灯具(如Ray)和

修改 MyContact struct:

  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(@玩家(A)大致在顶端击中B!
} else if(_playerFixture == contact.fixtureB&&& contact.normal.y> 0.707){
NSLog(@Player(B)大致在顶端击中A ;
} else {
//它是别的东西,我们不关心它
}
}


I've been working on an iOS project, using Cocos2D 1.0 and Box2D, and I've run into a bit of a problem.

What I need to be able to do is determine the orientation of a surface my player has hit. For example, if we have a rectangular platform, and the player collides with it, I need to know whether the player has hit the left, right, top, or bottom face of it. ALL the objects in the game are square, and the ONLY one moving is the player.

I'm currently using a b2ContactListener in Box2D (well, my own subclass of one, anyway), and have been playing around with the local normal of the manifold from the contact in BeginContact. The main problem I have is that that normal seems to be affected by the rotation of the player body (e.g. the player has rotated 90 degrees, OR the player is spinning wildly on impact - both situations are giving me trouble), and I seem to end up with ambiguity (i.e. collisions with different faces that give the same normal...) if I try to allow for that - although of course I could just be doing something horribly wrong. Now, I don't understand manifolds very well, so it's possible that my problem stems from that, or maybe I'm missing something obvious.

Any suggestions? I would prefer to do this in the cleanest and least ugly manner possible. Bear in mind that the main categorisation I care about is "player is landing on something from above" vs "everything else", but I may end up needing the exact If you need more information or clarification about anything, just ask.

EDIT: Just to clarify, I am aware that the normal points from A to B (in a collision between A and B) by convention in Box2D, and my code does check to see which one is the player and takes this into account before doing any calculations to determine which face has been hit.

解决方案

So, I feel a little awkward about answering my own question, but apparently it's officially encouraged.

Anyway, the problem with the way I was approaching things was twofold. Firstly, I was using the contact manifold's local normal instead of the world normal. Secondly, my code for reversing the object transformations was buggy (I would never have needed to do this if I had been using the world manifold).

The world manifold takes into account object transformations and sizes and as such contains data more easily applicable to the world co-ordinate system.

By convention in Box2d, the collision normal (for both the world manifold and the contact manifold) points from A to B - this has to be taken into account for some uses, since the normal from A to B is the inverse of the normal from B to A, so you can't just assume that one body will always be A.

So, the solution is to use get the world manifold for each collision, examine its normal, and then make whatever decisions you want to make.

For example, in the BeginContact method of a b2ContactListener subclass (if you have no idea what I'm talking about then check out part 2 of this tutorial):

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...  
}

Since you'll likely need to check what bodies are colliding, and which one is A and which one is B, you may want to keep a vector of structs containing the fixtures that collided (as in that tutorial) and the normal, and iterate over the vector in your tick() method or similar. (You can get these out of the contact with contact->GetFixtureA() and contact->GetFixtureB().)

Now, you could get the point data from the world manifold, and make your decisions based on that, but why would you when the normal is already available, since in this particular case the normal (combined with which shapes the normal points from and to) is all that is needed.


Edit (for @iBradApps):

First, I'm assuming here that you have followed the tutorial I linked to and have a contact listener set up. If you haven't, follow it because Ray explains it in depth quite well.

Second, I want to point out that there is no absolute guarantee which object is A and which is B (well, it depends on what kind of Box2D objects they are; suffice to say if they can both move, you can't guarantee the ordering, at least as far as I know), so in my case I wanted to see if the player object had hit something, so I created a class variable (b2Fixture *playerF) in my contact listener that stored a reference to the player object so I could determine whether contact A or contact B was the player.

You asked about detecting a collision where something else collided with the top of B. Something like the following should work, although I haven't had a chance to test it for you:

In your ContactListener.h:

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

When you make the ContactListener in your init() (assuming you called it _contactListener):

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

BeginContact method:

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
    }
}

As for doing it in your tick() method instead, yes, you can. I actually did all my stuff in PostSolve in the contact listener because I needed to know how hard the player hit, but all I cared about beyond that was whether the player had hit hard enough to kill them, so I didn't need or want to iterate over all the contacts in my tick() - I just set a flag in the contact listener that said the player had suffered a fatal impact.

If you want to do this all in the update method, then starting from what Ray has, add a b2Vec2 to the MyContact struct, and in BeginContact, add both the two fixtures (like Ray does) and get the collision normal (as I do) and add it too.

The modified MyContact struct:

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

The new BeginContact method:

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

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

This will give you all the information you need to do the checking I initially described in your tick().


Edit again: Your tick() method might contain something like this if you want to do the processing there, assuming you have called the player fixture (or ball fixture, like in the tutorial, or whatever it is you're interested in) _playerFixture, that you've got a contact listener with the same name as in the tutorial, that you added the b2Vec2 normal to the MyContact struct, that you are adding contacts to the vector (as above) in BeginContact, and that you are deleting contacts from the vector in the EndContact (as shown in the tutorial - it's probably fine as is):

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
    }
}

这篇关于区分box2d中的碰撞表面方向的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆