Как я могу предотвратить прилипание персонажа моего платформера к настенным плиткам?

9

В настоящее время у меня есть платформер с плитками для ландшафта (графика позаимствована у Cave Story). Игра написана с нуля с использованием XNA, поэтому я не использую существующий движок или физический движок.

Столкновения плитки описываются в точности так, как описано в этом ответе (с простым SAT для прямоугольников и окружностей), и все работает отлично.

За исключением случаев, когда игрок врезается в стену во время падения / прыжка. В этом случае они поймают плитку и начнут думать, что достигли пола или потолка, которых на самом деле нет.

Изображение характера ловли

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

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

Как мне подойти к решению этой проблемы, чтобы персонаж игрока мог просто упасть вдоль стены, как и должен?

doppelgreener
источник
Похож на этот вопрос? gamedev.stackexchange.com/questions/14486/…
MichaelHouse
@ Byte56 Вопрос не тот, но ответ может помочь.
Doppelgreener
Примечание: за последние пару недель у меня было очень мало времени, чтобы поэкспериментировать с ответами здесь. Я реализовал разрешение столкновений в мире, как было дано в ответе на вопрос @ Byte56, в котором есть отдельные ошибки на высоких скоростях, и пока не смог опробовать ответы на этот вопрос. Я попробую их, когда смогу, и либо приму ответ, когда смогу, либо опубликую свой, если решу сам.
двойник

Ответы:

8

Самый простой, более надежный подход - просто не проверять столкновения на скрытых ребрах. Если у вас две настенные плитки, одна над другой, то нижний край верхней плитки и верхний край нижней плитки не должны проверяться на столкновение с игроком.

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

Существуют более продвинутые версии этой же техники, которые облегчают и ускоряют поддержку неквадратных плиток.

Я рекомендую вам прочитать статьи ниже. Они представляют собой простое введение в основы физики и обнаружения столкновений в современных платформерах (таких как Cave Story). Если вы хотите почувствовать себя в духе старой школы Марио, есть еще несколько хитростей, о которых нужно беспокоиться, но большинство из них включают в себя обработку прыжков и анимации, а не столкновения.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html

Шон Миддледич
источник
Можете ли вы уточнить, как игнорировать некоторые края во время столкновения? Обычно я вижу столкновения как пересечение двух прямоугольников (границ игрока и границ тайлов). Когда вы говорите не проверять столкновение с отдельными ребрами, значит ли это, что алгоритм столкновения - это не пересечение прямоугольник-прямоугольник, а что-то еще? Ваше описание имеет смысл, но я не уверен, как будет выглядеть реализация.
Дэвид Гувейя
Да, просто сделай столкновение с прямоугольником. Упрощено только потому, что линия всегда выровнена по оси. Вы можете просто разложить свой флажок.
Шон Мидлдич
5

Здесь порядок я бы сделал вещи ....

1) Сохранить текущий X, Y символа

2) Переместить направление X

3) Проверьте все углы символа, получив данные плитки, и проверьте, является ли он твердым, выполнив следующие действия: (X и Y - позиция символа)

  • для верхнего левого угла: floor (X / tileWidth), floor (Y / tileHeight);
  • для верхнего правого: этаж ((X + символШирина) / tileWidth), этаж (Y / тайл-Высота);
  • для нижнего левого угла: floor (X + / tileWidth), floor ((Y + characterHeight) / tileHeight);
  • для нижнего правого: этаж ((X + символШирина) / tileWidth), пол ((Y + символВысота) / плиткаВысота);

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

4) Если какая-либо из плиток сплошная, вам нужно переместить X в наилучшее возможное положение, восстановив сохраненный X и ...

  • если вы двигаетесь влево: X = floor (X / tileWidth) * tileWidth;
  • если вы двигаетесь направо: X = ((floor ((X + characterWidth) / tileWidth) + 1) * tileWidth) - characterWidth;

5) Повторите 2-4, используя Y

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

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

Если символ шире, чем 1 блок, сделайте то же самое, но замените CharacterY, CharacterHeight, TileHeight и т. Д. На CharacterX, CharacterWidth и т. Д.

Джон
источник
2

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

Какой-то парень
источник
1

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

В вашем примере область столкновения по оси X будет больше, чем по оси Y, поэтому она будет обработана первой. Затем, прежде чем пытаться обработать столкновение по оси Y, он проверит и увидит, что оно больше не сталкивается после перемещения по оси X. :)

Ник Бадал
источник
1

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

Amplify91
источник
1

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

Обратите внимание, что 14-tilemap.py содержит проблему отсечения, но она исправлена ​​в 15-blocker-sides.py

Я не могу точно объяснить, почему последний пример учебника не обрезается, в то время как ваш код делает, но я думаю, что это как-то связано с:

  • Условный ответ на столкновение, основанный на типе тайла (какие ребра действительно активны).
  • Игрок всегда «падает». Хотя игрок, кажется, отдыхает на плитке, игрок фактически многократно проваливается сквозь плитку, а затем отталкивается назад к вершине. (Элемент self.restingв коде используется только для проверки того, может ли игрок прыгнуть. Несмотря на это, я не могу заставить игрока совершить «двойной» прыжок со стены. Хотя теоретически это может быть возможно)
Leftium
источник
0

Другой метод (на который я ответил на вопрос, связанный с Byte56) - проверить, помещает ли разрешение столкновения символ в пустое место или нет. Таким образом, в вашей задаче вы получаете столкновение с потолка внутреннего прямоугольника, чтобы переместить его вверх, что было бы недопустимо (поскольку вы все еще сталкиваетесь с другой плиткой). Вместо этого вы перемещаете его только в том случае, если вы перемещены в свободное пространство (например, как столкновение с верхней плитки переместит вас влево), и как только вы обнаружите это столкновение, все готово.

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

Джефф
источник
0

Я только сделал 3 SAT в 3D, так что, возможно, я не понимаю это правильно. Если я понимаю вашу проблему, это может быть связано с тем, что контакты с осью контакта не могут быть возможными, в результате чего игрок ведет себя так, как будто есть пол. Возможно, одним из обходных путей может быть использование формы круга для вашего персонажа, тогда вы будете получать контактные оси только в направлении оси X при ударе о стену.

Микаэль Хегстрём
источник