Коллизии: AABB и тайловые
Механизм
Перекрытие двух AABB — теорема о разделяющей оси в миниатюре
Коробка задана краями [minX,maxX]×[minY,maxY]. Две коробки не пересекаются, если есть ось, по которой их проекции расходятся. Для выровненных по осям коробок осей-кандидатов всего две (X и Y), поэтому пересечение — это отрицание «разошлись хоть по одной»:
Четыре сравнения, ноль умножений — потому это и есть базовый broad-phase-тест во всех движках. Если все четыре истинны, коробки перекрываются; глубину проникновения по каждой оси даёт минимум из перекрытий, а вытолкнуть объект дешевле всего по оси наименьшего перекрытия (minimum translation vector).
Тайловые коллизии — O(1) вместо «каждый с каждым»
Проверять объект против всех стен — это O(n). Но если мир выложен плитками размера T, координаты объекта прямо адресуют клетки: достаточно глянуть те 2–4 тайла, которые накрывает его коробка.
Это сводит «найти ближайшую стену» к чтению по индексу — то же преимущество, что у тайловой карты для рендера. Ключевой приём разрешения — двигать и разрешать оси по очереди: сдвинул по X → проверил/выдавил из стен по X; затем сдвинул по Y → проверил/выдавил по Y. Раздельность критична: при одновременном разрешении объект цепляется за угол плитки и застревает на ровном полу из стыков тайлов.
Туннелирование и «заметание» (swept AABB)
Дискретная проверка смотрит позицию после шага. Если за шаг объект сдвинулся дальше толщины стены, на прошлом кадре он был перед стеной, на этом — уже за ней, а контакт между кадрами никто не проверил → пролетел сквозь (tunneling). Два лечения: маленький фиксированный шаг (ограничивает сдвиг за тик — см. урок про игровой цикл) и swept AABB — считаем не «пересеклись ли сейчас», а когда на отрезке движения происходит первый контакт. Для движения вдоль оси время входа по каждой оси:
где d_near — расстояние до ближней грани препятствия по оси, v_axis — скорость по ней. Контакт реален, если t_hit ∈ [0,1] и по другой оси в этот момент проекции уже перекрыты. Объект ставим в позицию момента t_hit, гасим нормальную компоненту скорости и «скользим» по касательной.
Числовой пример. Пуля летит вправо со скоростью 50 px/тик, стена толщиной 8 px на её пути в 30 px впереди. Дискретно: за тик сдвиг 50 > (30+8) → следующая позиция уже за стеной, перекрытия в момент проверки нет → туннель. Swept: t_entry = 30/50 = 0.6 ∈ [0,1] → контакт на 60% шага, пуля честно останавливается в стене. Фикс-шаг поменьше (скажем, 4 подшага по 12.5 px) тоже ловит стену — но дороже.
🕹 В какие игры поиграть — и что заметить
От «коллизия в одну строку» до пиксельно-точной интеджерной физики платформеров. По каждому кейсу: как сделано и что спровоцировать, чтобы увидеть модель столкновений руками.
Мяч и ракетка — фактически AABB, но интересно поведение после контакта: угол отскока часто зависит от того, куда по ракетке попал мяч (край даёт круче угол) — это уже не чистая физика, а дизайнерский слой поверх простого теста перекрытия.
🎮 Сыграй: в Pong бей мяч краем ракетки против центра — сравни углы отскока. Проверь, ракетка — это отрезок или прямоугольник: лови мяч у самого торца.
Пуля против пришельца — перекрытие коробок; пришельцы стоят сеткой, так что проверка идёт по адресуемым ячейкам, а не «каждая пуля против каждого». Щиты — битмап, который выгрызается попиксельно: контакт пули со щитом стирает пиксели. Два разных режима столкновений в одной игре: грубый (AABB по сетке) и точный (битмаска по щиту).
🎮 Сыграй: постреляй в щит под углом — он деградирует попиксельно, дырами неправильной формы. Это пиксельная маска, а не AABB. А попадание по пришельцу — мгновенное «коробка в коробку».
Марио — AABB против тайловой сетки. Классика жанра: раздельное разрешение X и Y (иначе зацеп за стыки плиток), проверка «головой снизу вверх» для удара по блоку и «ногами» для приземления. На больших скоростях вылезает туннелирование — спидранеры протискиваются сквозь тонкие стены, разогнавшись.
🎮 Сыграй: в SMB разгонись на спуске и влетай в угол блока — почувствуешь, как игра «доводит» тебя по одной оси за раз. Глянь спидран-трюки с проходом сквозь стену — это туннелирование на высокой скорости.
Мэдди Торсон строит коллизии на целых пикселях: объект двигают по одному пикселю за раз с накоплением дробного остатка, на каждом шаге — простой AABB-тест против тайлов. Это анти-туннелирование «в лоб» (сдвиг ограничен пикселем) + детерминизм (целые координаты), на котором держатся фрейм-перфектные трюки и TAS.
🎮 Сыграй: в Celeste обрати внимание на «прощающие» коллизии у краёв (corner-correction подталкивает тебя мимо угла). Это поверх честного попиксельного AABB — дизайнерский слой, как угол отскока в Pong.
Где AABB не хватает — наклоны и петли. Sonic не коробка: у него сенсоры (лучи вниз/в стороны), которые читают высотные массивы тайлов (для каждого тайла — профиль высоты). Это уже не «перекрытие коробок», а «зондирование поверхности» — цена за скорость и рельеф, которых плоский AABB не даёт.
🎮 Сыграй: прокатись по петле/склону в Sonic — заметь, как он прилипает к поверхности под любым углом. Коробка так не умеет; это сенсоры по высотным картам тайлов.
Хардкор · теория: SAT, сумма Минковского и время контактаможно пропустить
AABB-тест — частный случай теоремы о разделяющей оси (SAT): два выпуклых тела не пересекаются ⇔ существует ось, на которую их проекции не перекрываются. Для произвольных выпуклых многоугольников осей-кандидатов — нормали всех граней обоих тел; для AABB нормали вырождаются в X и Y, поэтому осей всего две и тест так дёшев.
Сумма Минковского — почему «коробка против коробки» = «точка против коробки»
Пересечение A и B эквивалентно тому, что начало координат лежит внутри разности Минковского A ⊖ B. Для двух AABB эта разность — снова AABB (со сложенными полуразмерами). Отсюда трюк: задачу «движущаяся коробка против стены» сворачивают в «движущаяся точка (центр) против раздутой стены», и swept-тест становится пересечением луча с AABB — классический slab-метод.
Slab-метод и время входа/выхода
Луч p + t·v против AABB: по каждой оси считаем интервал [t₁,t₂], на котором луч внутри «плиты» (slab) этой оси, и берём пересечение интервалов по всем осям:
Пересечение есть ⇔ t_enter ≤ t_exit и интервал задевает [0,1]. Ось, давшая максимум на входе, задаёт нормаль контакта — по ней гасят скорость, по другой скользят. Это и есть continuous collision detection (CCD) для AABB в чистом виде.
Хардкор · инженерия: broad-phase, пространственные индексы, детерминизмможно пропустить
- Broad-phase → narrow-phase. Сначала дешёвый консервативный отсев пар-кандидатов (AABB-перекрытие), потом дорогой точный тест только для выживших. Антипаттерн джуна — гонять точный тест на всех парах: это O(n²).
- Пространственный хэш / равномерная сетка. Раскладываешь объекты по клеткам; проверяешь только внутри клетки и соседних. Для объектов схожего размера это близко к O(n) и проще дерева. Тайловые коллизии — вырожденный случай, где сетка уже есть.
- Sweep-and-prune. Держишь объекты отсортированными по проекции на ось; пары-кандидаты — те, чьи интервалы перекрываются. Хорошо работает при «временной когерентности» (кадр к кадру мало что меняется).
- Иерархии для разнокалиберного. Когда размеры объектов сильно разные (сетка плоха), берут BVH из AABB — тот же примитив, но в дереве. Прямой мост к рендеру (frustum/occlusion culling) и трассировке лучей.
- Детерминизм. Интеджерная/fixed-point коллизия + фиксированный порядок разрешения пар = воспроизводимый результат для лок-степа и реплеев. Float и недетерминированный порядок ломают сетевую синхронизацию.
ML / AI (твой домен): перекрытие AABB — это дословно IoU (intersection-over-union) в детекции объектов; NMS (non-max suppression) гасит рамки по тому же тесту пересечения. Пространственный хэш ⇄ ANN / LSH: бакетируешь векторы и сравниваешь только внутри бакета. Broad→narrow ⇄ coarse-to-fine retrieval: дешёвый ANN-кандидатогенератор, затем точный реранк — та же двухфазность, что broad/narrow-phase.
Системы / БД: пространственные индексы (R-tree, geohash, quadtree) для гео-запросов; «проверь только соседние клетки» = bucketing/sharding по ключу диапазона.
Графика / геометрия: BVH и frustum-culling в рендере и трассировке лучей — те же AABB в дереве; collision и visibility решают одной структурой.
Принцип: никогда не считай дорогой точный тест на всех парах. Сначала дешёвый консервативный фильтр (он может ошибаться только в сторону «возможно»), потом точный — на выживших.
move_and_slide по осям и понаблюдай за разрешением. Затем сломай: задери скорость объекта и убери CCD/малый шаг — поймаешь туннелирование сквозь тонкую плитку. Верни маленький фиксированный шаг — туннель исчезнет.dt на просадке его провоцирует.Почему X и Y разрешают раздельно, а не сразу обе?
AABB-перекрытие и IoU в детекции объектов — это правда одна формула?
max(0, minMaxX−maxMinX) · max(0, …Y…). IoU = это пересечение, делённое на объединение. То есть тест «перекрылись?» из коллизий — это числитель IoU. NMS в детекторах выкидывает рамки с высоким IoU к уже принятой — буквально тот же геометрический примитив, что выталкивание в физике. Разные домены, одна геометрия выровненных коробок.Туннелирование — это про коллизию или про игровой цикл?
dt шаг разбухает и туннель возвращается — поэтому фикс-шаг и коллизии — один разговор.Объект повернули на 30° — почему AABB вдруг врёт?
Когда тайловая сетка хуже дерева (BVH/quadtree)?
- Christer Ericson, «Real-Time Collision Detection» — индустриальная библия: AABB, SAT, swept-тесты, BVH.
- Rodrigo Monteiro, «Higher-Order Fun: 2D collision with tile maps» — классический разбор тайловой коллизии и раздельного разрешения осей.
- Maddy Thorson, «Celeste & TowerFall Physics» — попиксельная интеджерная коллизия на практике.
- Модуль 1, «Sprite Rendering / Collision» (
01-foundations-1970-1985.md).