Итак, я чувствую себя немного неловко, отвечая на свой вопрос, но, очевидно, это официально приветствуется.
Так или иначе, проблема с тем, как я подходил к вещам, была двоякой. Во-первых, я использовал локальную нормаль контактного коллектора вместо нормальной мира. Во-вторых, мой код для реверсирования объектных преобразований был ошибочным (мне никогда бы не пришлось делать это, если бы я использовал мировой многообразие).
Мировое многообразие учитывает преобразования и размеры объектов и, как таковое, содержит данные, более легко применимые к мировой системе координат.
По соглашению в 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
}
}