Глава 21 · Проект: космический шутер

Жизни, урон и временная неуязвимость

Столкновения и побег врага — два разных источника потери жизней с разными правилами.

Раздел 21.6 заканчивал игру от одного столкновения корабля с врагом. Финальная версия вместо этого считает жизни — но здесь есть менее очевидная ловушка: что, если сразу несколько врагов столкнутся с кораблём в один и тот же момент?

fragment_player_damage.py
def resolve_enemy_player_collisions(self):
    hit = pygame.sprite.spritecollide(self.player, self.enemies, True)
    for vrag in hit:
        self._spawn_explosion(vrag.rect.center)
    return len(hit) > 0

# в обновлении кадра:
collided = self.resolve_enemy_player_collisions()
if collided and not self.player.is_invulnerable:
    self.lives -= 1
    self.player.take_hit()
Три врага одновременно — минус одна жизнь, а не минус три
spritecollide() удаляет все столкнувшиеся вражеские спрайты безусловно — но урон кораблю отдельная проверка списывает не более одной жизни за одно обновление кадра, независимо от того, сколько врагов задели корабль одновременно. Без этого разделения три одновременно налетевших врага отняли бы три жизни за один кадр, что ощущалось бы несправедливо резко.

Временная неуязвимость

После удара включается короткое окно неуязвимости — во время него столкновения продолжают убирать врагов, но больше не отнимают жизни:

fragment_invulnerability.py
PLAYER_INVULNERABLE_SECONDS = 1.2

@property
def is_invulnerable(self):
    return self.invulnerable_timer > 0.0

def update_timers(self, dt):
    self.invulnerable_timer = max(0.0, self.invulnerable_timer - dt)

def take_hit(self):
    self.invulnerable_timer = PLAYER_INVULNERABLE_SECONDS
Реальное окно: тонкое голубое кольцо вокруг корабля игрока — визуальный индикатор временной неуязвимости
Реальное окно — ровное кольцо вокруг корабля, а не мигание: спокойный, не раздражающий индикатор неуязвимости.
Ровное кольцо вместо мигания
Мигающий спрайт — распространённый приём, но при быстром морганье он утомляет глаза. Здесь вместо этого — постоянное кольцо вокруг корабля, которое просто исчезает, когда invulnerable_timer достигает нуля.

Побег врага за нижнюю границу

У игры есть второй, отдельный источник потери жизней. Враг считается сбежавшим, только когда его спрайт целиком пересёк нижнюю границу игрового поля (vrag.rect.top > self.playfield.bottom). Каждый такой враг удаляется и стоит игроку одну жизнь:

fragment_enemy_escapes.py
def resolve_enemy_escapes(self):
    escaped = [
        vrag for vrag in self.enemies
        if vrag.rect.top > self.playfield.bottom
    ]
    for vrag in escaped:
        vrag.kill()
    return len(escaped)

# в обновлении кадра:
self.lives -= self.resolve_enemy_escapes()
Ограничение «одна жизнь за кадр» относится только к столкновениям
Побег — не удар по кораблю, поэтому временная неуязвимость его не блокирует. Если в одном обновлении поле покинули два врага, игра списывает две жизни; если жизней не осталось, состояние переходит в GAME_OVER. Это намеренное правило: столкнувшиеся одновременно враги образуют одно событие удара, а каждый пропущенный враг — отдельный результат, который учитывается ровно один раз.
Практика: урон и неуязвимость
Проверяется прямо в браузере — установка Pygame не требуется
Практика выполняется локально
Открыть практику →