2D Platformer AABB проблемы столкновения

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

У меня проблема с разрешением коллизий AABB.


Я разрешаю пересечение AABB, сначала разрешив ось X, а затем ось Y. Это сделано для предотвращения этой ошибки: http://i.stack.imgur.com/NLg4j.png


Текущий метод работает хорошо, когда объект перемещается в игрока, и игрок должен быть перемещен горизонтально. Как вы можете видеть в .gif, горизонтальные шипы толкают игрока правильно.


Однако когда вертикальные пики попадают в игрока, ось X все еще разрешается первой. Это делает невозможным использование шипов в качестве подъемника.

Когда игрок движется по вертикальным шипам (под воздействием силы тяжести, падает в них), его толкают по оси Y, потому что не было никакого перекрытия по оси X с самого начала.


Что-то, что я попробовал, был метод, описанный в первом ответе этой ссылки: 2D обнаружение столкновения прямоугольного объекта

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


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

Это мой текущий исходный код столкновения: http://pastebin.com/MiCi3nA1

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


Я попытался реализовать ту же систему коллизий, что и в демонстрационной платформе XNA AppHub (скопировав большую часть материала). Однако в моей игре возникает «прыгающая» ошибка, а в демке AppHub ее нет. [прыгающая ошибка: http://i.stack.imgur.com/NLg4j.png ]

Чтобы прыгнуть, я проверяю, находится ли игрок на «OnGround», затем добавляю -5 к Velocity.Y.

Поскольку Velocity.X игрока выше, чем Velocity.Y (см. Четвертую панель на диаграмме), onGround устанавливается в true, когда этого не должно быть, и, таким образом, позволяет игроку прыгать в воздухе.

Я полагаю, что этого не происходит в демоверсии AppHub, потому что Velocity.X игрока никогда не будет выше Velocity.Y, но я могу ошибаться.

Я решил это раньше, решив сначала по оси X, а затем по оси Y. Но это усиливает столкновение с шипами, как я уже говорил выше.

Витторио Ромео
источник
5
Там на самом деле ничего не происходит неправильно. Метод, который я использую, сначала перемещается по оси X, а затем по оси Y. Вот почему игрока толкают горизонтально даже на вертикальных шипах. Мне нужно найти решение, которое позволяет избежать «проблемы с прыжком» и толкает игрока на мелкую ось (ось с наименьшим проникновением), но я не могу ее найти.
Витторио Ромео
1
Вы можете определить, какое лицо препятствия касается игрок, и решить это
Иоахим
4
@ Джонатан Хоббс, прочитайте вопрос. Ви точно знает, что делает его код, но не знает, как решить определенную проблему. Шаги по коду не помогут ему в этой ситуации.
AttackingHobo
4
@Maik @Jonathan: С программой все в порядке, он точно знает, где и почему его алгоритм не выполняет то, что ему нужно. Он просто не знает, как изменить алгоритм, чтобы делать то, что он хочет. Так что отладчик в этом случае бесполезен.
BlueRaja - Дэнни Пфлюгофт
1
@AttackingHobo @BlueRaja Я согласен, и я удалил свой комментарий. Это был я, просто делающий выводы о том, что происходит. Я действительно извиняюсь за то, что заставил вас объяснить это, и за то, что ввел в заблуждение хотя бы одного человека. Честно говоря, вы можете рассчитывать на то, что я действительно правильно рассмотрю вопрос, прежде чем я оставлю ответ в следующий раз.
двойник

Ответы:

9

Хорошо, я понял, почему у демоверсии платформера XNA AppHub нет «прыгающей» ошибки: демоверсия тестирует плитки столкновений сверху вниз . Находясь напротив «стены», игрок может перекрывать несколько плиток. Порядок разрешения важен, потому что разрешение одного столкновения может также разрешить другие столкновения (но в другом направлении). onGroundСвойство устанавливается только тогда , когда столкновение решается нажатием игрока на оси у. Это разрешение не произойдет, если предыдущие разрешения толкали игрока вниз и / или по горизонтали.

Я смог воспроизвести «прыгающую» ошибку в демоверсии XNA, изменив эту строку:

for (int y = topTile; y <= bottomTile; ++y)

к этому:

for (int y = bottomTile; y >= topTile; --y)

(Я также подправил некоторые физические константы, но это не должно иметь значения.)

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

Leftium
источник
Это звучит интересно ... Как вы думаете, упорядочение всего по координате Y до обнаружения столкновений может решить все мои проблемы? Есть ли подвох?
Витторио Ромео
Аааа а я нашел "подвох". Используя этот метод, ошибка прыгания исправлена, но если я установлю Velocity.Y в 0 после удара потолка, это произойдет снова.
Витторио Ромео
@Vee: установить Position = nextPositionнепосредственно внутри forцикла, в противном случае нежелательные разрешения коллизий (настройка onGround) все еще происходят. Игрок должен толкаться вертикально вниз (и никогда не подниматься вверх) при ударе по потолку, поэтому onGroundникогда не должен быть установлен. Вот как это делает демо-версия XNA, и я не могу воспроизвести там ошибку «потолка».
Leftium
pastebin.com/TLrQPeBU - это мой код: я фактически работаю над самой позицией, не используя переменную "nextPosition". Он должен работать так же, как и в демоверсии XNA, но если я оставлю строку, которая устанавливает Velocity.Y в 0 (строка 57), без комментариев, произойдет ошибка перехода. Если я удаляю его, игрок продолжает плавать, когда прыгает в потолок. Это можно исправить?
Витторио Ромео
@Vee: ваш код не показывает, как onGroundустановлен, поэтому я не могу понять, почему прыжки неправильно разрешены. Демоверсия XNA обновляет свое аналогичное isOnGroundсвойство внутри HandleCollisions(). Кроме того, после установки Velocity.Yна 0, почему гравитация не начинает снова перемещать игрока вниз? Я предполагаю, что onGroundсвойство установлено неправильно. Посмотрите, как обновляется демо XNA previousBottomи isOnGround( IsOnGround).
Leftium
9

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

Ваш код будет выглядеть примерно так:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

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

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

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

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

Тревор Пауэлл
источник
1
i.stack.imgur.com/NLg4j.png - это происходит, если я использую описанный выше метод.
Витторио Ромео
1
Возможно, я неправильно понимаю ваш код, но позвольте мне объяснить проблему на диаграмме: так как игрок быстрее на оси X, проникновение X> Y проникновения. Столкновение выбирает ось Y (потому что проникновение меньше) и толкает игрока вертикально. Этого не происходит, если игрок достаточно медленный, так что проникновение Y всегда> проникновения X.
Витторио Ромео
1
@Vee. Какую область диаграммы вы здесь называете неправильным поведением, используя этот подход? Я предполагаю панель 5, в которой игрок явно проникает меньше в X, чем в Y, что привело бы к выталкиванию вдоль оси X - что является правильным поведением. Вы получаете плохое поведение, только если выбираете ось для разрешения на основе скорости (как на вашем изображении), а не на основе фактического проникновения (как я предположил).
Тревор Пауэлл
1
@Trevor: Ах, я понимаю, что вы говорите. В этом случае вы могли бы просто сделать этоif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Дэнни Пфлугхофт
1
@BlueRaja Отличный момент. Я отредактировал свой ответ, чтобы использовать как можно меньше условных выражений. Спасибо!
Тревор Пауэлл
6

редактировать

Я улучшил это

Кажется, ключевая вещь, которую я изменил, - это классификация пересечений.

Пересечения либо:

  • Потолочные неровности
  • Наземные удары (толчок из пола)
  • Столкновения в воздухе
  • Столкновения с заземленной стеной

и я решаю их в таком порядке

Определите столкновение с землей как игрок, находящийся как минимум на 1/4 пути на тайле

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

Но это НЕ наземное столкновение, и игрок «соскользнет» с правой стороны плитки, когда он приземлится на нее. не собирается быть игроком столкновения с землей

При таком способе игрок больше не будет попадаться по бокам стен

bobobobo
источник
Я попробовал ваш метод (см. Мою реализацию: pastebin.com/HarnNnnp ), но я не знаю, где установить Velocity игрока на 0. Я попытался установить его на 0, если encrY <= 0, но это позволяет игроку остановиться в воздухе скользя вдоль стены, а также позволяет ему многократно прыгать по стене.
Витторио Ромео
Тем не менее, это работает правильно, когда игрок взаимодействует с шипами (как показано на .gif). Я был бы очень признателен, если бы вы могли помочь мне решить проблему прыжков со стен (также застревание в стенах). Имейте в виду, что мои стены сделаны из нескольких плиток AABB.
Витторио Ромео
Извините за публикацию другого комментария, но после копирования кода в новом проекте, изменения sp на 5 и появления плиток в форме стены, возникает ошибка прыжка (см. Эту диаграмму: i.stack.imgur.com /NLg4j.png)
Витторио Ромео
Хорошо, попробуй это сейчас.
Бобобо
+1 для рабочего примера кода (хотя отсутствуют горизонтальные «всплески» блоков :)
Leftium
3

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

Мое решение:

Когда я столкнулся с такими проблемами в моем 2d-платформере, я сделал что-то вроде следующего:

- (движок быстро обнаруживает возможные столкновения)
- (игра информирует движок о точных столкновениях для конкретного объекта) [возвращает вектор объекта *]
- (объект смотрит на глубину проникновения, а также на предыдущую позицию относительно предыдущей позиции другого объекта, определить, с какой стороны выскользнуть)
- (объект движется и усредняет свою скорость со скоростью объекта (если объект движется))

ultifinitus
источник
«(объект смотрит на глубину проникновения, а также на предыдущую позицию относительно предыдущей позиции другого объекта, чтобы определить, с какой стороны выскользнуть)» это та часть, которая меня интересует. Можете ли вы уточнить? Удастся ли это в ситуации, показанной .gif и диаграммой?
Витторио Ромео
Мне еще предстоит найти ситуацию (кроме высоких скоростей), в которой этот метод не работает.
ultifinitus
Звучит хорошо, но можете ли вы объяснить, как вы решаете проникновения? Вы решаете всегда на самой маленькой оси? Вы проверяете сторону столкновения?
Витторио Ромео
Нет, на самом деле меньшая ось не является хорошим (основным) способом. ИМХО. Я обычно решаю, исходя из того, какая сторона ранее находилась за пределами стороны другого объекта, это наиболее часто. Когда это не удается, тогда я проверяю на основе скорости и глубины.
ультифинит
2

В видеоиграх, которые я запрограммировал, подход заключался в том, чтобы иметь функцию, сообщающую, является ли ваша позиция действительной, т.е. bool ValidPos (int x, int y, int wi, int he);

Функция проверяет ограничивающий прямоугольник (x, y, wi, he) на всю геометрию в игре и возвращает false, если есть пересечение.

Если вы хотите переместиться, скажем, вправо, занять позицию игрока, добавить 4 к x и проверить, является ли позиция действительной, если нет, проверять с помощью + 3, + 2 и т. Д., Пока она не будет.

Если вам также нужна гравитация, вам нужна переменная, которая растет до тех пор, пока вы не коснетесь земли (удар по земле: ValidPos (x, y + 1, wi, he) == true, y здесь положительно вниз). если вы можете переместиться на это расстояние (т. е. ValidPos (x, y + gravity, wi, he) возвращает true), вы падаете, иногда полезно, когда вы не сможете контролировать своего персонажа при падении.

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

Если это не так, вам нужно найти позицию, которая есть. Если игровые объекты не могут двигаться быстрее, чем, скажем, 2 пикселя за игровой оборот, вам нужно проверить, является ли позиция (x, y-1) действительной, затем (x, y-2), затем (x + 1, y) и т. Д. и т. д. следует проверять все пространство между (x-2, y-2) - (x + 2, y + 2). Если нет действительной позиции, значит, вы были «раздавлены».

НТН

Valmond

Valmond
источник
Я использовал этот подход еще во времена Game Maker. Я могу придумать способы ускорить его / улучшить его с помощью лучшего поиска (скажем, бинарный поиск по пройденному вами пространству, хотя в зависимости от пройденного расстояния и геометрии он может быть медленнее), но главный недостаток этого подхода заключается в том, что что вместо того, чтобы делать одну проверку на предмет всех возможных столкновений, вы делаете все, что бы ни изменили ваше положение.
Джефф
«Вы делаете все, что бы ни поменялось ваше изменение позиции» Извините, но что именно значит? Кстати, вы, конечно, будете использовать методы разделения пространства (BSP, Octree, quadtree, Tilemap и т. Д.), Чтобы ускорить игру, но вам всегда нужно будет делать это в любом случае (если карта большая), вопрос, однако, не об этом, но об алгоритме, используемом для перемещения (правильно) игрока.
Вальмонд
@Valmond Я думаю, что @Jeff означает, что если ваш игрок движется на 10 пикселей влево, вы можете иметь до 10 различных обнаружений столкновений.
Джонатан Коннелл
Если это то, что он имеет в виду, тогда он прав, и так и должно быть (кто иначе может сказать, остановимся ли мы на +6, а не на +7?). Компьютеры работают быстро, я использовал это на S40 (~ 200 МГц и не очень быстрый KVM), и это работало как шарм. Вы всегда можете попытаться оптимизировать исходный алгоритм, но это всегда даст вам угловой случай, подобный тому, который имеет ОП.
Вальмонд
Это именно то, что я имел в виду
Джефф
2

У меня есть несколько вопросов, прежде чем я начну отвечать на это. Во-первых, в первоначальной ошибке, в которой вы застряли в стенах, были ли эти плитки на левой отдельной плитке в отличие от одной большой плитки? И если они были, игрок застрял между ними? Если да на оба этих вопроса, просто убедитесь, что ваша новая позиция действительна . Это означает, что вам нужно проверить, нет ли столкновения с тем, куда вы указываете игроку двигаться. Так что решите минимальное смещение, как описано ниже, и затем двигайте своего игрока, основываясь на этом, только если он можетиди туда. Почти тоже под носом: P Это на самом деле представит еще одну ошибку, которую я называю «угловыми случаями». По существу, с точки зрения углов (например, в левом нижнем углу, где горизонтальные шипы появляются в вашем .gif, но если бы не было шипов), это не разрешило бы столкновение, потому что казалось бы, что ни одно из созданных вами разрешений не приведет к правильной позиции , Чтобы решить эту проблему, просто укажите, было ли разрешено столкновение, а также список всех минимальных разрешений проникновения. После этого, если столкновение не было разрешено, выполните циклическое повторение каждого созданного вами разрешения и отслеживайте максимальное X и максимальное разрешение Y (максимальные значения не должны приходиться на одно и то же разрешение). Затем разрешите столкновение на этих максимумах. Кажется, это решит все ваши проблемы, а также те, с которыми я столкнулся.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

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


Хорошо, так что в основном это то, что описывал @Trevor Powell. Поскольку вы используете только AABB, все, что вам нужно сделать, - это найти, насколько один прямоугольник проникает в другой. Это даст вам количество по осям X и Y. Выберите минимум из двух, и переместите ваш сталкивающийся объект вдоль этой оси на это количество. Это все, что вам нужно для разрешения коллизии AABB. При таком столкновении вам НИКОГДА не нужно будет перемещаться по нескольким осям, поэтому вас никогда не должно смущать вопрос о том, какой из них двигаться первым, поскольку вы будете двигаться только по минимуму.

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

Вот функция XNA, которую я сделал, чтобы найти вектор перекрытия двух прямоугольников:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(Надеюсь, за ним просто следовать, потому что я уверен, что есть лучшие способы его реализации ...)

isCollision (Rectangle) буквально является просто вызовом XNA Rectangle.Intersects (Rectangle).

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


Джефф
источник
Я скептически отношусь к тому комментарию, который я сделал, что он не работает с тонкими платформами. Я не могу придумать причину, почему это не будет. Кроме того, если вы хотите источник, я могу загрузить проект XNA для вас
Джефф
Да, я только что проверил это еще раз, и это возвращает вас к той же самой проблеме застревания в стене. Это потому, что, когда две плитки выровнены друг с другом, он говорит вам двигаться вверх из-за нижней, а затем выйти (прямо в вашем случае) за верхнюю, эффективно сводя на нет гравитационное движение, если ваша скорость у достаточно низкая , Мне придется искать решение для этого, я, кажется, помню эту проблему раньше.
Джефф
Хорошо, я придумала чрезвычайно хакерский способ обойти вашу первую проблему. Решение включает в себя поиск минимального проникновения для каждой AABB, разрешение только для одного с наибольшим X, затем второй проход, разрешающийся только для самого большого Y. Это выглядит плохо, когда вы раздавлены, но поднимает работу, и вас могут толкнуть горизонтально, и ваша первоначальная проблема слипания исчезла. Это хоккей, и я понятия не имею, как это могло бы перейти в другие формы. Другая возможность может заключаться в сварке трогательных геометрий, но я даже не пытался это сделать
Джефф
Стены выполнены из плитки 16х16. Шипы - это плитка размером 16х16. Если я решу на минимальной оси, возникает ошибка прыжка (посмотрите на диаграмму). Будет ли ваше «чрезвычайно хакерское» решение работать с ситуацией, показанной в .gif?
Витторио Ромео
Да, работает в моем. Каков размер вашего персонажа?
Джефф
1

Это просто.

Перепишите шипы. Это вина шипов.

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

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

Проблема с шагом 2, а не 3 !!

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

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

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

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

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Очевидно, что игрок все равно должен реагировать на шипы, если он в них попадает .

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

Крис Бёрт-Браун
источник
в крайнем случае, когда moveYне удается удалить пересечение (потому что мешает другая стена), вы можете снова проверить пересечение и либо попробовать moveX, либо просто убить его.
Крис Берт-Браун