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

Разделяем игру на классы

Пять классов с чёткими обязанностями — вместо одних глобальных переменных и списков.

Исторический код разделов 21.1–21.7 хранит корабль, врагов и пули как отдельные переменные и списки Rect, а всю логику — прямо в теле игрового цикла. Это работает для маленькой игры, но плохо растёт: чем больше правил, тем труднее понять, какой код за что отвечает. Финальная версия делит игру на пять классов с чёткими обязанностями.

Gamescreenclockassetsplayerbulletsenemiesexplosionsstatescoreliveshandle_events()handle_input(dt)update(dt)render()run()
Game владеет всем состоянием игры и координирует остальные классы — сам он не рисует врага и не двигает пулю напрямую.
GamePlayerВЛАДЕЕТ
Game хранит один экземпляр Player.
GameГруппы спрайтовВЛАДЕЕТ
Game хранит по одной pygame.sprite.Group для Bullet, Enemy и Explosion.

Bullet, Enemy и Explosion — наследники pygame.sprite.Sprite (раздел 20.19 уже показал, зачем спрайты нужны: у объекта появляются .image и .rect, которые понимают готовые функции столкновений и группы). Хранить их в pygame.sprite.Group, а не в обычном списке, удобно по той же причине, что и в разделе 21.6: группа сама умеет удалять объект (.kill()) без риска «сломать» цикл перебора, а pygame.sprite.groupcollide() (раздел 21.15) проверяет столкновения между целыми группами одним вызовом.

fragment_game_init.py
class Game:
    def __init__(self, *, rng=None, debug=False):
        pygame.init()
        self.screen = pygame.display.set_mode((SHIRINA, VYSOTA))
        self.clock = pygame.time.Clock()
        self.assets = AssetStore(IMAGE_DIR, AUDIO_DIR)
        self.rng = rng if rng is not None else random.Random()

        self.state = GameStatus.MENU
        self.bullets = pygame.sprite.Group()
        self.enemies = pygame.sprite.Group()
        self.explosions = pygame.sprite.Group()
        self.player = self._make_player()
        self.score = 0
        self.lives = STARTING_LIVES
Это набросок, а не полный файл
Полный, готовый к запуску класс Game — в файле projects/pygame/space-shooter/space_shooter.py, который подробно разбирает раздел 21.25.
Эта архитектура — не универсальный закон
Разделение на Game/Player/Bullet/Enemy/Explosion — удобное решение именно для проекта такого размера, а не единственно правильный способ строить любую игру. Раздел 20.8 уже объяснял: у разных инструментов и проектов архитектура может сильно отличаться.
Практика: набросок класса Game
Pygame открывает нативное окно Python — выполните локально в VS Code, PyCharm или Jupyter
Практика выполняется локально
Открыть практику →