Обработка конфликтов 2D-платформеров

Я пытаюсь создать 2D-платформер (типа Марио), и у меня возникают проблемы с правильной обработкой коллизий. Я пишу эту игру на C ++, используя SDL для ввода, загрузки изображений, загрузки шрифтов и т. Д. Я также использую OpenGL через библиотеку FreeGLUT в сочетании с SDL для отображения графики.

Мой метод обнаружения столкновений - AABB (ограничивающая рамка с выравниванием по оси), и это действительно все, с чего мне нужно начать. Мне нужен простой способ как определить, на какой стороне произошло столкновение, так и правильно обработать столкновения. Итак, в основном, если игрок сталкивается с вершиной платформы, переместите его на вершину; если есть столкновение по сторонам, переместите игрока обратно сбоку от объекта; если произойдет столкновение с дном, переместите игрока под платформу.

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

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

void world1Level1CollisionDetection()
{
for(int i; i < blocks; i++)
{
    if (de2dCheckCollision(ball,block[i],0.0f,0.0f)==true)
    {
        de2dObj ballPrev;
        ballPrev.coords[0] = ball.coords[0];
        ballPrev.coords[1] = ball.coords[1];
        ballPrev.coords[2] = ball.coords[2];
        ballPrev.coords[3] = ball.coords[3];
        ballPrev.coords[0] -= ball.xspeed;
        ballPrev.coords[1] -= ball.yspeed;
        ballPrev.coords[2] -= ball.xspeed;
        ballPrev.coords[3] -= ball.yspeed;

        int up = 0;
        int left = 0;
        int right = 0;
        int down = 0;

        if (ballPrev.coords[0] < block[i].coords[0] && ballPrev.coords[2] < block[i].coords[0] && (((ball.coords[1] < block[i].coords[1]) || (ball.coords[3] < ball.coords[1]))  || ((ball.coords[1] < block[i].coords[3]) || ball.coords[3] < block[i].coords[3])))
        {
            left = 1;
        }

        if (ballPrev.coords[0] > block[i].coords[2] && ballPrev.coords[2] > block[i].coords[2] && (((ball.coords[1] < block[i].coords[1]) || (ball.coords[3] < ball.coords[1]))  || ((ball.coords[1] < block[i].coords[3]) || (ball.coords[3] < block[i].coords[3]))))
        {
            right = 1;
        }
        if(ballPrev.coords[1] < block[i].coords[1] && block[i].coords[1] < ballPrev.coords[3] && ballPrev.coords[3] < block[i].coords[3])
        {
            up = 1;
        }
        if(block[i].coords[1] < ballPrev.coords[1] && ballPrev.coords[1] < block[i].coords[3] && block[i].coords[3] < ballPrev.coords[3])
        {
            down = 1;
        }

        cout << left << ", " << right << ", " << up << ", " << down << ", " << endl;

        if (left == 1)
        {
            ball.coords[0] = block[i].coords[0] - 18.0f;
            ball.coords[2] = block[i].coords[0] - 2.0f;
        }
        else if (right == 1)
        {
            ball.coords[0] = block[i].coords[2] + 2.0f;
            ball.coords[2] = block[i].coords[2] + 18.0f;
        }
        else if (down == 1)
        {
            ball.coords[1] = block[i].coords[3] + 4.0f;
            ball.coords[3] = block[i].coords[3] + 20.0f;
        }
        else if (up == 1)
        {
            ball.yspeed = 0.0f;
            ball.gravity = 0.0f;
            ball.coords[1] = block[i].coords[1] - 17.0f;
            ball.coords[3] = block[i].coords[1] - 1.0f;
        }
    }
    if (de2dCheckCollision(ball,block[i],0.0f,0.0f)==false)
    {
        ball.gravity = -0.5f;
    }
}
}

Чтобы объяснить, что означает часть этого кода:

