Глава 21 · Проект: космический шутер
Движение корабля и появление врагов
Скорость в пикселях в секунду с самого начала — и враги, появляющиеся сверху с течением времени.
Перемещаем космический корабль
Управление — через get_pressed() из главы 20 (непрерывное
движение, пока клавиша зажата). Скорость сразу задана в пикселях в секунду (px/s), а
позиция корабля хранится отдельным дробным числом — Rect
понимает только целые координаты, и обновлять его напрямую по чуть-чуть на каждом кадре
значило бы терять дробную часть движения:
dvizhenie_korablya.py
KORABL_SKOROST = 260.0 # px/s
korabl_x = float(korabl.x) # дробная копия позиции — сам Rect хранит только целые числа
def obrabotat_klavishi(korabl_x, klavishi, dt):
napravlenie = klavishi[pygame.K_RIGHT] - klavishi[pygame.K_LEFT]
korabl_x += napravlenie * KORABL_SKOROST * dt
return max(0.0, min(korabl_x, SHIRINA - KORABL_SHIRINA))
# в игровом цикле:
korabl_x = obrabotat_klavishi(korabl_x, klavishi, dt)
korabl.x = round(korabl_x)
max/min — тот же приём ограничения, что и в главе 20
max(0.0, min(korabl_x, SHIRINA - KORABL_SHIRINA)) гарантирует, что korabl_x никогда не выйдет за пределы [0, SHIRINA - KORABL_SHIRINA] — тот же самый приём «зажимания» значения в диапазон, что мы использовали для прыгающего мяча.napravlenie — число -1, 0 или 1
klavishi[pygame.K_RIGHT] - klavishi[pygame.K_LEFT] — булевы значения True/False ведут себя как 1/0 (глава 4), поэтому разность даёт ровно то, что нужно: 1, если зажата только правая стрелка, −1, если только левая, и 0, если обе или ни одна. Раздел 21.11 обобщит этот же приём на два измерения сразу через Vector2.Создаём и перемещаем врагов
Враг тоже появится позже как отдельный спрайт-класс (раздел 21.9), а пока — словарь с
Rect и дробной координатой Y, по той же причине, что и у корабля.
Врагов будет много, и они появляются со временем — значит, нужен список (глава 11) и таймер
в секундах до следующего появления:
vragi.py
VRAG_SHIRINA, VRAG_VYSOTA = 32, 28
VRAG_SKOROST = 150.0 # px/s
INTERVAL_POYAVLENIYA_VRAGA = 0.75 # секунд между новыми врагами
vragi = []
vremya_do_vraga = INTERVAL_POYAVLENIYA_VRAGA
def sozdat_vraga():
x = random.randint(0, SHIRINA - VRAG_SHIRINA)
return {{
"rect": pygame.Rect(x, -VRAG_VYSOTA, VRAG_SHIRINA, VRAG_VYSOTA),
"y": float(-VRAG_VYSOTA),
}}
# в игровом цикле, каждый кадр:
vremya_do_vraga -= dt
if vremya_do_vraga <= 0.0:
vragi.append(sozdat_vraga())
vremya_do_vraga += INTERVAL_POYAVLENIYA_VRAGA
for vrag in vragi:
vrag["y"] += VRAG_SKOROST * dt
vrag["rect"].y = round(vrag["y"])
y = -VRAG_VYSOTA — враг появляется чуть выше экрана
Отрицательная стартовая координата Y означает, что враг рождается чуть выше видимой области и плавно «въезжает» в кадр сверху — а не появляется резко посередине экрана.
+= interval, а не = interval — вот что сохраняет остаток
После появления врага мы прибавляем интервал к текущему значению таймера, а не присваиваем интервал заново. Поэтому небольшое превышение времени не теряется: если
vremya_do_vraga ушёл в −0.01, следующий отсчёт начнётся именно с этого небольшого долга, а не с чистого интервала. В разделе 21.14 этот же принцип будет обобщён с помощью while на случай, когда один обрабатываемый шаг может охватить сразу несколько интервалов.Практика: движение корабля и врагов
Pygame открывает нативное окно Python — выполните локально в VS Code, PyCharm или Jupyter
Практика выполняется локально