07 ноября 2022

🐍🕹️ Как написать игру на Python: 5 игровых движков

Пишу, перевожу и иллюстрирую IT-статьи. На proglib написал 140 материалов. Увлекаюсь Python, вебом и Data Science. Открыт к диалогу – ссылки на соцсети и мессенджеры: https://matyushkin.github.io/links/ Если понравился стиль изложения, упорядоченный список публикаций — https://github.com/matyushkin/lessons
Туториал для тех, кто хочет сделать игру на Python (и пока не изучать Unity или Unreal Engine). Напишем код простой игры со сбором монет и сравним на трех различных движках, а также сделаем пару игр в стилях Interactive Fiction и визуального романа.
🐍🕹️ Как написать игру на Python: 5 игровых движков
💡 Статья представляет собой незначительно сокращенный перевод лонгрида Джона Фишера в сообществе Real Python Top Python Game Engines. Код программ из туториала хранится в репозитории на Github.

Один из популярных мотивов обучения программированию — желание написать собственную игру. В этой статье мы сравним несколько игровых движков на практике.

Чтобы извлечь пользу из этого руководства, нужно уметь программировать на Python, а также знать, как в языке реализуется объектно-ориентированное программирование. Для независимой установки движков мы советуем использовать виртуальные окружения.

Обзор игровых движков Python

Игровые движки для Python обычно являются библиотеками, которые можно установить с помощью менеджера pip или загрузить с площадок распространения кода. В противовес к библиотекам существуют автономные среды, предназначенные исключительно для написания игр:

Такие программы отличаются от игровых движков Python во многих аспектах:

  • Поддерживаемые языки. Среды программирования игр обычно написаны на C-подобных языках и предлагают использовать соответствующие языки для написания игр: в Unity это C#, в Unreal Engine — C++.
  • Поддержка платформ. Автономные среды позволяют без дополнительных усилий создавать игры для различных платформ, включая мобильные устройства. Напротив, перенос Python-игры на мобильные устройства — задача не из лёгких.
  • Лицензирование. Игры, написанные с использованием автономного игрового движка, имеют особые условия лицензирования и дальнейшего распространения.

Зачем же вообще использовать Python для написания игр? Использование GameDev-сред требует изучения документации и обычно — овладения новым языком программирования. В то же время при работе с игровыми движками на Python питонисты применяют в основном уже имеющиеся знания. Это помогает быстрее двигаться вперед и получить первый результат.

Критерии отбора Python-движков в этой статье:

  • популярность,
  • актуальная поддержка,
  • качественная документация.

Для каждого движка мы рассмотрим способ установки, базовую терминологию и основные возможности для реализации игр.

🐍 Библиотека питониста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
🐍🎓 Библиотека собеса по Python
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека собеса по Python»
🐍🧩 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Pygame

Pygame — первое, что приходит на ум, когда кто-нибудь начинает разговор об играх на Python.

Pygame расширяет собой библиотеку SDL (сокр. от Simple DirectMedia Layer), предназначенную для межплатформенного доступа к мультимедийным аппаратным компонентам системы: мыши, клавиатуре, джойстику, аудио- и видеоустройствам.

Pygame определяет множество классов, которые инкапсулируют концепции, не зависящие от аппаратного обеспечения. В результате игры и мультимедийные программы запускаются в любых системах, где можно установить библиотеку.

Установка Pygame

После создания и активации виртуального окружения установите библиотеку с помощью pip :

        (venv) $ python -m pip install pygame

    

Чтобы проверить результат установки, запустите пример, поставляемый вместе с библиотекой:

        (venv) $ python -m pygame.examples.aliens

    

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

Базовые концепции Pygame

Game loop. Для управления ходом игры используется понятие игрового цикла. Функциональность игрового цикла реализует автор, а Pygame предоставляет необходимые методы и функции. Каждая итерация игрового цикла называется кадром (frame). За один кадр игра выполняет четыре действия:

  1. Обработка пользовательского ввода от мыши, клавиатуры или джойстика с помощью модели событий.
  2. Обновление состояния игровых объектов: спрайтов (образы героев и предметов), изображений, шрифтов и цветов. Объекты описываются подходящими структурами данных или с помощью классов Pygame.
  3. Обновление дисплея и аудиовыхода. Pygame обеспечивает абстрактный доступ к оборудованию для отображения картинки и передачи звука с помощью внутренних модулей display, mixer и music.
  4. Сохранение или изменение скорости игры. Модуль pygame.time позволяет авторам игр контролировать скорость игры. За счёт этого игра работает с одинаковой скоростью на различном оборудовании — библиотека гарантирует завершение каждого кадра в течение заданного периода времени.

Базовый пример Pygame

В трех следующих движках мы будем начинать рассмотрение с базового примера, в котором движок рисует на на экране пару фигур и выводит текст.

