Я делаю небольшой платформер на js, и у меня проблемы с коллизиями. К сожалению, кажется, что 90% информации в Интернете обнаруживает столкновения, а не то, что следует за ними. Я могу легко обнаруживать столкновения, так как все в моей игре представляет собой 2d-прямоугольник, выровненный по оси, но обработка этих столкновений — сложная часть.
Я пытался переместить игрока на ближайший этаж при обнаружении столкновения, но это также происходит при столкновении со стеной. Так что я попытался вычислить ближайшее лицо и привязать к нему игрока, но это приводит к всевозможным странностям. Вот код, который у меня есть до сих пор (текущий код сейчас предназначен только для столкновений с полом, но тот же принцип можно применить к остальным направлениям)
if (collided) {
let ld = {'a': 'l', 'b': Math.abs(player.left.x - col.left.x)}
let rd = {'a': 'r', 'b': Math.abs(player.right.x - col.right.x)}
let td = {'a': 't', 'b': Math.abs(player.top.y - col.top.y)}
let bd = {'a': 'b', 'b': Math.abs(player.bottom.y - col.bottom.y)}
let dirs = [ld, rd, td, bd]
let nearestFace = dirs.hasMin('b').a
if (nearestFace == 'b') {
player.grounded = true
player.yvel = 0
player.pos.y = col.top.y + player.size.y/2
} else {
player.grounded = false
}
}
Ваш код, кажется, не проверяет, где сталкивается ближайшее лицо, только то, что это ближайшее лицо после столкновения. Что может вызвать такую проблему:
Если ваша игра в 2D, все является прямоугольником, и ничто не имеет угла, тогда у вас есть проблема столкновения, известная как столкновение AABB (Axis-Aligned Bounding Boxes), это отличное ключевое слово для поиска в вашем случае и потрясающее начало. точка.
Во-первых, я советую вам прогнозировать коллизии, а не решать их после того, как они произошли из-за потенциальных проблем, подобных этой:
Кроме того, если вам нужен расширенный учебник по этому вопросу, приведенное выше изображение взято из этого руководства: Обнаружение столкновений AABB и реагирование на них. В нем, наверное, есть все, что вам нужно.
Короче говоря, входными данными для вашей логики обработки столкновений должны быть старая позиция вашего объекта, текущий размер, его скорость и все другие объекты, с которыми может сталкиваться объект (их размер и положение). Ответом должно быть ожидаемое поведение вашего объекта (т. е. новая скорость, скорректированная для «избежания» столкновения). Таким образом, вы можете легко тестировать сценарии и свою реализацию, учтите следующее:
const player = {x: 0, y: 0, width: 10, height: 10, xvel: 12, yvel: 0};
// player.right = {x: player.x + player.width, y: player.y} // Your engine does this, right?
const collidableList = [{x: 15, y: 0, width: 5, height: 10}];
const newVelocity = handleCollision(player, collidableList);
Я предполагаю, что положение игрока выровнено по левому верхнему углу, поэтому в этом случае вы можете предсказать, что ваша новая скорость должна быть xvel = 5, yvel = 0
. Это означает, что вам просто нужно создать handleCollision
, который работает для этого тестового примера, а затем вы можете запустить несколько тестов, чтобы убедиться, что столкновение ведет себя хорошо в крайних случаях, таких как когда ничего не сталкивается и когда сталкиваются два объекта, и один из них ближе, чем другой.
Основная идея этого столкновения состоит в том, чтобы найти скорость, ближайшую к нулю, необходимую для ИЗБЕЖАНИЯ столкновения в следующем кадре.
Например, представьте себе сценарий, в котором игрок движется вправо. Давайте проигнорируем ось Y, потому что ее следует использовать только для определения того, будут ли объекты сталкиваться в будущем, и она не повлияет на расчеты самой горизонтальной скорости. Если игрок движется вправо, он должен проверить расстояние между левой стороной каждого объекта и правой стороной игрока, если это расстояние меньше скорости, это, очевидно, вызовет столкновение, например:
let targetVelocityX = player.xvel;
for (const collidable of ...) {
// ...
if (targetVelocityX > 0) {
// We are moving to the right so:
// Figure out how much space do we have between the objects
const leftOverSpace = collidable.left.x - player.right.x;
// Is our velocity larger than the space we have available?
if (targetVelocityX > leftOverSpace) {
// We must restrict the velocity because the player will collide otherwise
// We can only move as much as we have space available
targetVelocityX = leftOverSpace;
}
} else if (targetVelocityX < 0) {
// Moving to the left...
}
Используя тестовый пример, который я сделал ранее, мы должны получить leftOverSpace = 5
, что меньше, чем наше targetVelocityX = 12
, поэтому новая скорость будет равна 5, что заставит его коснуться сталкивающегося объекта, а в следующем кадре наша позиция игрока будет x = 5
и xvel = 5
, если мы запустим его снова, логика столкновения скажет нам, что левое пространство равно нулю, поэтому горизонтальная скорость будет установлена на ноль, что означает, что мы больше не можем двигаться вправо, потому что мы касаемся объекта.
Я должен напомнить вам, что это отличается от учебника, на который я ссылался выше, учебник пытается найти время, когда произошло столкновение, как число с плавающей запятой, что полезно, если вы хотите сохранить скорость, чтобы отклонить мяч для понга или сдвинуть мяч. объект вдоль, которые являются некоторыми из его примеров.
Вова, спасибо за подробный ответ! Я исправил свою систему, и теперь она работает отлично :)