Проблемы обнаружения столкновений с использованием AABB

8

Я реализовал простую процедуру обнаружения столкновений, используя AABB между моим основным игровым спрайтом и различными платформами (см. Код ниже). Это прекрасно работает, но сейчас я ввожу гравитацию, чтобы заставить моего персонажа упасть, и это выявило некоторые проблемы с моей программой на CD.

Я думаю, что суть в том, что моя подпрограмма на компакт-диске перемещает спрайт назад вдоль оси, в которой он проник наименьшее количество в другой спрайт. Так что, если он находится больше по оси Y, чем по X, то он переместит его обратно вдоль оси X.

Тем не менее, теперь мой (герой) спрайт теперь падает со скоростью 30 пикселей на кадр (это экран высотой 1504 - мне нужно, чтобы он падал так быстро, как я хочу, чтобы попытаться симулировать «нормальную» гравитацию, любой медленнее просто выглядит странно ) Я получаю эти проблемы. Я попытаюсь показать, что происходит (и что, я думаю, вызывает это - хотя я не уверен) с несколькими картинками: (Код ниже изображения).

Буду признателен за некоторые предложения о том, как обойти эту проблему.

введите описание изображения здесь

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

введите описание изображения здесь

Мой код

public boolean heroWithPlatforms(){

    //Set Hero center for this frame
    heroCenterX  = hero.xScreen+(hero.quadWidth/2);
    heroCenterY  = hero.yScreen+(hero.quadHeight/2);

    //Iterate through all platforms to check for collisions
    for(x=0;x<platformCoords.length;x+=2){

    //Set platform Center for this iteration
    platformCenterX = platformCoords[x]+(platforms.quadWidth/2);
    platformCenterY = platformCoords[x+1]+(platforms.quadHeight/2);

    // the Dif variables are the difference (absolute value)
    // of the center of the two sprites being compared (one for X axis difference
    //and on for the Y axis difference)
    difX = Math.abs(heroCenterX-platformCenterX);
    difY = Math.abs(heroCenterY-platformCenterY);

    //If 1
    //Check the difference between the 2 centers and compare it to the vector (the sum of
    //the two sprite's widths/2.  If it is smaller, then the sprites are pverlapping along
    //the X axis and we can now proceed to check the Y axis 
    if (difX<vecXPlatform){

    //If 2
    //Same as above but for the Y axis, if this is also true, then we have a collision
    if(difY<vecYPlatform){
        //we now know sprites are colliding, so we now need to know exactly by how much
        //penX will hold the value equal to the amount one sprite (hero, in this case)
        //has overlapped with the other sprite (the platform)
        penX = vecXPlatform-difX;
        penY = vecYPlatform-difY;
        //One sprite has penetrated into the other, mostly in the Y axis, so move sprite
        //back on the X Axis
        if (penX < penY){hero.xScreen-=penX*(heroCenterX-platformCenterX>=0 ? -1 : 1);}
        //Sprite has penetrated into the other, mostly in the X asis, so move sprite
        //back on the Y Axis
        else if (penY < penX) {hero.yScreen-=penY*(heroCenterY-platformCenterY>=0 ? -1 : 1);}
        return true;
        }//Close 'If' 2
        } //Close 'If' 1
    }
    //Otherwise, no collision

    return false;
    }
BungleBonce
источник
Чтобы выяснить, почему вы перемещаете спрайты обратно на другую ось: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
Том «Синий» Пиддок
Привет @Blue, потому что спрайт должен быть скорректирован вдоль наименее проникающей оси, поэтому, если спрайт проник в платформу больше по оси X, чем по Y, это Y должен быть исправлен, так как он меньше из двух.
BungleBonce
Я прочитал этот вопрос до @Anko - он не дает мне ответы на мои вопросы. Поэтому я сочинил свой собственный вопрос (Не уверен, что спрашивающий задает то же самое, хотя в этом трудно убедиться) :-)
BungleBonce

Ответы:

1

во время моих экспериментов с HTML5 canvas и AABB я нашел именно то, что вы испытываете. Это произошло, когда я попытался сделать платформу из соседних блоков размером 32x32 пикселей.

Решения, которые я пробовал по порядку моего личного предпочтения

1 - Разделить движения по оси

Мой текущий, и я думаю, что я продолжу свою игру с ним. Но не забудьте проверить, что я называю Phantom AABB, если вам нужно быстрое решение без необходимости много менять ваш текущий дизайн.

В моем игровом мире очень простая физика. Нет понятия о силах (пока), только смещение. Гравитация - это постоянное смещение вниз. Нет ускорения (пока).

Смещения применяются по оси. И в этом состоит это первое решение.

Каждый игровой кадр:

player.moves.y += gravity;
player.moves.x += move_x;

move_x устанавливается в соответствии с обработкой ввода, если не нажата клавиша со стрелкой, то move_x = 0. Значения ниже нуля означают влево, в противном случае вправо.

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

Примечание: после обнаружения столкновения и реакции, компонент перемещения должен быть установлен в ноль, оба свойства x и y, потому что на следующем тике гравитация будет добавлена ​​снова. Если вы не выполните сброс, вы закончите с ходами в два раза больше желаемой силы тяжести, а на следующем тике это будет три раза.

