AABB: столкновение прямоугольников

Как игра понимает, что герой коснулся монетки или врага? Через коллизии. И самый частый их вид — AABB, проверка пересечения прямоугольников.
Суть: AABB-коллизия проверяет, перекрываются ли два прямоугольника по обеим осям. Если по X есть нахлёст И по Y есть нахлёст — объекты столкнулись.

Коллизия (столкновение) — это сердце игрового взаимодействия. Собрал монетку, получил урон, отскочил от стены, попал пулей — всё это коллизии. Самый распространённый и быстрый способ их проверять называется AABB: Axis-Aligned Bounding Box, «ограничивающий прямоугольник, выровненный по осям». Звучит грозно, а внутри — простейшая логика.

Каждый объект оборачиваем в прямоугольник. Два прямоугольника пересекаются тогда и только тогда, когда они перекрываются ОДНОВРЕМЕННО по горизонтали и по вертикали. Если они нахлёстываются по X, но один выше другого (по Y нет нахлёста) — столкновения нет. И наоборот. Только когда обе оси «согласны», объекты действительно касаются.

В Pygame это уже готово: rect_a.colliderect(rect_b) возвращает True при пересечении. Но понять формулу важно — она объясняет, почему иногда коллизии «не срабатывают», и лежит в основе физики платформеров.

Как работает под капотом

Проверка идёт по каждой оси отдельно. По X объекты пересекаются, если левый край одного левее правого края другого И наоборот. То же по Y. Столкновение = пересечение по X И по Y:

   нет нахлёста по Y -> НЕТ коллизии
      +----+
      | A  |
      +----+
           +----+
           | B  |
           +----+

   нахлёст по X И по Y -> ЕСТЬ коллизия
      +------+
      | A +--+---+
      +---+--+ B |
          +------+

   условие: A.left < B.right И A.right > B.left
        И   A.top < B.bottom И A.bottom > B.top

В pygame (читаем):

player = pygame.Rect(100, 100, 50, 50)
coin   = pygame.Rect(130, 120, 20, 20)

if player.colliderect(coin):
    score += 1
    coin_sprite.kill()

Реализуем AABB своими руками — это та самая формула, что внутри colliderect. Запусти и проверь на разных прямоугольниках. Попробуй сам:

def aabb(a, b):
    ax, ay, aw, ah = a
    bx, by, bw, bh = b
    return (ax < bx + bw and ax + aw > bx and
            ay < by + bh and ay + ah > by)

player = (100, 100, 50, 50)
tests = {
    "монетка рядом": (130, 120, 20, 20),
    "монетка ниже":  (130, 200, 20, 20),
    "касание углом": (149, 149, 20, 20),
}
for name, other in tests.items():
    print(f"{name}: столкновение = {aabb(player, other)}")

Когда AABB не подходит

AABB идеален для прямоугольных и почти прямоугольных объектов, но у него есть слабое место — он не знает о форме спрайта, только о его прямоугольной рамке. Для круглого мячика или диагонального меча прямоугольная рамка будет «толще» картинки, и попадания засчитаются в пустоту по углам. Для кругов точнее проверка по расстоянию между центрами (сравнить его с суммой радиусов), а для сложных форм — попиксельная маска pygame.mask, которая сверяет реальные непрозрачные пиксели.

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

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

Частые ошибки

  • Проверять только одну ось — получишь «коллизию» там, где объекты разнесены по другой оси.
  • Использовать <= вместо < — объекты, касающиеся ровно краями, будут считаться столкнувшимися (иногда это нежелательно).
  • Забыть обновить rect перед проверкой — проверяешь старую позицию.

Best practices

  • Для прямоугольных объектов используй встроенный colliderect — он быстрый и проверенный.
  • Делай хитбокс чуть меньше спрайта — игроку приятнее, когда «почти попал» не считается попаданием.
  • Понимай формулу: она пригодится для разрешения столкновений в платформере.

Итог: AABB — это проверка нахлёста по X И по Y. Простая формула, на которой держится почти вся 2D-физика.

Проверьте себя
1. Когда два прямоугольника считаются столкнувшимися по правилу AABB?
AКогда совпадают центры
BКогда есть нахлёст по X ИЛИ по Y
CКогда есть нахлёст по X И по Y
DКогда равны их площади
2. Какой метод Rect в Pygame проверяет AABB-столкновение?
Arect.aabb()
Brect.colliderect(other)
Crect.hit(other)
Drect.overlap_xy()