Коллизии спрайтов и групп

Проверять каждого с каждым вручную — утомительно. Pygame умеет находить столкновения между спрайтом и целой группой одной командой.
Суть: spritecollide ищет спрайты группы, столкнувшиеся с одним спрайтом, а groupcollide — между двумя группами. Флаг dokill автоматически удаляет столкнувшихся.

В прошлом уроке мы проверяли два прямоугольника. Но в реальной игре герой летит сквозь десятки монет, врагов и пуль. Писать вложенные циклы «каждый с каждым» руками — скучно и легко ошибиться. Pygame даёт готовые функции для коллизий со спрайт-группами.

pygame.sprite.spritecollide(sprite, group, dokill) возвращает список спрайтов из группы, которые столкнулись с заданным спрайтом. Например, какие монетки собрал герой. Флаг dokill=True сразу удалит эти монетки из всех групп — очень удобно. groupcollide(group_a, group_b, dokill_a, dokill_b) ищет столкновения между двумя группами: например, какие пули попали в каких врагов, и убирает и пули, и врагов.

Эти функции внутри используют ту же AABB-проверку, что мы разобрали, но избавляют тебя от ручных циклов. Один вызов — и ты знаешь весь список столкновений за кадр.

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

spritecollide пробегает по группе и для каждого спрайта делает colliderect с твоим. Собирает попавших в список. groupcollide делает это для всех пар из двух групп:

   spritecollide(player, coins, dokill=True)

   player  vs  [coin1] [coin2] [coin3] [coin4]
                  X       .       X       .
                  |               |
                  collide!        collide!
                  |               |
         вернёт [coin1, coin3], их же удалит

В pygame (читаем):

# собираем монетки
hits = pygame.sprite.spritecollide(player, coins, True)
score += len(hits)            # сколько собрали за кадр

# пули против врагов: удаляем и пули, и врагов
kills = pygame.sprite.groupcollide(bullets, enemies, True, True)
score += len(kills) * 10

Логику spritecollide легко воспроизвести на списках. Найдём, какие монетки собрал герой, и уберём их. Попробуй сам:

def aabb(a, b):
    return (a[0] < b[0]+b[2] and a[0]+a[2] > b[0] and
            a[1] < b[1]+b[3] and a[1]+a[3] > b[1])

player = (100, 100, 40, 40)
coins = [(110, 110, 20, 20), (300, 300, 20, 20), (130, 90, 20, 20)]

collected = [c for c in coins if aabb(player, c)]
coins = [c for c in coins if c not in collected]   # dokill=True

print("собрано монеток:", len(collected))
print("осталось на поле:", len(coins))

Точные хитбоксы через collided

У функций spritecollide и groupcollide есть необязательный аргумент collided — функция, которой они проверяют каждую пару. По умолчанию это прямоугольная AABB-проверка, но можно подставить pygame.sprite.collide_circle (по окружностям) или pygame.sprite.collide_mask (по форме спрайта). Так ты переключаешь точность столкновений, не переписывая остальной код, — одна и та же функция работает и грубо-быстро, и точно-аккуратно.

Часто хитбокс намеренно делают меньше картинки: игроку психологически приятнее, когда «впритык» не считается попаданием, а вражеская пуля чуть промахивается. Это называется «прощающие» хитбоксы, и они — секрет того, почему хорошие игры ощущаются честными. Уменьшить рамку легко через rect.inflate(-10, -10). Запомни: коллизии — это не только про техническую правильность, но и про ощущение справедливости. Игрок не видит твоих прямоугольников, но прекрасно чувствует, когда игра придирается, а когда прощает мелкие неточности.

Заведи привычку проектировать группы под коллизии заранее. Если пули должны бить врагов, но не друг друга, держи их в разных группах и сталкивай через groupcollide. Если монетки собираются игроком, но не врагами, проверяй spritecollide(player, coins, True) — и только с игроком. Продуманная раскладка по группам превращает сложные правила взаимодействий в пару понятных строк и избавляет от вложенных циклов с проверками «свой-чужой». Возвращаемый список столкновений — твой главный источник истины за кадр: по нему начисляют очки, проигрывают звуки, спавнят частицы. Думай о коллизиях не как о «проверке True/False», а как о потоке событий, который каждый кадр рассказывает, что в мире столкнулось.

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

  • Забыть про dokill — монетка остаётся на поле и собирается каждый кадр, счёт взлетает.
  • Перепутать порядок групп в groupcollide — флаги dokill_a и dokill_b относятся к первой и второй группе соответственно.
  • Игнорировать возвращаемый список — именно в нём лежит, что и с чем столкнулось.

Best practices

  • Используй dokill, чтобы не чистить группы вручную.
  • Для точных хитбоксов передавай collided=pygame.sprite.collide_mask (по форме спрайта).
  • Считай очки по длине возвращённого списка — это надёжнее, чем счётчики вручную.

Итог: spritecollide и groupcollide заменяют ручные циклы одной строкой и сами умеют удалять столкнувшихся. Это рабочий инструмент любой аркады.

Проверьте себя
1. Что делает флаг dokill=True в spritecollide?
AЗакрывает игру
BУдаляет столкнувшиеся спрайты из всех групп
CОстанавливает звук
DУдваивает счёт
2. Что возвращает pygame.sprite.spritecollide(player, coins, ...)?
ATrue или False
BСписок спрайтов из coins, столкнувшихся с player
CКоличество групп
DКоординаты player