Затем введите код применения смещения и обнаружения столкновения.

Компонент move на самом деле не является текущей позицией спрайта. Спрайт еще не переместился, даже если Move.x или y изменились. Компонент «ходы» - это способ сохранить смещение, которое будет применено к позиции спрайта в правильной части игрового цикла.

Сначала обработайте ось y (move.y). Применить move.y к sprite.position.y. Затем примените метод AABB, чтобы увидеть, перекрывает ли обрабатываемый движущийся спрайт платформу AABB (вы уже поняли, как это сделать). Если он перекрывается, переместите спрайт обратно по оси y, применяя пенетрации.y. Да, в отличие от моего другого ответа, теперь этот метод развитого движения игнорирует проникающий.x для этого шага процесса. И мы используем пенетрация. Даже если она больше, чем пенетрация, мы даже не проверяем это.

Затем обработайте ось X (move.x). Примените move.x к sprite.position.x. И сделать наоборот, чем раньше. На этот раз вы будете перемещать спрайт только по горизонтальной оси, применяя пенетрации.x. Как и прежде, вам все равно, будет ли пенетрация.х меньше или больше, чем пенетрация.

Суть в том, что если спрайт не движется и изначально не сталкивался, то он останется таким же в следующем кадре (sprite.moves.x и y равны нулю). Особый случай, когда другой игровой объект волшебным образом телепортируется в положение, которое перекрывает обработанный спрайт, - это то, что я рассмотрю позже.

Когда спрайт начинает двигаться. Если он движется только по оси x, то нас интересует, проникает ли он куда-нибудь слева или справа. Поскольку спрайт не движется вниз, и мы знаем это, потому что свойство y компонента ходов равно нулю, мы даже не проверяем вычисленное значение для пенетрации.y, мы видим только в пенетрации.x. Если в оси движения существует проникновение, мы применяем коррекцию, в противном случае мы просто позволяем спрайту двигаться.

Как насчет диагонального смещения, не обязательно под углом 45 градусов?

Предлагаемая система справится с этим. Вам нужно только обработать каждую ось отдельно. Во время вашей симуляции. Различные силы применяются к спрайту. Даже если ваш двигатель еще не разбирается в силах. Эти силы приводят к произвольному смещению в 2D-плоскости, это смещение представляет собой 2D-вектор в любом направлении и любой длины. Из этого вектора вы можете извлечь ось y и ось x.

Что делать, если вектор смещения слишком велик?

Вы не хотите этого:

|
|
|
|
|
|
|
|_ _ _ _ _ _ _ _ _

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

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

|_
  |_
    |_
      |_
        |_
          |_
            |_
              |_

Как насчет телепортации спрайтов?

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

2 - Фантом ААББ

Имейте вторичную AABB для каждого спрайта, на который влияет сила тяжести, которая начинается у основания спрайта, имеет одинаковую ширину спрайта AABB и высоту в 1 пиксель.

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

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

Эта:

player.position.y += gravity;

Становится что-то вроде этого:

if (player.onGround == false) player.position.y += gravity;

Очень просто и надежно.

Вы оставляете свою реализацию AABB как есть.

Фантомная AABB может иметь специальный цикл, который их обрабатывает, который проверяет только простые столкновения и не тратит время на вычисление вектора проникновения. Этот цикл должен быть перед нормальным коллизией и кодом ответа. Вы можете применить гравитацию во время этой петли, для спрайтов в воздухе примените гравитацию. Если фантомный AABB сам по себе является классом или структурой, он может иметь ссылку на спрайт, которому он принадлежит.

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

Хатору Ханьсу
источник
Мне нравится решение Phantom AABB здесь. Я уже применил нечто подобное, чтобы обойти проблему. (Но не использую фантомную AABB) - мне интересно узнать больше о вашем начальном методе выше, но меня это немного смущает. Является ли компонент Moves.x (и .y) в основном величиной, на которую будет перемещен спрайт (если ему разрешено перемещаться)? Кроме того, мне неясно, как это могло бы помочь с моей ситуацией напрямую. Был бы признателен, если бы вы могли отредактировать свой ответ, чтобы показать графическое изображение, как оно могло бы помочь в моей ситуации (как на моем рисунке выше) - спасибо!
BungleBonce
О компоненте ходов. Это даёт будущее смещение, да, но вы не проверяете, разрешаете ли вы движение или нет, вы фактически применяете движение по оси сначала y, затем x, а затем смотрите, столкнулись ли вы с чем-то, используете ли вы проникновение. вектор, чтобы двигаться назад. Почему сохранение смещения для последующей обработки отличается от фактического применения смещения, когда есть пользовательский ввод для обработки или что-то произошло в игровом мире? Я постараюсь поместить это в изображения, когда я редактирую свой ответ. А пока посмотрим, придет ли кто-нибудь еще с альтернативой.
Хатору Ханьсу
Ах, хорошо, я думаю, я понимаю, что вы говорите сейчас. Таким образом, вместо того, чтобы перемещать x & y и затем проверять столкновение, с помощью вашего метода вы сначала перемещаете X, а затем проверяете столкновение, если оно сталкивается, затем корректируйте по оси X. Затем переместите Y и проверьте столкновение, если есть столкновение, то двигайтесь вдоль оси X? Таким образом, мы всегда знаем правильную ось удара? Я правильно понял ?! Спасибо!
BungleBonce
Предупреждение: вы написали: «Затем переместите Y и проверьте столкновение, если есть столкновение, то двигайтесь вдоль оси X?» Это ось Y. Вероятно, опечатка. Да, это мой нынешний подход. Для очень больших смещений, превышающих ширину или высоту вашего спрайта, вам нужно разделить на более мелкие шаги, и это то, что я имею в виду по аналогии с L. Остерегайтесь не применять большого смещения по оси y, а затем большого x, вам нужно разделить как на маленькие шаги, так и на интеркалирование, чтобы получить правильное поведение. Что-то должно волновать, только если в вашей игре допускаются большие смещения. По моему, я знаю, что они будут происходить. Так что я подготовился к этому.
Hatoru Hansou
Великолепно @HatoruHansou, я никогда не думал о том, чтобы так поступить, я обязательно попробую - большое спасибо за вашу помощь, я отмечу ваш ответ как принятый. :-)
BungleBonce
5

