Различают ориентации поверхности столкновения в box2d - PullRequest
1 голос
/ 18 сентября 2011

Я работал над проектом iOS, используя Cocos2D 1.0 и Box2D, и столкнулся с небольшой проблемой.

Что мне нужно сделать, это определить ориентациюповерхность, на которую попал мой игрок.Например, если у нас есть прямоугольная платформа, и игрок сталкивается с ней, мне нужно знать, ударил ли игрок по левой, правой, верхней или нижней стороне.ВСЕ объекты в игре квадратные, и ЕДИНСТВЕННЫМ движением является игрок.

В настоящее время я использую b2ContactListener в Box2D (ну, во всяком случае, мой собственный подкласс из одного), и играю вокругс локальной нормалью многообразия от контакта в BeginContact.Основная проблема, с которой я столкнулся, заключается в том, что на нормальное явление, похоже, влияет поворот тела игрока (например, игрок повернулся на 90 градусов, ИЛИ игрок сильно крутится при ударе - обе ситуации создают мне проблемы), и я, кажется,в конечном итоге получится двусмысленность (то есть столкновения с разными лицами, которые дают одинаковую норму ...), если я попытаюсь это допустить - хотя, конечно, я могу просто делать что-то ужасно неправильное.Теперь я не очень хорошо понимаю коллекторы, поэтому возможно, что моя проблема проистекает из этого, или, может быть, я упускаю что-то очевидное.

Есть предложения?Я бы предпочел сделать это самым чистым и наименее уродливым способом.Имейте в виду, что основная категория, которая меня волнует, это «игрок приземляется на что-то сверху» против «всего остального», но мне может понадобиться точная информация. Если вам нужна дополнительная информация или разъяснения по чему-либо, просто спросите.

РЕДАКТИРОВАТЬ: Просто чтобы уточнить, я знаю, что нормальные точки от A до B (при столкновении между A и B) по соглашению в Box2D, и мой код проверяет, кто из них является игроком, и принимает это вПрежде чем делать какие-либо расчеты, определите, какое лицо было поражено.

1 Ответ

4 голосов
/ 19 сентября 2011

Итак, я чувствую себя немного неловко, отвечая на свой вопрос, но, очевидно, это официально приветствуется.

Так или иначе, проблема с тем, как я подходил к вещам, была двоякой. Во-первых, я использовал локальную нормаль контактного коллектора вместо нормальной мира. Во-вторых, мой код для реверсирования объектных преобразований был ошибочным (мне никогда бы не пришлось делать это, если бы я использовал мировой многообразие).

Мировое многообразие учитывает преобразования и размеры объектов и, как таковое, содержит данные, более легко применимые к мировой системе координат.

По соглашению в Box2d нормаль столкновения (как для мирового, так и для контактного многообразий) указывает от A до B - это необходимо учитывать для некоторых применений, поскольку нормаль от A к B является обратной к нормаль от B до A, так что вы не можете просто предположить, что одно тело всегда будет A.

Итак, решение состоит в том, чтобы использовать для каждого столкновения многообразие мира, проверять его нормальность и затем принимать любые решения, которые вы хотите принять.

Например, в методе BeginContact подкласса b2ContactListener (если вы не знаете, о чем я говорю, посмотрите part 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...  
}

Поскольку вам, вероятно, потребуется проверить, какие тела сталкиваются, а какое - A, а какое - B, вы можете захотеть сохранить вектор структур, содержащих столкнувшиеся приборы (как в этом уроке ) 1020 *) и нормаль, итерируйте вектор в вашем методе tick() или подобном. (Вы можете получить их из контакта с contact->GetFixtureA() и contact->GetFixtureB().)

Теперь, вы могли бы получить точечные данные из мирового многообразия и принимать решения на их основе, но зачем вам, когда нормаль уже доступна, поскольку в этом конкретном случай, когда нормаль (в сочетании с которой формирует нормальные точки от и до) - это все, что нужно.


Редактировать (для @iBradApps):

Во-первых, я предполагаю, что вы следовали учебнику, с которым я связан, и настроили прослушиватель контактов. Если нет, следуйте этому, потому что Рэй подробно объясняет это очень хорошо.

Во-вторых, я хочу указать, что нет абсолютной гарантии того, какой объект является А, а какой - В (ну, это зависит от того, что это за объекты Box2D; достаточно сказать, что они оба могут двигаться, вы можете ' я не могу гарантировать порядок, по крайней мере, насколько я знаю), поэтому в моем случае я хотел посмотреть, попал ли объект игрока в что-то, поэтому я создал переменную класса (b2Fixture *playerF) в моем приемнике контактов, который сохранил ссылку на объект игрока, чтобы я мог определить, был ли контакт А или контакт В игроком.

Вы спрашивали об обнаружении столкновения, когда что-то еще сталкивалось с вершиной B. Должно сработать что-то вроде следующего, хотя у меня не было возможности проверить это для вас:

В вашем ContactListener.h:

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

Когда вы делаете ContactListener в вашем init() (при условии, что вы назвали его _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
    }
}
...