Я хотел сделать игру, которая бы работала и чувствовала себя как Super Mario World из JavaScript. Физика и движения персонажей на экране в этих играх просты и элегантны. Самый простой способ приблизиться к этому типу обнаружения двумерных столкновений - использовать концепцию, известную как ограничивающая рамка с выравниванием по оси.

Выровненная по оси ограничивающая рамка или AABB определяет, перекрываются ли два невращающихся многоугольника друг друга. Обычно алгоритмы AABB работают примерно так:

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

Затем мы сравниваем позиции X и Y вместе с шириной и высотой, чтобы увидеть, не перекрываются ли они.

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

строка 2 - левая сторона A находится слева от правой стороны B.

строка 3 - правая сторона A находится справа от левой стороны B.

строка 4 - верх А выше низа Б.

строка 5 - низ A ниже вершины B.

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

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

Компоненты игры

Мой проект представлял собой простую двухмерную платформенную игру на основе тайлов, состоящую из объекта игрока, красного квадрата 32 на 32 пикселя с названием «Кубио», трех типов «злодеев» и тайловой карты мира игры.

Плитки

Вся карта состоит всего из четырех типов плиток. Размер 80 на 80 пикселей, каждый имеет собственное числовое значение и определенные наборы правил, которые определяют, как игрок взаимодействует с ними при столкновении.

0: небо

Игрок свободно перемещается по этой плитке.

1: земля

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

2: ящик

Если игрок сталкивается с любой стороной этой плитки, его позиция остается за пределами этой плитки.

3: sky_island

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

карта

Не вдаваясь в подробности здесь, тайловая карта представлена ​​массивом типов тайлов в назначенных им столбце и строке, так что массив:

… Отображается как:

Взаимодействие и поведение

Будь то небо или земля, мой игрок всегда сталкивается хотя бы с одной плиткой в ​​каждом кадре, поэтому нет необходимости запускать функцию aabb (). Моя функция collision_detection () сначала определяет положение каждого угла игрока. Затем он извлекает тип плитки, с которой сталкивается угол. Затем он передает тип плитки и положение угла по осям x и y в функцию collide ().

Функция collide () использует оператор switch для выбора соответствующего блока кода для запуска в зависимости от типа плитки. Как видите, тип «0» (небо) не имеет никакого эффекта, а тип «2» (ящик) сталкивается со всеми четырьмя сторонами. Если вам интересно, почему я дважды определил эти точки в collision_detection (), это потому, что каждый раз, когда я вызываю collide (), он может переместить игрока. Поэтому каждую точку нужно переопределять каждый раз после вызова collide ().

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

Функция должна знать, в каком направлении движется игрок, чтобы получить правильный ответ. В противном случае, если игрок столкнется с нижней частью плитки ящика, ее верх будет выше дна ящика. Затем, когда мы запускаем collide (), сначала срабатывает collide_top () и помещает игрока на вершину плитки. Для пользователя это будет выглядеть так, как если бы игрок прошел сквозь дно ящика и приземлился наверху. Этот негативный эффект известен как «туннелирование». Таким образом, определение направления игрока дает нам лучшее представление о том, что происходит.

Вы можете видеть, как это при написании функции baddy_collision_detection () было бы очень похоже на это, только мы можем изменить collide_top (), чтобы убить злодея, и все другие столкновения, чтобы убить игрок на контакте.

function baddyCollideTopMortal(player, bad){
    if (player.getBottom() > bad.getTop() && player.getOldBottom() <= bad.getTop()){
      
        player.setBottom(bad.getTop() - 0.01);
        
        player.y_velocity = -27;
        player.jumping = false;
        let dead_index = currentGame.alive_baddies.indexOf(bad);
        currentGame.alive_baddies.splice(dead_index, 1);
        return true;
    } return false;
  }
  
  function baddyCollideLeft(player, bad){
  
    if (player.getRight() > bad.getLeft() && player.getOldRight() <= bad.getLeft()){
        player.setRight(bad.getLeft() - 1);
        player.x_velocity = 0;
        player_dead = true;
        return true;
    } return false;
    
  }

Эффективность с AABB, монетами и злодеями

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

Здесь вы разделяете процесс проверки на столкновение на две фазы, широкую и узкую.

Широкий

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

Узкий

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

Обнаружение столкновений - это механизм, который оживляет вашу игру. Каждый шаг прост для понимания и может быть переосмыслен множеством разных способов, но однажды реализованный, он становится мощным. Удачного кодирования!