Я бы объединил плитки платформы в единую платформу.

Например, предположим, что вы берете 3 плитки с картин и объединяете их в одну сущность, у вашей сущности будет поле столкновения:

Left   = box1.left
Right  = box3.right
Top    = box1.top
Bottom = box1.bottom

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

UnderscoreZero
источник
Привет @UnderscoreZero - спасибо, это один из способов, единственное, что я в настоящее время храню свои плитки в 3 отдельных массивах, один для плиток левого края, для средних плиток и один для плиток правого края - думаю, я мог бы объединить их все в один массив для целей CD, просто не знаю, как мне написать код для этого.
BungleBonce
Другой вариант, который вы можете попробовать использовать во многих физических движках, - это когда объект приземлился на плоскую плоскость, он отключает гравитацию для объекта, чтобы предотвратить его падение. Как только объект получает достаточную силу, чтобы оттянуть его от плоскости или он падает со стороны плоскости, они вновь включают гравитацию и выполняют физику в соответствии с нормой.
UnderscoreZero
0

Подобные проблемы характерны для новых методов обнаружения столкновений.

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

Прежде всего, сделайте эти переменные:

  1. Сделайте скорость X и Y
  2. Сделать гравитацию
  3. Сделать прыжок скорость
  4. Сделать isJumping

Во-вторых, сделайте таймер для расчета дельты, чтобы повлиять на скорость движения объекта.

В-третьих, сделайте это:

  1. Проверьте, нажимает ли игрок кнопку прыжка и не прыгает ли, если это так, добавьте jumpSpeed ​​к скорости Y.
  2. Вычитайте гравитацию из скорости Y каждое обновление, чтобы моделировать гравитацию
  3. Если у игрока высота y + меньше или равна y платформы, установите скорость Y на 0 и установите Y игрока на платформу y + рост игрока.

Если вы сделаете это, не должно быть никаких проблем. Надеюсь это поможет!

LiquidFeline
источник
Привет @ user1870398, спасибо, но в моей игре на данный момент даже нет прыжков, вопрос связан с проблемами, возникающими из-за использования метода «наименее проникающей оси» для исправления позиции спрайта. Проблема в том, что гравитация тянет моего персонажа дальше в мои платформы вдоль оси Y, чем перекрывает ось X, поэтому моя рутина (как это правильно для этого типа алгоритма) корректирует ось X, поэтому я не могу перемещаться по платформе (поскольку подпрограмма CD видит отдельные фрагменты и обрабатывает их как таковые, даже если они представлены как одна платформа).
BungleBonce
Я понял твою проблему. Просто вы не правильно корректируете свое движение. Вам нужна переменная isJumping . Если вы нажмете кнопку прыжка и не будете прыгать, вы установите для него значение true, если вы коснулись земли, установите для него значение false, переместите Y игрока в правильное положение ( platform.y + player.height ) и установите для isJumping значение false. Затем, сразу после проверки этих вещей, выполните if (left_key) player.velocity.x - = (player.isJumping? Player.speed: player.speed - player.groundFriction) . Я знаю, что вы еще не реализовали прыжки, поэтому просто установите isJumping в false.
LiquidFeline
У меня нет прыжков, но если переменная 'ifJumping' всегда установлена ​​в false, как это отличается от отсутствия переменной? (скажем, в играх, где нет прыжков) Я просто немного озадачен тем, как булевский 'isjumping' может помочь в моей ситуации - был бы признателен, если бы вы могли объяснить немного больше - спасибо!
BungleBonce
Это не нужно Просто лучше реализовать это, пока вы еще программируете. В любом случае, причина заключается в том , что isJumping организовывает ваш код и предотвращает проблемы, только добавляя гравитацию, когда игрок прыгает. Просто сделай то, что я рекомендовал. Этот метод используется большинством людей. Даже Minecraft использует это! Именно так эффективно моделируется гравитация! :)
LiquidFeline