У меня проблема с разрешением коллизий 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. Но это усиливает столкновение с шипами, как я уже говорил выше.
источник
Ответы:
Хорошо, я понял, почему у демоверсии платформера XNA AppHub нет «прыгающей» ошибки: демоверсия тестирует плитки столкновений сверху вниз . Находясь напротив «стены», игрок может перекрывать несколько плиток. Порядок разрешения важен, потому что разрешение одного столкновения может также разрешить другие столкновения (но в другом направлении).
onGround
Свойство устанавливается только тогда , когда столкновение решается нажатием игрока на оси у. Это разрешение не произойдет, если предыдущие разрешения толкали игрока вниз и / или по горизонтали.Я смог воспроизвести «прыгающую» ошибку в демоверсии XNA, изменив эту строку:
к этому:
(Я также подправил некоторые физические константы, но это не должно иметь значения.)
Возможно, сортировка
bodiesToCheck
по оси Y перед разрешением коллизий в вашей игре исправит ошибку «прыжка». Я предлагаю разрешить столкновение на «мелкой» оси проникновения, как это делает демонстрация XNA и Тревор . Также обратите внимание, что демонстрационный проигрыватель XNA в два раза больше плиток, способных к столкновению, что повышает вероятность множественного столкновения.источник
Position = nextPosition
непосредственно внутриfor
цикла, в противном случае нежелательные разрешения коллизий (настройкаonGround
) все еще происходят. Игрок должен толкаться вертикально вниз (и никогда не подниматься вверх) при ударе по потолку, поэтомуonGround
никогда не должен быть установлен. Вот как это делает демо-версия XNA, и я не могу воспроизвести там ошибку «потолка».onGround
установлен, поэтому я не могу понять, почему прыжки неправильно разрешены. Демоверсия XNA обновляет свое аналогичноеisOnGround
свойство внутриHandleCollisions()
. Кроме того, после установкиVelocity.Y
на 0, почему гравитация не начинает снова перемещать игрока вниз? Я предполагаю, чтоonGround
свойство установлено неправильно. Посмотрите, как обновляется демо XNApreviousBottom
иisOnGround
(IsOnGround
).Самым простым решением для вас будет проверить оба направления столкновения для каждого объекта в мире, прежде чем разрешать любые столкновения, и просто разрешить меньшее из двух возникающих «составных столкновений». Это означает, что вы решаете наименьшее возможное значение, вместо того, чтобы всегда разрешать x первым или всегда разрешать y первым.
Ваш код будет выглядеть примерно так:
большая редакция : прочитав комментарий к другим ответам, я, наконец, заметил необъявленное предположение, которое приведет к тому, что этот подход не сработает (и объясняет, почему я не мог понять проблемы, с которыми сталкиваются некоторые, но не все) люди видели с таким подходом). Чтобы уточнить, вот еще несколько псевдокодов, более четко показывающих, что функции, на которые я ссылался ранее, должны были на самом деле делать:
Это не тест "для каждой пары объектов"; он не работает, тестируя и сопоставляя движущийся объект с каждой плиткой карты мира в отдельности (этот подход никогда не будет работать надежно, и он будет приводить к катастрофическим последствиям при уменьшении размеров плитки). Вместо этого он тестирует движущийся объект против каждого объекта на карте мира одновременно, а затем разрешается на основе столкновений с картой всего мира.
Таким образом вы гарантируете, что (например) отдельные настенные плитки внутри стены никогда не подпрыгивают игрока вверх и вниз между двумя соседними плитками, в результате чего игрок оказывается в ловушке в некотором несуществующем пространстве «между ними»; расстояния разрешения столкновений всегда рассчитываются вплоть до пустого пространства в мире, а не только до границы одной плитки, которая может иметь другую сплошную плитку поверх нее.
источник
if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
редактировать
Я улучшил это
Кажется, ключевая вещь, которую я изменил, - это классификация пересечений.
Пересечения либо:
и я решаю их в таком порядке
Определите столкновение с землей как игрок, находящийся как минимум на 1/4 пути на тайле
Так что это столкновение с землей, и игрок ( синий ) будет сидеть на вершине плитки ( черный )
Но это НЕ наземное столкновение, и игрок «соскользнет» с правой стороны плитки, когда он приземлится на нее.
При таком способе игрок больше не будет попадаться по бокам стен
источник
Примечание:
мой движок использует класс обнаружения столкновений, который проверяет столкновения со всеми объектами в режиме реального времени, только когда объект перемещается и только в квадратах сетки, которые он в настоящее время занимает.
Мое решение:
Когда я столкнулся с такими проблемами в моем 2d-платформере, я сделал что-то вроде следующего:
- (движок быстро обнаруживает возможные столкновения)
- (игра информирует движок о точных столкновениях для конкретного объекта) [возвращает вектор объекта *]
- (объект смотрит на глубину проникновения, а также на предыдущую позицию относительно предыдущей позиции другого объекта, определить, с какой стороны выскользнуть)
- (объект движется и усредняет свою скорость со скоростью объекта (если объект движется))
источник
В видеоиграх, которые я запрограммировал, подход заключался в том, чтобы иметь функцию, сообщающую, является ли ваша позиция действительной, т.е. 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
источник
У меня есть несколько вопросов, прежде чем я начну отвечать на это. Во-первых, в первоначальной ошибке, в которой вы застряли в стенах, были ли эти плитки на левой отдельной плитке в отличие от одной большой плитки? И если они были, игрок застрял между ними? Если да на оба этих вопроса, просто убедитесь, что ваша новая позиция действительна . Это означает, что вам нужно проверить, нет ли столкновения с тем, куда вы указываете игроку двигаться. Так что решите минимальное смещение, как описано ниже, и затем двигайте своего игрока, основываясь на этом, только если он можетиди туда. Почти тоже под носом: P Это на самом деле представит еще одну ошибку, которую я называю «угловыми случаями». По существу, с точки зрения углов (например, в левом нижнем углу, где горизонтальные шипы появляются в вашем .gif, но если бы не было шипов), это не разрешило бы столкновение, потому что казалось бы, что ни одно из созданных вами разрешений не приведет к правильной позиции , Чтобы решить эту проблему, просто укажите, было ли разрешено столкновение, а также список всех минимальных разрешений проникновения. После этого, если столкновение не было разрешено, выполните циклическое повторение каждого созданного вами разрешения и отслеживайте максимальное X и максимальное разрешение Y (максимальные значения не должны приходиться на одно и то же разрешение). Затем разрешите столкновение на этих максимумах. Кажется, это решит все ваши проблемы, а также те, с которыми я столкнулся.
Другой вопрос, шипы вы показываете одну плитку, или отдельные плитки? Если они представляют собой отдельные тонкие плитки, возможно, вам придется использовать другой подход для горизонтальных и вертикальных плиток, чем тот, который я опишу ниже. Но если они целые плитки, это должно работать.
Хорошо, так что в основном это то, что описывал @Trevor Powell. Поскольку вы используете только AABB, все, что вам нужно сделать, - это найти, насколько один прямоугольник проникает в другой. Это даст вам количество по осям X и Y. Выберите минимум из двух, и переместите ваш сталкивающийся объект вдоль этой оси на это количество. Это все, что вам нужно для разрешения коллизии AABB. При таком столкновении вам НИКОГДА не нужно будет перемещаться по нескольким осям, поэтому вас никогда не должно смущать вопрос о том, какой из них двигаться первым, поскольку вы будете двигаться только по минимуму.
Метанет программное обеспечение имеет классический учебник по подходу здесь . Это также входит в другие формы также.
Вот функция XNA, которую я сделал, чтобы найти вектор перекрытия двух прямоугольников:
(Надеюсь, за ним просто следовать, потому что я уверен, что есть лучшие способы его реализации ...)
isCollision (Rectangle) буквально является просто вызовом XNA Rectangle.Intersects (Rectangle).
Я проверил это с движущимися платформами, и, кажется, работает нормально. Я сделаю еще несколько тестов, более похожих на ваш .gif, чтобы удостовериться и сообщу, если он не работает.
источник
Это просто.
Перепишите шипы. Это вина шипов.
Ваши столкновения должны происходить в дискретных, осязаемых единицах. Проблема, насколько я могу видеть это:
Проблема с шагом 2, а не 3 !!
Если вы пытаетесь заставить вещи чувствовать себя солидно, вы не должны позволять предметам скользить друг в друга таким образом. Как только вы окажетесь на пересечении, если вы потеряете свое место, проблему станет сложнее решить.
Шипы в идеале должны проверять наличие игрока, а когда они двигаются, они должны толкать его по мере необходимости.
Самый простой способ для достижения этой цели является для игрока , чтобы иметь
moveX
иmoveY
функцию , которые понимают ландшафт и засунуть игрок на определенной дельте или насколько они могут не задев препятствия .Обычно они будут вызваны циклом событий. Однако они также могут быть вызваны объектами, чтобы подтолкнуть игрока.
Очевидно, что игрок все равно должен реагировать на шипы, если он в них попадает .
Общее правило заключается в том, что после любого перемещения объекта он должен заканчиваться в позиции без пересечения. Таким образом, невозможно получить глюки, которые вы видите.
источник
moveY
не удается удалить пересечение (потому что мешает другая стена), вы можете снова проверить пересечение и либо попробовать moveX, либо просто убить его.