Разрешение коллизий по осям
Иногда нужно не просто узнать о столкновении, а правильно остановить героя у стены. Разрешение коллизий по осям — приём, который спасает платформеры.
Суть: чтобы герой не проходил сквозь стены, движение разбивают по осям: сначала двигаем по X и гасим столкновения, потом по Y. Это убирает «застревания в углах».
Обнаружить столкновение — половина дела. Вторая половина — что с ним делать. Если герой въехал в стену, мало знать «есть коллизия», нужно аккуратно выдвинуть его обратно, чтобы он встал вплотную, а не застрял внутри или не проскочил насквозь. Это называется разрешением коллизий, и для платформеров есть проверенный приём.
Хитрость в том, чтобы двигать героя по одной оси за раз. Сначала сдвигаем по горизонтали (X) и проверяем столкновения со стенами: если въехали — выравниваем героя по краю стены (правым боком к левой грани стены или наоборот). Потом отдельно двигаем по вертикали (Y) и так же гасим. Почему по очереди? Потому что так мы точно знаем, с какой стороны произошёл контакт, и можем выдвинуть героя именно по этой оси, не ломая другую.
Если двигать сразу по обеим осям и пытаться разрулить, герой будет «цепляться» за углы и дёргаться. Раздельное разрешение — стандартное и надёжное решение, его используют в большинстве 2D-платформеров.
Как работает под капотом
Двигаем по X, проверяем стены, выдвигаем по X. Затем то же по Y. При движении вправо и столкновении — правый край героя становится левым краем стены:
шаг 1: двигаем по X
[герой]-->||стена||
столкновение -> herой.right = стена.left
[герой]||стена|| (встал вплотную)
шаг 2: двигаем по Y
[герой]
|
v падение
===пол===
столкновение -> герой.bottom = пол.top
В pygame (читаем):
# движение по X
player.x += vel.x * dt
for wall in walls:
if player.colliderect(wall):
if vel.x > 0: player.right = wall.left # ехал вправо
if vel.x < 0: player.left = wall.right # ехал влево
# движение по Y (отдельно!)
player.y += vel.y * dt
for wall in walls:
if player.colliderect(wall):
if vel.y > 0: player.bottom = wall.top # падал
if vel.y < 0: player.top = wall.bottom # летел вверхЛогику разрешения по одной оси можно проверить без графики. Подвинем героя в стену по X и выдвинем обратно. Попробуй сам:
def resolve_x(player, wall, vx):
# player и wall: списки [left, right]
if player[1] > wall[0] and player[0] < wall[1]: # пересеклись
if vx > 0: # ехал вправо
width = player[1] - player[0]
player = [wall[0]-width, wall[0]] # right = wall.left
print("упёрся в стену слева от неё")
return player
player = [90, 130] # left=90, right=130, ширина 40
wall = [120, 200] # стена слева 120
player = resolve_x(player, wall, vx=200)
print("позиция героя [left, right]:", player)Туннелирование на больших скоростях
У пошагового разрешения есть враг — туннелирование. Если объект движется очень быстро, за один кадр он может перепрыгнуть тонкую стену целиком: в начале кадра он перед стеной, в конце — уже за ней, а в момент проверки пересечения не было. Особенно страдают быстрые пули и падение с большой высоты. Поэтому тонкие стены и быстрые объекты — опасное сочетание, о котором стоит помнить заранее.
Лечат это по-разному. Простой способ — делать стены потолще, чтобы за кадр их было не перескочить. Надёжнее — разбивать большой шаг движения на несколько маленьких подшагов и проверять коллизию после каждого, как будто кадр стал «дробнее». Для самых быстрых объектов применяют непрерывную проверку — луч (raycast) по траектории движения, ищущий первое пересечение. Начинающим достаточно знать о проблеме и держать стены разумной толщины; продвинутые решения пригодятся, когда в игре появятся по-настоящему быстрые объекты.
Частые ошибки
- Двигать сразу по двум осям и разруливать — герой цепляется за углы и дёргается.
- Выдвигать не по той оси — герой «телепортируется» вбок при падении.
- Не учитывать направление скорости — непонятно, к какой грани прижимать.
Best practices
- Всегда разрешай коллизии по осям раздельно: сначала X, потом Y.
- Прижимай героя к грани стены по направлению его движения.
- После Y-разрешения при падении ставь
on_ground = Trueдля прыжка.
Итог: двигай по одной оси, проверяй, выдвигай — потом вторая ось. Этот приём убирает застревания и делает столкновения со стенами надёжными.