*P.S.

Я всё ещё школьник, поэтому ждать красивого кода не стоит, но если тебе хочется погрузиться в мир видеоигр с обратной стороны, эта статья должна показаться тебе хоть немного интересной.

Этот подробный гайд по созданию простецкой игры на Python. Всё это делать мы будем конечно в пучарме «PyCharm». Его настройки в этой статье не будет, всё это уже давно есть ру(ю)тубе.

Подготовка к разработке: Установка Pygame и организация проекта

Прежде чем мы начнем, необходимо настроить окружение для разработки. Мы будем делать игру с помощью pygame.

  • Установка Pygame.

    • Проверьте установлен ли у вас Python.

    • Открываем терминал и пишем: pip install pygame

  • Структура проекта. Для удобства организации кода, создайте структуру папок и файлов:

    проект/
        ├── img/           # Папка для изображений (спрайтов)
        ├── sound/         # Папка для звуков
        ├── config.py      # Файл с настройками игры
        ├── objects.py     # Файл с классами игровых объектов
        └── main.py        # Основной файл игры
  • Файл config.py – Сердце настроек. Создадим файл config.py и поместим туда основные настройки игры. Это упростит изменение параметров в будущем.

    # config.py
    from os import path
    
    WIDTH = 480  # Ширина экрана
    HEIGHT = 600  # Высота экрана
    FPS = 60  # Частота кадров в секунду (FPS)
    # --- Цвета ---
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)
    RED = (255, 0, 0)
    GREEN = (0, 255, 0)
    BLUE = (0, 0, 255)
    YELLOW = (255, 255, 0)
    # --- Пути к ресурсам ---
    img_dir = path.join(path.dirname(__file__), 'img')  # Путь к папке img
    sound_dir = path.join(path.dirname(__file__), 'sound')  # Путь к папке sound
    # --- Настройки Босса ---
    BOSS_LASER_SPEED = 5
    BOSS_LASER_COLOR = RED
    • Перевожу со змеиного на человеческий

      • WIDTHHEIGHT: Определяют размеры окна игры.

      • from os import path: Импортируем модуль path. Мы будем иметь доступ к переменным из этого файла во всём проекте.

      • FPS: Определяет скорость обновления экрана. Более высокое значение – более плавная анимация, но требует больше ресурсов.

      • Цвета: Задаем цвета в формате RGB (Красный, Зеленый, Синий).

      • img_dirsound_dir: Эти переменные определяют пути к папкам с изображениями и звуками, что упрощает доступ к ресурсам в коде. path.join используется для корректного определения пути в зависимости от операционной системы.

Основные элементы игры: Спрайты и управление

