Коллизии спрайтов и групп
Проверять каждого с каждым вручную — утомительно. 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 заменяют ручные циклы одной строкой и сами умеют удалять столкнувшихся. Это рабочий инструмент любой аркады.