2D AABB и разрешение нескольких столкновений

8

Итак, это проблема, которую я пытался выяснить уже довольно давно. Mine - 2D платформерная игра с миром, состоящим из (обычно) неподвижных плиток и мобильных спрайтов, которые используют AABB для представления своих хитбоксов. Эта игра НЕ основана на сетке из-за некоторых сложностей с перемещением слоев плиток.

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

Диаграмма № 1

Это достаточно просто и работает довольно хорошо. То есть до тех пор, пока спрайт не столкнется с более чем одним тайлом. Поскольку по своей природе каждое столкновение должно проверяться отдельно, разные столкновения могут иметь разное направление для разрешения. Например, если спрайт пытается пройти по ряду плиток, для одного кадра они будут пересекать следующую плитку, например что горизонтальная глубина меньше, чем вертикальная глубина. Поскольку столкновение говорит «разрешить влево», оно будет отброшено назад и застрянет в углу.

Диаграмма № 2

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

Я полагаю, что другим возможным способом было бы предсказать столкновения до того, как они произойдут, и «отработать» движение до точки столкновения, но я не уверен, как работает математика для этого.

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

Celarix
источник
Вы можете просто изменить местоположение игрока в зависимости от того, какой тайл был первым в вашем чеке
Chachmu

Ответы:

6

Проблема

Проблема заключается в вашем методе разрешения столкновений. Ваш метод выглядит следующим образом:

  1. Переместить игрока.
  2. Проверьте на столкновение.
  3. Определите кратчайшую глубину столкновения.
  4. Разрешить столкновение.

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

Столкновение ошибка

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

Решение

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

  1. Переместите игрока вдоль оси X.
  2. Проверьте на сталкивающиеся плитки.
  3. Разрешить X столкновения.
  4. Переместите игрока вдоль оси Y.
  5. Проверьте на сталкивающиеся плитки.
  6. Разрешить Y столкновения.

Теперь я надеюсь, что вы не выбросили свой код проверки глубины, потому что он вам все еще понадобится для шагов 3 и 6.

Чтобы разрешить столкновение между плитками на любой из двух осей (после перемещения игрока), вы сначала получаете глубину столкновения. Затем вы берете глубину столкновения и вычитаете ее из осей, которые вы в настоящее время проверяете на столкновение. Обратите внимание, что глубина должна быть отрицательной, если вы двигаетесь влево, чтобы игрок двигался в правильном направлении.

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

Пример кода:

void move(velocity)
{
    top = player.y / TILE_HEIGHT;
    bottom = top + (player.height / TILE_HEIGHT);
    left = player.x / TILE_WIDTH;
    right = left + (player.width / TILE_WIDTH);

    // Check X

    player.x += velocity.x;
    player.updateAABB();
    for(int tx = left - 1; tx <= right + 1; tx++)
    {
        for(int ty = top - 1; ty <= bottom + 1; ty++)
        {
            aabb = world.getTileAABB(tx, ty);
            if(aabb.collidesWith(player.aabb))
            {
                depth = player.aabb.getXDepth(aabb);
                player.x -= depth;
            }
        }
    }

    // Now check Y

    player.y += velocity.y;
    player.updateAABB();
    for(int tx = left - 1; tx <= right + 1; tx++)
    {
        for(int ty = top - 1; ty <= bottom + 1; ty++)
        {
            aabb = world.getTileAABB(tx, ty);
            if(aabb.collidesWith(player.aabb))
            {
                depth = player.aabb.getYDepth(aabb);
                player.y -= depth;
            }
        }
    }

    player.updateAABB();
}
Lysol
источник
Интригует, но я все еще вижу проблему. В моем втором сценарии спрайт сталкивается с рядом плиток. Если я сначала проверю X коллизий, в этом сценарии будет одно неверное обнаружение, и оно все равно будет неправильно разрешено слева.
Celarix
@Celarix Второй сценарий не должен произойти, потому что вы сначала не просто проверяете ось X, но сначала двигаетесь вдоль нее. Спрайт никогда не будет в ряду плиток, потому что тест на столкновение Y из предыдущего движения не позволит вам столкнуться с таким рядом плиток. Просто убедитесь, что столкновения всегда разрешаются правильно. Однажды у меня возникли некоторые проблемы, связанные с тем, что я использовал floats для хранения своих координат. Так что это вызывало дрожь. Решением было округлить координаты, когда я закончил разрешать столкновения.
Lysol
Вы правы, и я думаю, что это может быть решением моей проблемы почти два года. (Я медленный разработчик.) Большое спасибо!
Celarix
Есть некоторые проблемы с моим ответом. Это работает для большинства ситуаций, но обратите внимание, что будут разные результаты в зависимости от того, проверяете ли вы сначала X-коллизию или Y-коллизию. Также имейте в виду, что туннелирование является проблемой; это не удастся с высокоскоростными объектами, так как они будут пропускать плитки.
Лизоль
Я думаю, что я пошел с X первым, чтобы склоны работали правильно. Пулевая бумага не является на самом деле проблемой для моего платформера, потому что ничто не движется достаточно быстро, чтобы пройти сквозь плитки. Спасибо за дальнейший вклад!
Celarix
0

Вы переосмысливаете проблему и объединяете пару вопросов. Но это нормально, потому что, как вы сказали, это очень решенная проблема с множеством отличных ответов.

Давайте разберемся с этим:

  1. Tilemaps . Ваш первый пример - спрайт, идущий по пачке плиток, выложенных горизонтально (или скольжение по стене плиток, выложенных вертикально, они изоморфны). Одним из очень элегантных решений этого является просто не проверять края плиток, к которым, как мы знаем, спрайт не может добраться, такие как ребра, которые находятся «под землей», или ребра, граничащие с другой полностью сплошной плиткой.

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

    Ознакомьтесь с учебными пособиями по плитке Metanet для пошагового объяснения этого. В своем вопросе вы говорите, что не используете традиционную карту листов, но это тоже нормально: статические плитки находятся в карте листов и обновляются, как указано выше, при перемещении платформ и таких обновлениях, как # 2 ниже.

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

    Если AABB могут перемещаться очень быстро в одном кадре, то вы должны «смести» движение при проверке столкновений: разбейте движение на более мелкие дроби и проверяйте столкновения на каждом шаге.

drhayes
источник