Перейти к содержимому
Тема 4 из 5
Теория

Столкновения

Без столкновений нет игры

Пуля попала во врага, герой встал на платформу, мяч отбился от ракетки, змейка съела яблоко — всё это проверка столкновений: пересекаются ли две фигуры прямо сейчас. Холст этого не знает (на нём только пиксели), поэтому столкновения считают по числам из состояния игры.

В большинстве 2D-игр объекты для столкновений упрощают до двух фигур: прямоугольника и круга. Даже если на экране нарисован сложный персонаж, его «хитбокс» — простой прямоугольник.

Прямоугольник с прямоугольником (AABB)

Прямоугольники, стороны которых параллельны краям экрана, называют AABB (axis-aligned bounding box). Проверка — от обратного: прямоугольники не пересекаются, если один целиком левее, правее, выше или ниже другого. Во всех остальных случаях — пересекаются.

function rectsOverlap(a, b) {
  return (
    a.x < b.x + b.w &&    // левый край a левее правого края b
    a.x + a.w > b.x &&    // правый край a правее левого края b
    a.y < b.y + b.h &&    // верх a выше низа b
    a.y + a.h > b.y       // низ a ниже верха b
  );
}

Строгие < и > означают, что касание краями — ещё не столкновение. Если в игре нужно иначе, меняют на <= и >=.

Круг с кругом

Круги пересекаются, когда расстояние между центрами меньше суммы радиусов:

function circlesOverlap(a, b) {
  const dist = Math.hypot(a.x - b.x, a.y - b.y);   // √(dx² + dy²)
  return dist < a.r + b.r;
}

Math.hypot(dx, dy) — длина гипотенузы, то есть расстояние по теореме Пифагора. Когда объектов очень много, корень не считают и сравнивают квадраты: dx * dx + dy * dy < (a.r + b.r) ** 2.

Круг с прямоугольником

Мяч и кирпич, монета и платформа. Точный способ — найти точку прямоугольника, ближайшую к центру круга, и проверить расстояние до неё:

function circleRect(c, r) {
  const nearX = Math.max(r.x, Math.min(c.x, r.x + r.w));
  const nearY = Math.max(r.y, Math.min(c.y, r.y + r.h));
  return Math.hypot(c.x - nearX, c.y - nearY) < c.r;
}

В простых аркадах часто делают ещё проще: считают круг квадратом со стороной 2r и используют rectsOverlap. Ошибка заметна только на углах.

Отскок от стен

Мяч не должен вылетать за поле. Проверяем каждую стену: если мяч в неё залез — возвращаем его на границу и разворачиваем скорость:

if (ball.x - ball.r < 0) {          // левая стена
  ball.x = ball.r;
  ball.vx = Math.abs(ball.vx);      // теперь точно летит вправо
}
if (ball.x + ball.r > canvas.width) {   // правая стена
  ball.x = canvas.width - ball.r;
  ball.vx = -Math.abs(ball.vx);     // теперь точно летит влево
}

Почему не ball.vx = -ball.vx? За один кадр мяч может залезть в стену глубоко. Если только развернуть скорость, на следующем кадре он может ещё не выбраться — и скорость развернётся снова, мяч «прилипнет» и задрожит. Возврат на границу плюс Math.abs делают отскок надёжным.

Что делать после столкновения

Столкновение — это только «да / нет». Дальше решает игра:

for (const enemy of enemies) {
  if (rectsOverlap(bullet, enemy)) {
    enemy.alive = false;
    bullet.alive = false;
    score += 10;
  }
}
enemies = enemies.filter((e) => e.alive);

Удалять из массива прямо во время перебора опасно (индексы сдвигаются), поэтому сначала помечают alive = false, а потом убирают через filter.

Слишком быстрые объекты

Если пуля за кадр пролетает 50 пикселей, а стена толщиной 10, на одном кадре пуля ещё перед стеной, а на следующем — уже за ней, и пересечения не случится. Это называется «туннелирование». Простые средства: ограничить скорость, сделать объекты толще или двигать быстрые объекты в несколько мелких шагов за кадр.

Итог

  • Хитбоксы — прямоугольники и круги, проверяем числа из состояния, а не пиксели.
  • AABB: четыре сравнения краёв; круги: Math.hypot(dx, dy) < r1 + r2.
  • Отскок: вернуть на границу и задать направление через Math.abs.
  • После столкновения помечаем объекты и убираем filter-ом.

Прямоугольники пересекаются?

Лёгкая+10 XP

Напиши функцию rectsOverlap(a, b). Прямоугольники заданы как { x, y, w, h } (левый верхний угол и размер). Функция возвращает true, если у прямоугольников есть общая площадь. Если они только касаются краями — это не пересечение.

rectsOverlap({x:0,y:0,w:10,h:10}, {x:5,y:5,w:10,h:10})    → true
rectsOverlap({x:0,y:0,w:10,h:10}, {x:10,y:0,w:5,h:5})     → false (касание)

Подключи коннектор, чтобы решать задачи

Код задач выполняется на твоём компьютере — так быстрее и надёжнее, чем в браузере. Для этого нужна бесплатная программа-коннектор CodeWithMe. Подключить её нужно один раз.

Ищем коннектор на этом компьютере…

  1. 1
    Скачай коннектор для своей системы (Windows, macOS или Linux).Скачать коннекторНа странице загрузки выбери файл со своей системой в названии, например windows-amd64.
  2. 2Запусти его и выбери папку, где будут лежать твои решения. Python и Node.js он поставит туда сам, когда понадобится.
  3. 3
    Введи ниже код из 6 цифр — он написан в окне коннектора («Код для сайта CodeWithMe»). Если браузер спросит про доступ к локальной сети — разреши.

Коннектор открытый (исходный код на GitHub), работает только на твоём компьютере и выполняет код лишь после ввода кода сопряжения.