🐍🕹️ Как написать игру на Python: 5 игровых движков
        # Импортируем и инициализируем библиотеку
import pygame

pygame.init()

# Устанавливаем ширину и высоту окна в пикселях
WIDTH = 800
HEIGHT = 600

# Настраиваем окно отрисовки
screen = pygame.display.set_mode([WIDTH, HEIGHT])

# Игровой цикл выполняется, пока пользователь не захочет выйти
running = True
while running:

    # Нажал ли пользователь кнопку зыкрытия окна?
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Заполняем фон белым цветом
    screen.fill((255, 255, 255))

    # Рисуем синий круг в центре экрана радиусом 50
    pygame.draw.circle(screen, (0, 0, 255), (WIDTH // 2, HEIGHT // 2), 50)

    # Рисуем красный контурный квадрат в верхнем левом углу экрана
    red_square = pygame.Rect((50, 50), (100, 100))
    pygame.draw.rect(screen, (200, 0, 0), red_square, 1)

    # Рисуем оранжевый текст с кеглем 60
    text_font = pygame.font.SysFont("any_font", 60)
    text_block = text_font.render(
        "Hello, World! From Pygame", False, (200, 100, 0)
    )
    screen.blit(text_block, (50, HEIGHT - 50))

		# Обновляем экран
    pygame.display.flip()

# Цикл завершился! Уходим.
pygame.quit()

    

Для запуска кода используйте команду:

        (venv) $ python pygame/pygame_basic.py

    

Игровой цикл есть даже в такой скромной программе. В примере он управляется переменной running. Её установка в значение False завершает выполнение программы.

Обработка событий. События хранятся в виде очереди, из неё события извлекаются с помощью pygame.event.get(). В рассматриваемом случае обрабатывается только событие pygame.QUIT, генерируемое при закрытии пользователем окна программы. При обработке этого события мы устанавливаем running = False.

Отрисовка фигур и текста. В то время как для отрисовки фигур просто используются специальные методы, отрисовка текста выглядит несколько сложнее. Сначала выбираем шрифт и создаем объект шрифта. Далее вызываем метод .render() и передаем ему текст, шрифт и цвет. В ответ метод создает объект класса Surface. Этот объект мы копируем на экран screen, используя его метод screen.blit().

В конце игрового цикла мы даем команду отобразить на дисплее все, что было ранее нарисовано.

Продвинутый вариант игры на Pygame

Чтобы лучше изучить возможности Pygame, напишем настоящую игру.

Геймплей. Главный герой представлен одиночным спрайтом, управляемым перемещением мыши. Через равные промежутки времени на экране одна за другой появляются монеты. Когда персонаж перемещается поверх монеты или сталкивается с ней, монета исчезает, а игрок получает 10 очков. По ходу игры монеты начинают добавляться быстрее. Игра заканчивается, когда на экране присутствует более десяти монет или игрок закрыл окно.

При запуске игра выглядит примерно вот так
При запуске игра выглядит примерно вот так
        # Импорт и инициализация
import pygame

# Для случайного размещения монет
from random import randint

# Для поиска ресурсов
from pathlib import Path

# Для аннотации типов
from typing import Tuple

# Устанавливаем размеры окна
WIDTH = 800
HEIGHT = 600

# Как часто должны генерироваться монеты (мс)
coin_countdown = 2500
coin_interval = 100

# Сколько монет должно быть на экране, чтобы игра закончилась
COIN_COUNT = 10

# Определяем спрайт для игрока
class Player(pygame.sprite.Sprite):
    def __init__(self):
        """Инициализирует спрайт игрока"""
        super(Player, self).__init__()

        # Получаем изображение персонажа
        player_image = str(
            Path.cwd() / "pygame" / "images" / "alien_green_stand.png"
        )
        # Загружаем изображение, настраиваем альфа канал для прозрачности
        self.surf = pygame.image.load(player_image).convert_alpha()
        # Сохраняем в прямоугольнике, чтобы перемещать объект
        self.rect = self.surf.get_rect()

    def update(self, pos: Tuple):
        """Обновляет позицию персонажа

        Аргументы:
            pos {Tuple} -- (X,Y) позиция для движения персонажа
        """
        self.rect.center = pos

# Определяем спрайт для монет
class Coin(pygame.sprite.Sprite):
    def __init__(self):
        """Инициализирует спрайт монеты"""
        super(Coin, self).__init__()

        # Получаем изображение монеты
        coin_image = str(Path.cwd() / "pygame" / "images" / "coin_gold.png")

        # Загружаем изображение, настраиваем альфа канал для прозрачности
        self.surf = pygame.image.load(coin_image).convert_alpha()

        # Задаем стартовую позицию, сгенерированную случайным образом
        self.rect = self.surf.get_rect(
            center=(
                randint(10, WIDTH - 10),
                randint(10, HEIGHT - 10),
            )
        )

# Инициализируем движок
pygame.init()

# Настраиваем окно
screen = pygame.display.set_mode(size=[WIDTH, HEIGHT])

# Скрываем курсор мыши
pygame.mouse.set_visible(False)

# Запускаем часы для фиксации времени фрейма
clock = pygame.time.Clock()

# Создаем событие для добавления монеты
ADDCOIN = pygame.USEREVENT + 1
pygame.time.set_timer(ADDCOIN, coin_countdown)

# Настраиваем список монет
coin_list = pygame.sprite.Group()

# Инициализируем счет
score = 0

# Определяем звук для столкновения с монетой 
coin_pickup_sound = pygame.mixer.Sound(
    str(Path.cwd() / "pygame" / "sounds" / "coin_pickup.wav")
)

# Создаем спрайт героя и устанавливаем на заданную позицию
player = Player()
player.update(pygame.mouse.get_pos())

# Цикл событий
running = True
while running:

    # Проверяем, нажал ли пользователь кнопку закрытия окна
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        # Определяем, нужно ли добавлять новую монету
        elif event.type == ADDCOIN:
            # Добавляем новую монету
            new_coin = Coin()
            coin_list.add(new_coin)

            # Ускоряем игру, если на экранее менее 3 монет
            if len(coin_list) < 3:
                coin_countdown -= coin_interval
            # Ограничиваем скорость
            if coin_countdown < 100:
                coin_countdown = 100

            # Останавливаем предыдущий таймер
            pygame.time.set_timer(ADDCOIN, 0)

            # Запускаем новый таймер
            pygame.time.set_timer(ADDCOIN, coin_countdown)

    # Обновляем позицию персонажа
    player.update(pygame.mouse.get_pos())

    # Проверяем, столкнулся ли игрок с монетой и удаляем, если это так
    coins_collected = pygame.sprite.spritecollide(
        sprite=player, group=coin_list, dokill=True
    )
    for coin in coins_collected:
        # Каждая монета стоит 10 очков
        score += 10
        # Воспроизводим звук для монеты
        coin_pickup_sound.play()

    # Проверяем, не слишком ли много монет
    if len(coin_list) >= COIN_COUNT:
        # Если монет много, останавливаем игру
        running = False

    # Указываем цвет фона
    screen.fill((255, 170, 164))

    # Рисуем следующие монеты
    for coin in coin_list:
        screen.blit(coin.surf, coin.rect)

    # Отрисовываем персонажа
    screen.blit(player.surf, player.rect)

    # Выводим текущий счет
    score_font = pygame.font.SysFont("any_font", 36)
    score_block = score_font.render(f"Score: {score}", False, (0, 0, 0))
    screen.blit(score_block, (50, HEIGHT - 50))

    # Отображаем всё на экране
    pygame.display.flip()

    # Скорость обновления - 30 кадров в секунду
    clock.tick(30)

# Готово! Печатаем итоговый результат
print(f"Game over! Final score: {score}")

# Делаем курсор мыши вновь видимым
pygame.mouse.set_visible(True)

# Выходим из игры
pygame.quit()

    

Спрайты в Pygame предоставляют лишь базовую функциональность — в нашем коде мы их расширяем, создавая подклассы для персонажа (Player) и монет (Coin). Объекты спрайтов сохраняются в self.surf и позиционируются с помощью свойства self.rect.

Вывод монет. Чтобы добавлять монеты на экран через равные промежутки времени, мы используем таймер time.set_timer(), отсчитывающий время до события в миллисекундах (coin_countdown). Добавлению монет соответствует событие ADDCOIN. Событие создает новый объект Coin и добавляет его в coin_list. Далее проверяется количество монет на экране. Если монет меньше трех, то coin_countdown уменьшается. Предыдущий таймер останавливается и запускается новый.

Движение персонажа. При движении игрок сталкивается с монетами и собирает их. При этом мы удаляем соответствующий объект из coin_list, обновляем счёт и воспроизводим звук. Автор игры также должен помнить об очистке экрана, отрисовке спрайтов в правильном порядке и отображении счёта.

Заключение по Pygame

Pygame — мощная и хорошо зарекомендовавшая себя библиотека, но у неё есть недостатки. Автор игры должен самостоятельно оформить базовое поведение спрайтов, реализовать игровые циклы и основные обработчики событий. Как вы увидите далее, более современные игровые движки позволяют достичь аналогичных результатов при меньшем объёме выполняемой работы.

Pygame Zero

С одним задачами Pygame справляется хорошо, в других — сказывается возраст библиотеки. Для новичков в написании игр есть вариант получше — Pygame Zero. Эта библиотека разработана для образовательных задач, поэтому текст документации будет понятен даже для новичков в программировании. Кроме того, есть подробное пошаговое руководство.

Установка Pygame Zero

Pygame Zero можно установить, как и любую другую библиотеку Python:

        (venv) $ python -m pip install pgzero

    

Базовый пример на Pygame Zero

Pygame Zero автоматизирует многие вещи, которые программистам приходится описывать вручную при использовании стандартного движка Pygame. По умолчанию Pygame Zero предоставляет создателю игры:

  • игровой цикл — его не нужно писать;
  • модель событий для отрисовки, обновлений и обработки ввода;
  • единую обработку изображений, текста и звука;
  • класс спрайтов и методы анимации для пользовательских спрайтов.

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

💡 Чтобы не «раздувать» текст статьи, мы отсылаем читателей посмотреть этот и следующие примеры кода мы в упомянутому выше репозиторию.

Отрисовка окна. Pygame Zero автоматически распознает, что константы WIDTH и HEIGHT относятся к размеру окна. Pygame Zero также предоставляет базовый код для закрытия окна, так что закрытие окна не нужно описывать в обработчике.

Отрисовка фигур и текста. Поскольку Pygame Zero основан на Pygame, он наследует часть кода для отрисовки фигур, но вывод текста выглядит проще и занимает один вызов вместо трех для обычного Pygame.

        screen.draw.text(
        f"Score: {score}",
        (50, HEIGHT - 50),
        fontsize=48,
        color="black",
    )

    

Запуск программ Pygame Zero осуществляется из командной строки с помощью команды:

        (venv) $ python pygame_zero/pygame_zero_basic.py

    

Продвинутый вариант игры на Pygame Zero

Чтобы продемонстрировать разницу между игровыми движками, мы вернемся к той же игре, что описали для Pygame. Игра ведет себя идентично версии, продемонстрированной ранее. Программный код игры также доступен в репозитории.

🐍🕹️ Как написать игру на Python: 5 игровых движков

Спрайты в Pygame Zero называются Actors. Их характеристики требуют некоторых пояснений, так как эти сведения используются в коде примера:

  1. Для каждого Actor задаются, как минимум, изображение и положение.
  2. Все изображения должны располагаться во вложенной папке с именем ./images/. Названия файлов должны содержать лишь строчные буквы, цифры или символы подчеркивания.
  3. При ссылке на изображение используется имя файла без расширения. Например, если изображение называется alien.png, то в программе на него ссылаются строкой "alien" .

Вывод монет через равные промежутки времени производится с помощью метода clock.schedule(). Метод принимает вызываемую функцию (в нашем случае add_coin) и количество секунд перед вызовом самой функции. Запускаемая функция add_coin() создает объект класса Actor и добавляет спрайт в глобальный список видимых монет.

Перемещение персонажа задействует обработчик события on_mouse_move(). В исходном положении спрайт персонажа находится в центре экрана. Положение мыши хранится в глобальной переменной.

Столкновение с монетой приводит к воспроизведению звука и увеличению счёта. Удаляемая монета добавляется в список coin_remove_list. При обработке столкновений мы проверяем, не слишком ли много монет сейчас находится на экране. Если это так, мы заканчиваем игру, прекращаем создавать новые монеты и выводим окончательный счёт игры.

Обновление состояний. Функция update() вызывается Pygame Zero один раз за кадр. Мы используем её, чтобы перемещать объекты класса Actor и обновлять состояния игровых объектов. Также один раз за кадр для отрисовки вызывается функция draw().

Заключение по Pygame Zero

Реализация игры на Pygame Zero заняла 152 строки кода вместо 182 строк на обычном Pygame. Хотя количество строк сопоставимо, версия на Pygame Zero получилась более ясной в плане кода и проще для понимания и дополнения.

Arcade

Arcade — движок Python для создания игр с современными графикой и звуком, разработанный профессором Полом Крэйвеном из Симпсон-колледжа (Айова, США).

Arcade базируется на мультимедийной библиотеке pyglet и выгодно отличается, как от Pygame, так и от Pygame Zero:

  • поддерживает современную OpenGL-графику;
  • поддерживает аннотации типов Python 3;
  • умеет анимировать с анимированными спрайтами;
  • имеет согласованные имена команд, функций и параметров;
  • поощряет отделение игровой логики от кода, обеспечивающего отображение;
  • сокращает использование шаблонного кода;
  • имеет поддерживаемую и актуальную документацию, в том числе несколько учебных пособий и полные примеры игр на Python;
  • имеет встроенные физические движки для игр с видом сверху и игр-платформеров.

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

Установка Arcade

Чтобы установить Arcade и его зависимости, используйте соответствующую команду pip:

        (venv) $ python -m pip install arcade

    

Есть также инструкции по установке для Windows, macOS и Linux. При желании можно собрать движок из исходного кода.

Базовый пример на Arcade

🐍🕹️ Как написать игру на Python: 5 игровых движков

Начнем с базового примера с фигурами и текстом. Чтобы запустить код, используйте следующую команду:

        (venv) $ python arcade/arcade_basic.py

    

Начало координат (0, 0) расположено в левом нижнем углу экрана. Это отличает Arcade от большинства игровых движков, в которых начало координат расположено в левом верхнем углу.

Arcade — это объектно-ориентированная библиотека. Для подклассов игр используется класс arcade.Window, для настройки игры вызывается super().__init().

Отрисовка всего что есть на экране производится обработчиком событий .on_draw() . Обработчик стартует с вызова .start_render(), который сообщает Arcade подготовить окно для рисования. Напоминает pygame.flip() для отрисовки в Pygame.

Фигуры и цвета. Каждый из основных методов рисования фигур в Arcade начинается с draw_*. Arcade умеет отрисовывать множество разнообразных фигур и сотни именованных цветов из пакета arcade.color. Также можно использовать кортежи RGB или RGBA.

Продвинутый вариант игры на Arcade

Чтобы показать, чем Arcade отличается от других игровых движков, запрограммируем ту же игру с монетами, что и раньше.

Инициализация. Объектно-ориентированный характер Arcade позволяет отделить инициализацию игры от инициализации отдельного уровня: игра инициализируется методом .__init__(), а уровни настраиваются и перезапускаются с помощью .setup(). Это отличный шаблон для использования даже в играх с одним уровнем, как в нашем случае.

Спрайты создаются в виде объекта класса arcade.Sprite, которому задан путь к изображению — Arcade поддерживает pathlib-пути.

Создание монет планируется с помощью arcade.schedule() и вызова self.add_coin() через равные промежутки времени. Метод .add_coin() создает новый спрайт монеты в случайном месте и добавляет его в список.

Перемещение персонажа мышью реализуется с помощью метода .on_mouse_motion(). Метод arcade.clamp() гарантирует, что координаты центра страйпа не выйдут за пределы экрана.

Столкновение с монетой обрабатывается методом .on_update(). Метод arcade.check_for_collision_with_list() возвращает список всех спрайтов, которые столкнулись с указанным спрайтом. Код проходит по этому списку, увеличивает счет и воспроизводит звук. Метод .on_update() проверяет, не слишком ли много монет сейчас есть на экране.

Заключение по Arcade

Реализация Arcade так же удобна для чтения и хорошо структурирована, как и код для Pygame Zero. Однако программный код занимает больше места — 194 строки. Причина в том, что в Arcade заложены возможности, которые лучше реализуются в более крупных играх:

  • анимированные спрайты;
  • встроенные физические движки;
  • поддержка сторонних игровых карт;
  • системы работы с частицами и шейдирование.

Авторы игр, пришедшие в Arcade из Pygame Zero обнаружат здесь знакомую структуру, но более мощные и продвинутые функции.

adventurelib

Мир игр полон самых различных жанров. Бывают и такие игры, как например, Zork, в которых основой игрового повествования является текст. Такой тип игр ещё называют Interactive Fiction. Для создания текстовых игр на Python существует движок adventurelib. Библиотека отлично подойдет для тех, кто хочет создать текстовую игру без необходимости писать парсер языка.

Установка adventurelib

adventurelib доступен на PyPI и может быть установлен с помощью соответствующей команды pip :

        (venv) $ python -m pip install adventurelib

    

Библиотеку также можно установить из репозитория GitHub, сохранив в той же папке, что и ваша игра, и использовать напрямую.

Базовые концепции adventurelib

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

Чтобы запустить код из примера, используйте следующую команду:

        (venv) $ python adventurelib/adventurelib_basic.py

    

Декоратор @when. Текстовые игры в значительной степени полагаются на синтаксический анализ пользовательского ввода. Библиотека определяет текст, который вводит игрок, как команду (command) **и предоставляет для описания команд декоратор @when(). Например, декоратор @when("look") добавляет команду в список допустимых команд и связывает с ней функцией look(). Всякий раз, когда игрок набирает look, adventurelib вызывает соответствующую функцию. Для удобства игроков и разработчиков команды нечувствительны к регистру.

Несколько команд могут использовать одну и ту же функцию. Функция go() декорирована девятью различными командами, чтобы игрок мог перемещаться по игровому миру. В скриншоте ниже есть три из них: south, east, north.

🐍🕹️ Как написать игру на Python: 5 игровых движков

Предметы. В текстовых играх часто есть предметы, которые необходимо собирать, чтобы открывать новые области игры или решать головоломки. Или это могут быть персонажи, с которыми игрок может взаимодействовать. Движок adventurelib предоставляет класс Item для определения и предметов, и персонажей по их именам и псевдонимам.

Конструктор Item()принимает одну или несколько строк. Первая строка — имя предмета, которое используется при печати. Остальные строки используются в качестве псевдонимов, чтобы игроку не приходилось вводить полное имя объекта.

Взаимодействие с предметами. Часто команды, которые вводит игрок, направлены на конкретный предмет. Разработчик игры может задать дополнительный контекст команды. Для этого в описании декоратора @when() используются слова, написанные заглавными буквами.

💡 Пример контекста команды можно увидеть в функции look_at(), которая принимает строковый параметр с именем item. В декораторах этой функции, определяющих команды look at и inspect, слово ITEM выступает в качестве переменной-заполнителя текста, следующего за командой. Этот текст и передается функции look_at() в качестве входного значения. Например, если игрок введет look at book, то item внутри функции получит строковое значение "book".

Реплики. Для вывода реплик используйте функцию say(), которая отлично справляется с выводом многострочного текста. Пример использования есть в теле функции look() — всякий раз, когда игрок набирает look, функция say() выводит в консоль описание текущей комнаты.

Комнаты. Для определения различных областей игрового мира библиотека adventurelib предоставляет класс Room. При создании комнаты конструктору Room() передается описание комнаты и связь с другими комнатами с помощью свойств .north, .south, .east и .west.

Препятствие. Мы также создаем ограничение, указав, что между гостиной и верандой есть запертая дверь living_room.locked. Чтобы открыть эту дверь, игроку потребуется найти ключ. В исходном состоянии ключ находится в спальне.

Ключ имеет не только имя и псевдонимы, но и метод, с помощью которого используется в игре. Метод key.use_item вызывается, когда игрок пытается использовать предмет, набрав строку use key.

Коллекции предметов, таких как инвентарь игрока или предметы на полу комнаты, хранятся в объекте класса Bag. Эти объекты-коллекции позволяют добавлять, удалять предметы или проверять содержимое.

В нашем коде определены четыре Bag-объекта: три для каждой комнаты и один для инвентаря, собираемого игроком. Для добавления предметов в инвентарь используется функция get(), Чтобы взять какой-либо предмет из имеющегося инвентаря — функция take(). При переносе предмета в инвентарь, он удаляется из Bag-объекта комнаты.

Продвинутый вариант игры с использованием adventurelib

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

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

В игре есть несколько областей:

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

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

Программный код игры разбит на несколько файлов:

  • adventurelib_game_rooms.py описывает комнаты и области;
  • adventurelib_game_items.py определяет предметы и их атрибуты;
  • adventurelib_game_characters.py описывает персонажей, с которыми может взаимодействовать игрок;
  • adventurelib_game.py собирает всё вместе, добавляет команды и запускает игру.

Игру можно запустить с помощью следующей команды:

        (venv) $ python adventurelib/adventurelib_game.py

    

В предыстории мы наметили игровые области и пути, по которым может перемещаться игрок. Они показаны на блок-схеме ниже.

🐍🕹️ Как написать игру на Python: 5 игровых движков

Каждая область имеет свои свойства:

  • название, краткое и подробное описания;
  • предметы и персонажи;
  • заблокированные выходы;
  • указание на то, был ли уже игрок в этой области или нет.

Используем ООП. Чтобы быть уверенными, что каждая область имеет собственный экземпляр каждого из этих свойств, мы создадим в файле у подкласс Room с именем GameArea. Предметы в каждой комнате хранятся в объекте класса Bag с именем items, а персонажи — в characters.

Игровые предметы определены в adventurelib_game_items.py как объекты типа Item(). Одни игровые предметы необходимы для завершения игры, в то время как другие разнообразят геймплей. Некоторые элементы имеют определенные свойства, уникальные для данного элемента. Например, у мечей wooden_sword и steel_sword есть свойство, показывающее наносимый ими урон и поддерживаемые магические бонусы.

Взаимодействие с персонажами помогает продвигать игровой сюжет. Персонажи определены в adventurelib_game_characters.py. У каждого героя, как и у каждого предмета, есть связанные с ним универсальные свойства, такие как описание и приветствие, используемое, когда игрок встречает персонажа в первый раз.

Контекст. Для того, чтобы отличать взаимодействие с персонажем или простое нахождение с ним в одной зоне, движок использует понятие контекста. Контекст позволяет для разных ситуаций запускать разные команды. Или вести себя по-разному разным командам и отслеживать дополнительную информацию о действиях, которые мог предпринять игрок.

Примеры контекста. В какой-то момент игрок впервые сталкивается со старейшиной Бэрроном (Elder Barron). Когда игрок набирает talk to elder, контекст устанавливается по свойству elder.context. Приветствие старейшины заканчивается вопросом требующим ответа «да» или «нет». Если игрок вводит yes, то в adventurelib_game.py запускается обработчик команды, заданный декоратором @when("yes", context="elder").

🐍🕹️ Как написать игру на Python: 5 игровых движков

Позже при разговоре игрока с кузнецом добавляется второй уровень контекста, отражающий возможную покупку оружия. За счет этого программа понимает, что одна и ту же команда "yes" приводит к разным последствиям.

Запрет действий в контексте. Вы также можете проверить, как помогает контекст в обработчике команд. Например, игрок не может просто выйти из боя с великаном, закончив разговор. Обработчик команды "goodbye" проверяет, находится ли игрок в контексте "giant", который вводится, когда он начинает сражаться. Если контекст в силе, прекращать разговор нельзя, — это смертельный бой!

Команды, не имеющие совпадений, обрабатываются функцией no_command_matches(). Её можно использовать для диалогов, требующих конкретного ответа. Так, когда волшебник просит игрока разгадать загадку, создается контекст wizard.riddle. Неправильный ответ приводит к прекращению разговора.

Заключение по adventurelib

Создать полноценную текстовую приключенческую игру непросто, но adventurelib берёт на себя основную рутину, связанную с обработкой локаций, предметов, героев, а также их поведением в зависимости от истории взаимодействий.

Ren’Py

Наследником текстовых игр являются современные игры в стиле визуальных романов (новелл). В таких играх наибольшее значение также играют повествование и сюжет, но игровой опыт разнообразнее за счет картинок, визуальных эффектов и звуков. Для создания подобных игр на Python используется Ren'Py. Название движка образовано от японского слова, означающего романтическую любовь.

Строго говоря, Ren'Py не является классической библиотекой Python, которую можно установить посредством pip install. Игры Ren'Py создаются и запускаются с помощью лаунчера, входящего в состав SDK Ren'Py, в котором есть и редактор игр, и собственный язык сценариев. Однако так как Ren'Py основан на Pygame, с ним можно работать и с помощью Python.

Установка Ren'Py

Ren'Py SDK доступен на Windows, Mac и Linux, пакет для соответствующей платформы можно скачать на официальном сайте. После установки перейдите в папку, содержащую SDK, и запустите Ren'Py Launcher.

🐍🕹️ Как написать игру на Python: 5 игровых движков

Базовые концепции Ren'Py

В той же программе можно начать новый проект, это создаст необходимую структуру файлов и папок. Хотя для запуска игр требуется Ren'Py Launcher, для редактирования кода можно использовать любой удобный редактор.

🐍🕹️ Как написать игру на Python: 5 игровых движков

Сценарии игр Ren'Py хранятся в файлах с расширением .rpy , написанных на специальном языке Ren'Py. Файлы хранятся в папке game/ внутри папки проекта.

Для нового проекта Ren'Py создаёт следующие сценарии, которые можно сразу же использовать и редактировать:

  • gui.rpy определяет внешний вид всех элементов пользовательского интерфейса;
  • options.rpy определяет изменяемые параметры для настройки игры;
  • screens.rpy описывает стили диалогов, меню и других элементов вывода информации;
  • script.rpy — место, где вы начинаете писать игру.

Чтобы запустить примеры игры из материалов этого руководства, следуйте инструкции:

  1. Запустите Ren'Py Launcher.
  2. Нажмите Preferences, затем Projects Directory.
  3. Измените Projects Directory на папку renpy из загруженного репозитория с примерами.
  4. Нажмите Return, чтобы вернуться на главную страницу Ren'Py Launcher.

В списке проектов слева вы увидите basic_game и giant_quest_game. Выберите, что хотите запустить и нажмите Launch Project.

Ниже мы рассмотрим script.rpy для basic_game.

Метки (labels) **определяют точки входа в историю, а также используются для запуска новых сцен и альтернативных путей прохождения истории. Все игры Ren'Py начинают работать с метки start:, которая может появляться в любом сценарии.

💡 Вы также можете использовать метки для определения фоновых изображений, настройки переходов между сценами и управления внешним видом персонажей. В примере вторая сцена начинается со строки с меткой check_room:.

Персонажей можно определить просто назвав их в истории или указав в начале сценария. Так мы определили главного персонажа, его маму и брата по имени Кевин. Оператор define инициализирует три переменные класса Characters.

Медиафайлы. Как и Pygame Zero, Ren'Py требует, чтобы все изображения и звуки, используемые в игре, находились в определенных папках — соответственно game/images/ и game/audio/. В сценарии игры к ним можно обращаться по имени файла без расширения.

💡 Пример. Когда ваш персонаж открывает глаза и впервые видит спальню, ключевое слово scene очищает экран, а затем показывает изображение спальни, хранящееся в day.png. Ren'Py поддерживает формы файлов изображений JPG, WEBP и PNG.

Ветвления сюжета. Игра не была бы игрой, если бы в ней нельзя было принимать решения. В Ren'Py возможности для выбора оформляются в виде меню. В ответ на выбор игра переходит к заданной метке, изменяет изображение и воспроизводит заданные звуки. В примере такой выбор возникает, когда главный персонаж понимает, что забыл свой телефон.

Продвинутый пример игры на Ren'Py

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

Персонажи и локации. В игре есть несколько персонажей: кузнец, волшебник и великан. И несколько локаций: родная деревушка, деревня оружейника, ведущая к волшебнику дорожка и пещера с грабителем-великаном.

В материалах к туториалу вы найдете четыре сценария:

  • script.rpy, где начинается игра;
  • town.rpy с историей близлежащей деревни;
  • path.rpy , который описывает тропу между деревнями;
  • giant.rpy, содержащий логику битвы с великаном.

Встреча с волшебником оставлена в качестве упражнения.

Описание объектов. Как и в предыдущем примере, в начале script.rpy мы определяем объекты с помощью Character(). Далее мы задаем несколько изображений, на которые ссылаемся позже для использования в качестве фона и отображения объектов. Использование специального синтаксиса позволяет назначать изображениям короткие внутренние имена.

Активный инвентарь. Чтобы показать, какое оружие сейчас активно, мы показываем его изображение в углу экрана. Для этого мы используем команду show с модификатором with moveinleft. Важно помнить, что при смене сцены экран очищается, поэтому нужно запускать команду повторно.

Смена оружия. При входе в город в town.rpy, вы встречаете приветствующего вас кузнеца:

🐍🕹️ Как написать игру на Python: 5 игровых движков

Кузнец предлагает улучшить ваше оружие. Если вы решите это сделать, вы обновите значения для current_weapon и характеристики оружия.

Операторы Python. Строки, начинающиеся с символа $, интерпретируются Ren'Py как операторы Python. Это позволяет прописывать в сценарии произвольный код Python. Обновление current_weapon и статистики оружия выполняется с помощью трех операторов Python, которые изменяют значения переменных по умолчанию, определенных в начале script.rpy.

Вы также можете определить большой блок кода Python, используя слово python:, как показано в файле giant.rpy, начиная со строки 41.

Сцена битвы управляется функцией fight_giant() и игровым циклом с переменной battle_over. Выбор игрока сражаться или бежать отображается с помощью метода renpy.display_menu(). Если игрок сражается, то великану наносится случайное количество урона и корректируются его очки здоровья. Если великан остается в живых, он может атаковать в ответ аналогичным образом. Обратите внимание, что у великана есть шанс промахнуться, в то время как игрок всегда попадает в цель. Бой продолжается до тех пор, пока у игрока или великана не закончится здоровье, либо пока игрок не сбежит.

Используемый код очень похож на тот, который мы использовали для описания битвы в adventurelib. Пример демонстирирует, как вы можете интегрировать код Python в Ren'Py без необходимости переводить его в сценарий Ren'Py.

Если вы заинтересовались движком, обратитесь к документации Ren'Py для получения более подробной информации.

Другие популярные игровые движки на Python

Описанная в статье пятерка библиотек — лишь небольшая выборка из множества доступных игровых движков на Python. Среди десятков доступных мы отметим также следующие:

  • Wasabi 2D разработан командой Pygame Zero. Это современная среда, построенная на moderngl , которая автоматизирует рендеринг, предоставляет готовые решения для анимационных эффектов, имеет встроенные эффекты и использует собственную модель игровых событий.
  • Panda 3D — платформа с открытым исходным кодом для создания 3D-игр и трехмерной визуализации. Panda 3D переносится на разные платформы, поддерживает несколько типов ресурсов, интегрируется с многочисленными сторонними библиотеками и обеспечивает встроенное профилирование.
  • Ursina построена на основе Panda 3D и предоставляет специальный движок для разработки игр, который упрощает многие аспекты Panda 3D. На момент написания статьи Ursina хорошо поддерживается и документируется.
  • PursuedPyBear позиционируется как образовательная библиотека с системой управления сценами, анимированными спрайтами и низким входным барьером.

Если вы знаете о других хороших движках на Python, не стесняйтесь рассказать в комментариях!

Источники контента для игр

Наполнение игр. Обычно самой трудной частью разработки игры является создание игровых ресурсов. Крупные компании, занимающиеся видеоиграми, нанимают команды художников, аниматоров и музыкантов для разработки внешнего вида и саунд-дизайна своих игр.

Разработчики-одиночки могут посчитать этот аспект разработки игр пугающим. Но к счастью, существует множество различных источников игровых ресурсов:

  • OpenGameArt.org предлагает широкий спектр артов, музыки, фонов, значков и других ресурсов для двумерных и трехмерных игр. Большинство файлов находятся в свободном доступе.
  • Kenney.nl содержит набор разнообразных бесплатных и платных ресурсов.
  • Itch.io — торговая площадка для создателей цифровых продуктов, ориентированных на независимую разработку игр. Здесь можно найти ресурсы практически для любых целей: и бесплатные, и платные, и даже готовые игры.

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

Заключение

Поздравляем — теперь вы знакомы с основами дизайна игр на Python! Благодаря стараниям GameDev-сообщества писать качественные компьютерные игры на Python сегодня намного проще, чем раньше.

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

***

Материалы по теме

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