Теперь перейдем к созданию игровых объектов и управлению ими. В Pygame все, что мы видим на экране (игрок, враги, пули), будет представлено спрайтами.

  • Класс Entity. Общие свойства задаются в классах Player и Mob. Иными словами, это общий “скелет” для всего.

    • Что нужно знать:

      • Спрайт – это изображение, которое можно перемещать по экрану.

      • pygame.sprite.Sprite – базовый класс для спрайтов в Pygame.

      • image: Атрибут, в котором хранится изображение спрайта.

      • rect: Атрибут, который представляет прямоугольник, окружающий изображение. Используется для определения положения спрайта и обнаружения коллизий.

      • xy: Координаты верхнего левого угла прямоугольника rect.

      • speed: Скорость перемещения объекта.

  • Класс Player (определение игрока). Класс Player (в objects.py) наследует (или содержит) общие свойства и добавляет специфичную для игрока логику:

    # objects.py
    import pygame
    from config import *  # Импортируем настройки из config.py
    
    class Player(pygame.sprite.Sprite):  # Наследуемся от pygame.sprite.Sprite
        def __init__(self, all_sprites, bullets):
            pygame.sprite.Sprite.__init__(self)
            player_img = pygame.image.load(path.join(img_dir, "player.png")).convert_alpha()
            self.image = pygame.transform.scale(player_img, (50, 50))  # Масштабируем изображение
            self.image.set_colorkey(BLACK)  # Убираем фон (делаем прозрачным)
            self.rect = self.image.get_rect()  # Получаем прямоугольник спрайта
            self.rect.centerx = WIDTH / 2  # Начальная позиция по x (по центру)
            self.rect.bottom = HEIGHT - 10  # Начальная позиция по y (внизу)
            self.speedx = 0  # Горизонтальная скорость (изначально 0)
            self.all_sprites = all_sprites
            self.bullets = bullets
    
        def update(self):  # Метод, вызываемый каждый кадр
            self.speedx = 0  # Сбрасываем скорость
            keystate = pygame.key.get_pressed()  # Получаем состояние всех клавиш
            if keystate[pygame.K_LEFT]:
                self.speedx = -8  # Двигаемся влево, если нажата клавиша влево
            if keystate[pygame.K_RIGHT]:
                self.speedx = 8  # Двигаемся вправо
            self.rect.x += self.speedx  # Обновляем позицию
            if self.rect.right > WIDTH:  # Не даем вылезти за правую границу
                self.rect.right = WIDTH
            if self.rect.left < 0:  # Не даем вылезти за левую границу
                self.rect.left = 0
    
        def shoot(self):  # Метод для выстрела
            bullet = Bullet(self.rect.centerx, self.rect.top)  # Создаем пулю
            self.all_sprites.add(bullet)  # Добавляем пулю в общую группу
            self.bullets.add(bullet)  # Добавляем пулю в группу пуль
            laser_sound.play()  # Проигрываем звук выстрела
    • Что делает код?

      • init(): Конструктор класса. Загружает изображение игрока, масштабирует его, убирает фон (делает прозрачным), устанавливает начальную позицию и скорость.

      • update(): Метод, вызываемый каждый кадр для обновления состояния игрока:

        • Сбрасывает горизонтальную скорость (чтобы игрок останавливался, когда клавиши не нажаты).

        • Получает состояние клавиш.

        • Устанавливает скорость в зависимости от нажатых клавиш (влево/вправо).

        • Обновляет позицию игрока.

        • Ограничивает движение игрока по краям экрана.

      • shoot(): Создает пулю и добавляет ее в группы спрайтов.

  • Обработка событий Pygame (игровой цикл). Теперь, в main.py, необходимо создать основной игровой цикл и обрабатывать события.

    # main.py
    import pygame
    from config import *
    from objects import *
    
    # --- Инициализация ---
    pygame.init()
    pygame.mixer.init()  # Инициализация звука
    screen = pygame.display.set_mode((WIDTH, HEIGHT))  # Создаем окно игры
    pygame.display.set_caption("Space Shooter")  # Устанавливаем заголовок окна
    clock = pygame.time.Clock()  # Создаем объект для управления FPS
    
    # --- Загрузка изображений ---
    background = pygame.image.load(path.join(img_dir, "starfield.png")).convert_alpha()
    background_rect = background.get_rect()  # Получаем прямоугольник фона
    
    # --- Создание групп спрайтов ---
    all_sprites = pygame.sprite.Group()  # Группа для всех спрайтов
    mobs = pygame.sprite.Group()  # Группа для врагов
    bullets = pygame.sprite.Group()  # Группа для пуль
    boss_lasers = pygame.sprite.Group()  # Группа лазеров босса
    
    # --- Создание объектов ---
    player = Player(all_sprites, bullets)  # Создаем игрока
    all_sprites.add(player)  # Добавляем игрока в общую группу
    
    # --- Загрузка музыки и звуков ---
    pygame.mixer.music.load(path.join(sound_dir, 'Star_Wars_-_Rogue_One_-_OST_Izgojj-Odin_Zvjozdnye_Vojjny_65069199.mp3'))  # Загружаем музыку
    pygame.mixer.music.play(-1)  # Проигрываем музыку бесконечно
    laser_sound = pygame.mixer.Sound(path.join(sound_dir, 'blaster.mp3'))  # Звук выстрела
    
    # --- Состояние игры ---
    game_over = True  # Изначально игра не запущена
    
    # --- Стартовый экран (пока не используем) ---
    # start_screen() # Перенесем эту функцию позже
    
    # --- Основной игровой цикл ---
    def start_screen():
        # ... (реализация стартового экрана, как в коде) ...
        pass  # заглушка для начала, чтобы игра запустилась
    
    def reset_game(self):
        # ... (сброс состояния игры, создание новых объектов) ...
        pass  # заглушка для начала
    
    def run():  # Функция запуска игрового цикла
        nonlocal game_over
        while True:
            if game_over:
                if not start_screen():  # Отображаем стартовый экран, возвращаем False при выходе
                    return
    
                continue
    
            # --- Ограничение FPS ---
            clock.tick(FPS)
    
            # --- Обработка событий (ввод, выход) ---
            for event in pygame.event.get():
                if event.type == pygame.QUIT:  # Если нажали на крестик
                    pygame.quit()  # Выход из Pygame
                    return  # Завершаем функцию run()
                elif event.type == pygame.KEYDOWN:  # Нажата клавиша
                    if event.key == pygame.K_SPACE:  # Если пробел
                        player.shoot()
                    elif event.key == pygame.K_ESCAPE:
                        pygame.quit()
                        return  # Завершаем игру
    
            # --- Обновление спрайтов ---
            all_sprites.update()  # Вызываем update() для всех спрайтов
    
            # --- Отрисовка ---
            screen.fill(BLACK)  # Заполняем экран черным цветом
            screen.blit(background, background_rect)  # Рисуем фон
            all_sprites.draw(screen)  # Рисуем все спрайты
    
            # --- Обновление экрана ---
            pygame.display.flip()  # Переворачиваем буфер экрана, чтобы отобразить изменения
    
    def main():
        run()
    
    if __name__ == "__main__":
        main()
    • Что делает код?

      • Инициализирует Pygame.

      • Создает окно игры и устанавливает заголовок.

      • Создает объект для управления FPS (clock).

      • Загружает фоновое изображение.

      • Создает группы спрайтов:

        • all_sprites: Для всех спрайтов (игрок, враги, пули).

        • mobs: Для врагов.

        • bullets: Для пуль.

        • boss_lasers: Для лазеров босса.

      • Создает объект игрока.

      • Загружает музыку и звук выстрела.

      • Входит в основной игровой цикл run().

      • В игровом цикле:

        • clock.tick(FPS): Ограничивает частоту кадров.

        • pygame.event.get(): Получает все события (действия пользователя, такие как нажатия клавиш, закрытие окна).

        • for event in ...: Обрабатывает события.

          • pygame.QUIT: Если пользователь закрывает окно, игра завершается.

          • pygame.KEYDOWN: Если нажата клавиша:

            • K_SPACE: Игрок стреляет.

            • K_ESCAPE: Игрок выходит из игры.

        • all_sprites.update(): Вызывает метод update() для каждого спрайта в группе. Это обновляет положение и состояние спрайтов (например, движение игрока, движение пуль).

        • Отрисовка:

          • screen.fill(BLACK): Заливает экран черным цветом.

          • screen.blit(background, background_rect): Отображает фон.

          • all_sprites.draw(screen): Отрисовывает все спрайты в группе all_sprites. Pygame автоматически использует атрибут image спрайтов и их rect для отрисовки.

          • pygame.display.flip(): Обновляет (переворачивает) экран, чтобы отобразить все изменения.

