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

Скорострельность и интервал между выстрелами

Удержание пробела стреляет с ограниченной частотой — в секундах, а не в кадрах.

Раздел 21.4 стрелял по событию KEYDOWN — оно происходит ровно один раз на одно физическое нажатие пробела, поэтому даже долгое удержание клавиши там давало только один выстрел. Финальной версии игры нужна стрельба очередями, пока пробел зажат, а для этого требуется другой источник данных — pygame.key.get_pressed(), которая на каждом обновлении сообщает, зажата ли клавиша прямо сейчас. Без ограничений это означало бы выстрел на каждом обновлении игры, то есть скорострельность, зависящую от FPS: 60 выстрелов в секунду при 60 FPS и 120 — при 120 FPS. Поэтому стрельба через get_pressed() всегда сопровождается минимальным интервалом между выстрелами (cooldown, в профессиональной речи — кулдаун), заданным в секундах.

fragment_fire_cooldown.py
FIRE_INTERVAL = 0.20   # секунд между выстрелами

self.fire_cooldown = max(0.0, self.fire_cooldown - dt)

if keys[pygame.K_SPACE] and self.fire_cooldown <= 0.0:
    self._spawn_bullet()
    self.fire_cooldown = FIRE_INTERVAL
Интервал измеряется в секундах, а не в кадрах
Если бы вместо fire_cooldown -= dt счётчик уменьшался на единицу каждый кадр (kadrov_do_vystrela -= 1), скорострельность зависела бы от FPS точно так же, как нескорректированное движение из раздела 20.16: на 120 FPS корабль стрелял бы вдвое чаще, чем на 60 FPS, при одном и том же коде.

Почему здесь не используется while, как в таймере появления врагов

Раздел 21.14 покажет финальный таймер появления врагов: он продвигает время через += interval, сохраняя остаток, а while там нужен на случай, если один обрабатываемый шаг способен охватить сразу несколько интервалов. Интервал между выстрелами устроен иначе:

Таймер появления враговИнтервал между выстрелами игрока
Источник событияАвтономная симуляция (сама игра решает, когда)Ввод игрока (человек решает, когда)
Долгий обрабатываемый шагЕсли шаг способен охватить несколько интервалов, таймер может обработать несколько событий подрядне должен выстрелить сразу несколько пуль одним кадром
Приём в кодеwhile: продолжать, пока накоплено времяif: не больше одного выстрела за кадр
В текущей игре второй столбец не наступает
MAX_DT не позволяет одному обработанному шагу стать длиннее минимального интервала появления врагов (подробности — в разделе 21.14) — поэтому в этой конкретной игре ни пауза отладчика, ни зависание ОС не приводят к тому, что игра «наверстывает» всё накопленное реальное время залпом из нескольких врагов сразу.
Это осознанный выбор, а не недосмотр
После действительно долгого кадра (пауза отладчика, зависание ОС) внезапный залп из десяти пуль ощущался бы как ошибка, а не как честное поведение — поэтому интервал между выстрелами игрока нарочно даёт максимум один новый выстрел за одно обновление, даже если dt оказался большим.
Практика: интервал между выстрелами
Проверяется прямо в браузере — установка Pygame не требуется
Практика выполняется локально
Открыть практику →