Переменная блоков - это в основном целое число, в котором хранится количество блоков или платформ. Я проверяю все блоки с помощью цикла for, и номер, на котором в данный момент находится цикл, представлен целым числом i. Система координат может показаться немного странной, так что это стоит пояснить. coords [0] представляет позицию x (слева) объекта (где он начинается на оси x). coords [1] представляет позицию y (вверху) объекта (где он начинается на оси y). coords [2] представляет собой ширину объекта плюс координаты [0] (справа). coords [3] представляет высоту объекта плюс координаты [1] (внизу). de2dCheckCollision выполняет обнаружение столкновений AABB. Вверх - отрицательный y, а вниз - положительный y, как и в большинстве игр.

Надеюсь, я предоставил достаточно информации, чтобы кто-то смог мне успешно помочь. Если я упустил что-то важное, дайте мне знать, и я предоставлю необходимую информацию. Наконец, для всех, кто может помочь, предоставление кода было бы очень полезным и очень полезным.

Еще раз спасибо за вашу помощь!

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

Изменить: вот URL-адрес изображения, пытающегося показать, какие у меня проблемы: http://img8.imageshack.us/img8/4603/collisionproblem.png

В случае, если объяснение на картинке не имеет особого смысла, мяч не может переместиться влево за угол объекта, если я не перепрыгну через него. Однако мяч может двигаться вправо, но он перемещается вправо от объекта при движении, что не требуется. По сути, это создает движение с пропуском, когда оно выглядит так, как будто мяч перескакивает через половину объекта или около того, когда я двигаюсь вправо. Если в этом нет смысла, спросите меня, и я постараюсь уточнить подробности.


person defender-zone    schedule 01.03.2011    source источник
comment
Этот вопрос, возможно, больше подходит для gamedev.stackexchange.com, однако ... там, похоже, население испытывает недостаток во многих отношениях. В конце концов, может быть, здесь будет лучше.   -  person Sion Sheevok    schedule 01.03.2011
comment
Как бы то ни было, код движения в платформерах действительно содержит много ручной настройки и глюков. Есть даже сообщество людей, которые перепроектируют их, чтобы сделать спидран с помощью инструментов.   -  person Heatsink    schedule 01.03.2011
comment
Я уже слышал о спидране с помощью инструментов. Спасибо за информацию о коде движений платформеров. Похоже, мне нужно было бы сделать много ручной настройки, чтобы это работало должным образом. Надеюсь, у кого-то появятся идеи, как я могу решить эту проблему.   -  person defender-zone    schedule 01.03.2011


Ответы (1)


Одна из проблем с вашим кодом заключается в том, что вы обнаруживаете только такие ситуации:

мяч

Если круг оказывается полностью внутри блока, вы вообще не меняете положение. И это проблема.

Вы пытаетесь представить себе симуляцию непрерывной, но помните, что она дискретна. В общем, если вы только посмотрите на текущее состояние мяча, вы действительно не сможете узнать, с какой стороной он столкнулся. Посмотрите на эти две возможности: case

Первое решение, которое приходит на ум, - это также посмотреть на последнюю позицию мяча; точнее, посмотрите на вектор дельты. Посмотрите, пересекает ли дельта-вектор стену. Если это так, измените положение в направлении оси к стене, пересекаемой вектором дельты.

Изменить: Когда я сказал «дельта-вектор», я забыл, что вы перемещаете квадрат, а не одну точку. Итак, если вы просто посмотрите на дельта-вектор верхнего левого угла, этого будет недостаточно, потому что он может не обнаружить, что часть мяча вошла в блок. Вместо этого вы можете посмотреть на дельта-векторы всех 4 углов.