Враги и коллизии

Теперь добавим врагов, чтобы сделать игру более интересной, и реализуем систему коллизий, чтобы определить, когда пули попадают во врагов, и когда игрок сталкивается с врагами.

  • Класс Mob (создание врагов). Создадим класс Mob (в objects.py) для представления врагов. Враги будут появляться сверху и двигаться вниз.

    # objects.py
    import pygame
    import random
    from config import *
    
    class Mob(pygame.sprite.Sprite):
        def __init__(self):
            pygame.sprite.Sprite.__init__(self)
            meteor_img = pygame.image.load(path.join(img_dir, "Ship.png")).convert_alpha()  # Загружаем изображение врага
            self.image = meteor_img #.convert() # Альтернатива convert_alpha()
            self.image.set_colorkey(BLACK)
            self.rect = self.image.get_rect()  # Получаем прямоугольник врага
            self.rect.x = random.randrange(WIDTH - self.rect.width)  # Случайная начальная позиция по x
            self.rect.y = random.randrange(-100, -40)  # Начальная позиция по y (за пределами экрана)
            self.speedy = random.randrange(1, 8)  # Случайная скорость падения
            self.speedx = random.randrange(-3, 3)  # Случайная скорость по x
    
        def update(self):  # Метод для обновления позиции врага
            self.rect.x += self.speedx # Двигаем по x
            self.rect.y += self.speedy  # Двигаем вниз
            if self.rect.top > HEIGHT + 10 or self.rect.left < -25 or self.rect.right > WIDTH + 20:
                # Если враг ушел за пределы экрана, перезапускаем его
                self.rect.x = random.randrange(WIDTH - self.rect.width)
                self.rect.y = random.randrange(-100, -40)
                self.speedy = random.randrange(1, 8)
    • Что делает код?

      • init(): Загружает изображение врага, устанавливает случайную начальную позицию и скорость падения.

      • update(): Перемещает врага вниз, а также, если враг выходит за пределы экрана, переносит его обратно наверх.

  • Класс Bullet (создание пуль). Этот класс уже был представлен выше.

  • Обнаружение коллизий и логика уничтожения. Теперь добавим логику обнаружения коллизий в основной цикл игры (main.py):

    # main.py (внутри цикла run())
    # ... (код игрового цикла) ...
    
    # Проверка столкновений пуль с врагами (пули уничтожают врагов)
    hits = pygame.sprite.groupcollide(self.mobs, self.bullets, True, True)  # Проверяем столкновения. True, True = удаляем и врага и пулю
    for hit in hits:
        m = Mob()  # Создаем нового врага
        self.all_sprites.add(m)  # Добавляем его в общую группу
        self.mobs.add(m)  # Добавляем его в группу врагов
        self.score_counter.add(1)  # Увеличиваем счет
    
    # Проверка столкновения игрока с врагами (проигрыш)
    hits = pygame.sprite.spritecollide(self.player, self.mobs, False)  # Проверяем столкновения игрока с врагами. False = враг не удаляется
    if hits:
        self.game_over = True  # Если есть столкновение, устанавливаем флаг game_over
        # Дополнительно: можно уменьшать здоровье игрока, а не сразу завершать игру
    
    # ... (остальной код игрового цикла) ...
    • Что делает код?

      • pygame.sprite.groupcollide(self.mobs, self.bullets, True, True): Проверяет столкновения между группой врагов (self.mobs) и группой пуль (self.bullets). Если пуля попадает во врага, и пуля, и враг удаляются из своих групп (параметры True, True). После каждого столкновения создается новый враг и увеличивается счет.

      • pygame.sprite.spritecollide(self.player, self.mobs, False): Проверяет столкновения между игроком и врагами. Если происходит столкновение, устанавливается флаг self.game_over = True, что означает конец игры.

