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

Ресурсы игры: графика и звук

Настоящие спрайты вместо прямоугольников — и класс, который загружает их ровно один раз.

До сих пор корабль, враги и пули рисовались простыми прямоугольниками и кругами — удобно для первых шагов (раздел 20.3). Финальная версия использует собственные спрайты, нарисованные специально для этого проекта, вместо геометрических заглушек.

Спрайты проекта

Реальное окно: маленький оранжевый враг-разведчик слева и более крупный розово-красный враг-истребитель справа, синий корабль игрока снизу
Три собственных спрайта одновременно: разведчик (scout), истребитель (fighter) и корабль игрока — разные силуэты и цвета, чтобы враги считывались с одного взгляда.

Все изображения сохранены как файлы .png с прозрачным фоном. При загрузке они попадают в Surface, которая поддерживает альфа-канал (флаг pygame.SRCALPHA при создании такой Surface, раздел 20.17, — это свойство самой Surface в pygame, а не PNG-файла), поэтому прозрачные пиксели остаются прозрачными и на экране. Спрайты нарисованы напрямую через pygame.draw в отдельном скрипте scripts/generate_chapter_21_assets.py, а не скачаны откуда-то — простые геометрические фигуры, без единого существующего коммерческого спрайта.

ФайлРольОсобенность
player_ship.pngКорабль игрокаСтреловидный силуэт, светлая кабина, свечение двигателя
enemy_scout.pngБыстрый, дешёвый врагМаленький, оранжево-красный треугольник
enemy_fighter.pngМедленный, дорогой врагКрупнее, пурпурно-красный, с боковыми модулями
bullet.pngСнаряд игрокаМаленькая яркая жёлтая капсула
explosion_sheet.pngАнимация взрыва6 кадров в ряд — раздел 21.20 режет их через subsurface()

AssetStore — загрузка один раз

Раздел 20.24 уже предупреждал: загрузка файла внутри игрового цикла — частая причина просадок кадров. Здесь всё загружается один раз в момент создания игры, в отдельном классе:

fragment_asset_store.py
class AssetStore:
    def __init__(self, image_dir, audio_dir):
        self.images = {{}}
        for key in ("player_ship", "enemy_scout", "enemy_fighter", "bullet"):
            self.images[key] = pygame.image.load(image_dir / f"{{key}}.png").convert_alpha()

        self.sounds = {{}}
        for key in ("laser", "explosion", "player_hit"):
            path = audio_dir / f"{{key}}.wav"
            try:
                self.sounds[key] = pygame.mixer.Sound(str(path))
            except (pygame.error, FileNotFoundError):
                self.sounds[key] = None
convert_alpha() — тот же смысл, что и в главе 20
pygame.image.load(...) уже сохраняет прозрачность загруженного PNG сама по себе — .convert_alpha() не «создаёт» прозрачность, а лишь готовит копию в формате пикселей экрана для быстрой повторной отрисовки (раздел 20.19). Здесь она вызывается сразу после загрузки, пока изображений мало и это не нужно откладывать.
Звук — необязательное условие для запуска игры
Если в системе нет звукового устройства (например, при автоматическом тестировании), pygame.mixer.Sound(...) может завершиться ошибкой — тогда self.sounds[key] остаётся None, а игра всё равно запускается и работает, просто без звука. Раздел 21.21 показывает, как проигрывание учитывает это на каждом вызове.