person Stefan Monov    schedule 01.03.2011
comment
+1 за то, что дошел до того, чтобы проиллюстрировать решение. Был в процессе я. - person Sion Sheevok; 01.03.2011
comment
Я понимаю. Так что, если я правильно понимаю, проверьте, где был мяч до фактического столкновения, и попробуйте обработать его соответствующим образом, правильно? Однако одна из больших проблем, с которыми я сталкиваюсь, - это обработка по нескольким осям и когда объект попадает в угол. - person defender-zone; 01.03.2011
comment
@ defender-zone: Вы меня правильно поняли. Я не знаю, что вы имеете в виду, работая с несколькими осями - решения дельта-вектора отлично работают как в направлении x, так и в направлении y. Что касается углового случая - вы хотите, чтобы ваш мяч был круглым в симуляции физики или просто квадратом, выровненным по оси? - person Stefan Monov; 01.03.2011
comment
@Stefan Monov: Я подумал, что если объект попадет в угол, это будет другой сценарий. Спасибо, что развеяли для меня это сомнение. Кроме того, мой мяч - это просто квадрат с выравниванием по оси. Я постараюсь включить ваши предложения в свой код. Огромное спасибо за помощь. Я ценю это. - person defender-zone; 01.03.2011
comment
@ defender-zone: Нарисуйте это на бумаге - должно быть понятнее. Что касается углового случая - подумайте об этом так: что будет делать новый алгоритм в случае углового столкновения? Он выберет стену, чтобы скользить по ней, и она будет скользить по ней. Какую из двух угловых стен он выберет? Нарисуйте на бумаге несколько ящиков, и вы увидите, что он выбирает правильную стену (это сложно объяснить, но вы можете строго доказать это сами или попросить дополнительную помощь, если не можете). Кроме того, я отредактировал свой ответ, чтобы включить дополнительную информацию. - person Stefan Monov; 01.03.2011
comment
Таким образом, при угловом столкновении вам нужно такое же поведение, как и при краевом столкновении. Никакого особого случая. - person Stefan Monov; 01.03.2011
comment
Спасибо, что разъяснили мне это. Я просто не могу придумать достойный алгоритм, который я мог бы использовать, чтобы определить, на какой стороне произошло столкновение. Я все думаю, что 2 случая вернут истину и вызовут проблемы. Могу ли я внести какие-то простые изменения в существующий алгоритм, или мне придется переписать новый? - person defender-zone; 01.03.2011
comment
2 случая? Вы имеете в виду 2 из 4 случаев, которые я упомянул в редакции? Да, иногда они будут. Хорошо, допустим, вы проверяете 4 дельта-вектора и находите n пересечений (n может быть ›4, если вы пройдете через весь блок). Хитрость в том, что первым произошло одно из n пересечений. Который из? Что ж, все 4 угла движутся с одинаковой скоростью. Итак, разделив расстояние на скорость, вы получите время. Итак, первое столкновение произошло на перекрестке, ближайшем к его положению в последнем кадре. Хорошо, теперь, когда вы знаете, где произошло столкновение, вы знаете, по какой стене вам следует скользить. - person Stefan Monov; 01.03.2011
comment
Ах, теперь это начинает иметь для меня смысл. Возможный недостаток, который я мог бы заметить, - это когда вы идете по земле. То, что я испытал в своих предыдущих попытках, заключается в том, что когда я достигаю конца объекта, который является частью пола, мяч не может пройти мимо него, и он привязан к расстоянию x этого одного меньшего объекта. Итак, в этом случае мой мяч технически летит сбоку до того, как произойдет столкновение, и алгоритм сначала хотел бы изменить его положение по оси x. Скажите, если в этом нет смысла, и я постараюсь объяснить подробнее. - person defender-zone; 01.03.2011
comment
Я не понимаю. Разместите картинку. В основном я не понимаю, почему вы меняете положение по оси x, если вы не столкнулись с какой-либо вертикальной стеной. - person Stefan Monov; 01.03.2011
comment
Я только что выложил картинку. Обратите внимание, что я еще не учел ваши идеи, так как все еще немного не понимаю, как это сделать правильно. Надеюсь, эта картинка немного прояснит ситуацию. - person defender-zone; 01.03.2011
comment
В случае, если объяснение на картинке не имеет особого смысла, мяч не может переместиться влево за угол объекта, если я не перепрыгну через него. Однако мяч может двигаться вправо, но он перемещается вправо от объекта при движении, что не требуется. По сути, это создает движение с пропуском, когда оно выглядит так, как будто мяч перескакивает через половину объекта или около того, когда я двигаюсь вправо. Если в этом нет смысла, спросите меня, и я постараюсь уточнить подробности. - person defender-zone; 01.03.2011
comment
@ defender-zone: похоже, что de2dCheckCollision возвращает true для крайних случаев, когда самая верхняя координата одного прямоугольника равна самой нижней координате другого прямоугольника. В таких случаях он должен возвращать false. - person Stefan Monov; 01.03.2011
comment
@Stefan Monov: Ах, да, именно это функция сейчас делает - возвращает true для крайних случаев. Я рисовал это на бумаге, как вы предлагали, и начинаю понимать это лучше. Меня сейчас нет дома, поэтому у меня нет компьютера, чтобы проверить это. Просто для подтверждения: я должен проверить, где находился объект до столкновения, и если, скажем, он находился над платформой, переместите его только вверх, правильно? И то же самое в других случаях? - person defender-zone; 01.03.2011
comment
@Stefan Monov: После попытки написать алгоритм, который проверяет, где находился мяч до столкновения, у меня все еще проблемы. Я разместил свой новый код выше, чтобы дать вам представление о том, что я пытаюсь сделать. Угловые шкафы теперь работают на этой единственной платформе правильно, и когда у меня есть стена из объектов, я теперь могу правильно скользить по ней. Единственная оставшаяся проблема заключается в том, что есть небольшой эффект дрожания, который возникает, когда я нахожусь на земле, когда мяч постоянно поднимается и опускается, как будто его тянет сила тяжести, а затем мяч снова падает в объект. Любые идеи? - person defender-zone; 02.03.2011
comment
Ого. Условие if с вложенностью и 5 логическими операторами? А с индексированием [0] [1] [2] [3] вместо обычных членов .x1, .x2, .y1, .y2 в структуре? Я даже не буду пытаться это читать. Но насчет дрожания: мяч когда-либо явно пересекает блок или просто отскакивает от него? - person Stefan Monov; 02.03.2011
comment
Я знаю - мой код запутан. Мяч просто отскакивает от блока, а не пересекает его. Я могу исправить эту проблему, переместив мяч на 1 пиксель вглубь блока по оси y. Не совсем то решение, на которое я надеялся, но я думаю, что смогу с ним справиться. Мне кажется, что это происходит из-за того, что de2dCheckCollision не проверяет крайние случаи (на самом деле он даже этого раньше не делал; моя ошибка). Если у вас есть лучшее решение, дайте мне знать. Я отметил этот пост как ответ. Большое вам спасибо за помощь, которую вы мне оказали. Я действительно ценю это. Вы дали мне отличное начало. - person defender-zone; 02.03.2011
comment
@ defender-zone: Ваша самая большая проблема в том, что вы на самом деле не знаете, что делает ваш код. Я вижу это по вашим сообщениям. Вам следует научиться отлаживать такой простой код. Предварительное условие: ball.y2==block.y1. Физика делает ball.y+=delta из-за гравитации и ball.y-=delta из-за изменения положения. Постусловие: ball.y2==block.y1. Учитывая это, вы вставляете отладочные отпечатки для проверки предусловия, постусловия, предположения, что гравитация добавляет дельту, и предположения, что изменение положения вычитает (ту же самую) дельту. Тогда причина ошибки станет очевидной. - person Stefan Monov; 02.03.2011
comment
@ defender-zone: Использование системного подхода означает, что если у вас будет достаточно времени, вы сможете исправить любую ошибку, и все, что останется, - это концептуальные проблемы. Чего вам не следует делать, так это начинать изменять код, исходя из некоторой смутной догадки о том, что может быть не так. Было бы неплохо найти наставника - кого-то, с кем можно было бы работать, так что вы получите от него хорошую методологию. - person Stefan Monov; 02.03.2011
comment
@Stefan Monov: Да, я признаю это: я в основном собирал полуслучайный код в быстрой последовательности, чтобы посмотреть, будет ли что-то работать или нет. Это только казалось мне разочарованием. Я должен применять систематический подход вместо того, чтобы собирать код вместе, но я полагаю, что я стал нетерпеливым с этой проблемой, поскольку я потратил недели, пытаясь ее исправить. Что ж, спасибо за совет и за объяснение того, как лучше отладить мой код. - person defender-zone; 03.03.2011