4. Босс: Создание финального противника

После набора определенного количества очков появляется босс.

  • Класс Boss (создание босса). Создадим класс Boss (в objects.py).

    # objects.py
    import pygame
    import random
    from config import *
    
    class Boss(pygame.sprite.Sprite):
        def __init__(self, x, y, all_sprites, boss_lasers):
            pygame.sprite.Sprite.__init__(self)
            boss_image = pygame.image.load(path.join(img_dir, "Boss.png")).convert_alpha()  # Загрузка изображения босса
            self.image = pygame.transform.scale(boss_image, (90, 150))  # Масштабирование
            self.image.set_colorkey(BLACK)
            self.rect = self.image.get_rect()  # Получаем прямоугольник босса
            self.rect.x = x  # Позиция по X
            self.rect.y = y  # Позиция по Y
            print("Boss created at:", self.rect.x, self.rect.y)
            self.health = 50  # Здоровье босса
            self.original_image = self.image  # Сохраняем оригинальное изображение (для анимации)
            self.last_shot = pygame.time.get_ticks()  # Время последнего выстрела
            self.shoot_delay = 2000  # Задержка между выстрелами (в миллисекундах)
            self.all_sprites = all_sprites  # Группа всех спрайтов
            self.boss_lasers = boss_lasers  # Группа лазеров босса
    
        def update(self):
            # Двигаем босса (просто для примера, можно сделать сложнее)
            self.rect.x += random.choice([-2, -1, 0, 1, 2])  # Случайное движение по горизонтали
            if self.rect.left < 0:
                self.rect.left = 0  # Ограничиваем движение слева
            if self.rect.right > WIDTH:
                self.rect.right = WIDTH  # Ограничиваем движение справа
    
            # Стреляем
            now = pygame.time.get_ticks()  # Получаем текущее время
            if now - self.last_shot > self.shoot_delay:  # Если прошло достаточно времени с последнего выстрела
                self.last_shot = now  # Обновляем время последнего выстрела
                laser = BossLaser(self.rect.centerx, self.rect.bottom)  # Создаем лазер
                self.all_sprites.add(laser)  # Добавляем лазер в общую группу спрайтов
                self.boss_lasers.add(laser)  # Добавляем лазер в группу лазеров босса
    
        def draw(self, screen):
            screen.blit(self.image, self.rect)
    
        def damage(self, amount):
            self.health -= amount
            if self.health <= 0:
                self.kill()  # Если здоровье <= 0, уничтожаем босса
    • Что делает код?

      • init(): Загружает изображение босса, масштабирует, устанавливает начальную позицию, здоровье, и другие параметры.

      • update(): Отвечает за перемещение босса и стрельбу лазерами.

      • draw(): Рисует изображение босса на экране.

      • damage(): Уменьшает здоровье босса. При достижении 0, босс удаляется.

  • Класс BossLaser (лазеры босса).

    # objects.py
    import pygame
    from config import *
    
    class BossLaser(pygame.sprite.Sprite):
        def __init__(self, x, y):
            pygame.sprite.Sprite.__init__(self)
            self.image = pygame.Surface((5, 20))  # Создаем поверхность для лазера (прямоугольник)
            self.image.fill(BOSS_LASER_COLOR)  # Заливаем поверхность цветом лазера
            self.image = self.image.convert_alpha() # Прозрачность
            self.rect = self.image.get_rect()  # Получаем прямоугольник лазера
            self.rect.centerx = x  # Центрируем лазер по x
            self.rect.bottom = y  # Располагаем лазер под боссом
            self.speedy = BOSS_LASER_SPEED  # Устанавливаем скорость падения
    
        def update(self):
            self.rect.y += self.speedy  # Двигаем лазер вниз
            if self.rect.bottom > HEIGHT:
                self.kill()  # Удаляем лазер, если он вышел за пределы экрана
  • Добавление босса в игру (main.py). Логика появления босса и столкновения с ним:

    # main.py (внутри run())
    # ... (код игрового цикла) ...
    
    # Проверка на появление босса
    if self.score_counter.get_score() % 100 == 0 and self.score_counter.get_score()!=0 and self.boss is None:
        self.boss = Boss(WIDTH // 2 - 50, 50, self.all_sprites, self.boss_lasers)  # Создаем босса
        self.all_sprites.add(self.boss)  # Добавляем босса в группу спрайтов
    
    # Проверка столкновений пуль с боссом
    if self.boss:
        for bullet in self.bullets:
            if pygame.sprite.collide_rect(bullet, self.boss):  # Проверяем столкновение
                self.boss.damage(3)  # Уменьшаем здоровье босса
                self.bullets.remove(bullet)  # Удаляем пулю
                self.all_sprites.remove(bullet)
                break  # Важно: только одно попадание за раз
    
        if self.boss.health <= 0:
            self.boss.kill()  # Если здоровье босса меньше или равно 0, уничтожаем его
            self.boss = None
    
    # Проверка столкновения игрока с лазерами босса (проигрыш)
    hits = pygame.sprite.spritecollide(self.player, self.boss_lasers, True)  # True - лазеры удаляются
    if hits:
        self.game_over = True  # Конец игры
    • Что делает код?

      • Проверяет, набрал ли игрок достаточно очков (self.score_counter.get_score() % 100 == 0 and self.score_counter.get_score()!=0) и отсутствует ли босс на экране (self.boss is None).

      • Если условия выполняются, создается новый экземпляр Boss.

      • Проверяет, произошло ли столкновение пуль с боссом. Если да, то босс получает урон, а пуля удаляется.

      • Проверяет, уничтожен ли босс (здоровье <= 0). Если да, то босс удаляется.

      • Проверяет столкновения игрока с лазерами босса. Если игрок столкнулся с лазером, игра заканчивается.

Уровни и игровой процесс: Организация игры (Упрощено)

В данной реализации используется простая структура уровней - игра переходит в game_over после коллизии игрока с мобов/пулей босса, а после отображается стартовый экран.

  • Класс ScoreCount (добавлено в objects.py). Класс, отображающий очки.

    # objects.py
    import pygame
    from config import *
    
    class ScoreCount:
        def __init__(self, initial_score=0):
            self.score = initial_score
            self._score = initial_score
            self.font = pygame.font.Font(None, 36)  # Шрифт и размер
            self.update_score_surface()
    
        def add(self, points):
            self.score += points
            self.update_score_surface()
    
        def get_score(self):
            return self.score
    
        def update_score_surface(self):
            self.score_surface = self.font.render(f"Score: {int(self.score)}", True, (255, 255, 255))
    
        def reset(self):
            self.score = 0
    
        def draw(self, screen, x=10, y=10):
            if self.score_surface:
                screen.blit(self.score_surface, (x, y))
  • Отображение счета в игре (main.py). Необходимо создать объект ScoreCount и отрисовывать его.

    # main.py
    # ... (внутри класса Application.__init__) ...
    self.score_counter = ScoreCount()
    
    # ... (внутри игрового цикла run()) ...
    self.score_counter.draw(self.screen)

6. Завершение игры: Состояние игры и стартовый экран (main.py)

  • Состояние игры: Переменная self.game_over отслеживает состояние игры.

    # main.py (внутри класса Application)
    def start_screen(self):
        start_image = pygame.image.load(path.join(img_dir, "start.png")).convert_alpha()
        start_rect = start_image.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    
        font_name = pygame.font.match_font('arial')
        font = pygame.font.Font(font_name, 30)
    
        line1 = "Нажмите на любую кнопку,"
        line2 = "чтобы начать игру, или Esc, чтобы выйти"
    
        text_surface1 = font.render(line1, True, WHITE)
        text_surface2 = font.render(line2, True, WHITE)
    
        text_rect1 = text_surface1.get_rect(center=(WIDTH // 2, HEIGHT // 2 - 20))
        text_rect2 = text_surface2.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 20))
    
        running = True
        while running:
            self.clock.tick(FPS)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    return False  # Возврат False для выхода из игры
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        pygame.quit()
                        return False  # Выход из игры
                    else:
                        running = False  # Нажата другая кнопка -> начинаем игру
    
            self.screen.blit(start_image, start_rect)
            self.screen.blit(text_surface1, text_rect1)
            self.screen.blit(text_surface2, text_rect2)
            pygame.display.flip()
    
        self.game_over = False  # Игра началась
        self.reset_game()  # Сброс параметров
  • Что делает код?

    • Отображает изображение стартового экрана.

    • Отображает текст.

    • Ожидает нажатия клавиши (любой, кроме ESC).

    • Если нажата клавиша - self.game_over = False, игра начинается.

    • Функция возвращает False, если был произведен выход из игры.

Итоги

  • Вот что получилось

Комментарии (0)