Создаем 2D игру на Python с библиотекой Arcade

    Всем привет!

    Мы продолжаем делится с вами интересными найденными вещами про питончик. Сегодня вот решили разобраться с 2D играми. Это, конечно, немного попроще, чем то, что проходят у нас на курсе «Разработчик Python», но не менее интересно это уж точно.

    ПЕРЕВОД
    Оригинал статьи — opensource.com/article/18/4/easy-2d-game-creation-python-and-arcade
    Автор — Paul Vincent Craven


    Поехали.

    Python — выдающийся язык для начинающих изучать программирование. Он также идеально подходит тем, кто хочет “просто взять и сделать”, а не тратить кучу времени на шаблонный код. Arcade — библиотека Python для создания 2D игр, с низким порогом вхождения, но очень функциональная в опытных руках. В этом статье я объясню, как начать использовать Python и Arcade для программирования игр.

    Я начал разрабатывать на Arcade после преподавания азов библиотеки PyGame студентам. Я очно преподавал PyGames в течение почти 10 лет, а также разработал ProgramArcadeGames.com для обучения онлайн. PyGames отличная, но в какой-то момент я понял, что устал тратить время на оправдание багов, которые никогда не фиксятся.

    Меня беспокоило преподавание таких вещей, как событийный цикл, которым уже почти не пользовались. И был целый раздел, в котором я объяснял, почему y-координаты повернуты в противоположном направлении. PyGames обновлялась редко и базировалась на старой библиотеке SDL 1, а не чем-то более современном вроде OpenGL. На светлое будущее я не рассчитывал.

    В моих мечтах была простая и мощная библиотека, которая бы использовала новые фичи Python 3, например, декораторы и тайп-хинтинг. Ей оказалась Arcade. Посмотрим, как начать ее использовать.



    Установка


    Arcade, как и многие другие пакеты, доступна на PyPi, а значит, можно установить Arcade при помощи команды pip (или pipenv). Если Python уже установлен, скорее всего можно просто открыть командную строку Windows и написать:

    pip install arcade

    А в Linux и MacOS:

    pip3 install arcade

    Для более детализированной инструкции по установке, почитайте документацию по установке Arcade.

    Простой рисунок


    Вы можете открыть окно и нарисовать простой рисунок всего несколькими строчками кода. В качестве примера, нарисуем смайлик, как на картинке ниже:



    Скрипт ниже показывает, как это сделать, используя команды рисования Arcade. Заметьте, что вам не обязательно знать, как использовать классы или определять функции. Программирование с быстрым визуальным фидбеком — хороший старт для тех, кто только учится.

    import arcade
    
    # Задать константы для размеров экрана
    SCREEN_WIDTH = 600
    SCREEN_HEIGHT = 600
    
    # Открыть окно. Задать заголовок и размеры окна (ширина и высота)
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")
    
    # Задать белый цвет фона.
    # Для просмотра списка названий цветов прочитайте:
    # http://arcade.academy/arcade.color.html
    # Цвета также можно задавать в (красный, зеленый, синий) и
    # (красный, зеленый, синий, альфа) формате.
    arcade.set_background_color(arcade.color.WHITE)
    
    # Начать процесс рендера. Это нужно сделать до команд рисования
    arcade.start_render()
    
    # Нарисовать лицо
    x = 300
    y = 300
    radius = 200
    arcade.draw_circle_filled(x, y, radius, arcade.color.YELLOW)
    
    # Нарисовать правый глаз
    x = 370
    y = 350
    radius = 20
    arcade.draw_circle_filled(x, y, radius, arcade.color.BLACK)
    
    # Нарисовать левый глаз
    x = 230
    y = 350
    radius = 20
    arcade.draw_circle_filled(x, y, radius, arcade.color.BLACK)
    
    # Нарисовать улыбку
    x = 300
    y = 280
    width = 120
    height = 100
    start_angle = 190
    end_angle = 350
    arcade.draw_arc_outline(x, y, width, height, arcade.color.BLACK, start_angle, 
                            end_angle, 10)
    
    # Завершить рисование и показать результат
    arcade.finish_render()
    
    # Держать окно открытым до тех пор, пока пользователь не нажмет кнопку “закрыть”
    arcade.run()

    Использование функций


    Конечно, писать код в глобальном контексте — не лучший способ. К счастью, использование функций поможет улучшить ваш код. Ниже приведен пример того, как нарисовать елку в заданных координатах (x, y), используя функцию:

    def draw_pine_tree(x, y):
        """ Эта функция рисует елку в указанном месте"""
        
        # Нарисовать треугольник поверх ствола.
        # Необходимы три x, y точки для рисования треугольника.
        arcade.draw_triangle_filled(x + 40, y,       # Point 1
                                    x, y - 100,      # Point 2
                                    x + 80, y - 100, # Point 3
                                    arcade.color.DARK_GREEN)
    
        # Нарисовать ствол
        arcade.draw_lrtb_rectangle_filled(x + 30, x + 50, y - 100, y - 140,
                                          arcade.color.DARK_BROWN)

    Для полного примера, посмотрите рисунок с функциями.



    Более опытные программисты знают, что современные программы сначала загружают графическую информацию на видеокарту, а затем просят ее отрисовать batch-файлом. Arcade это поддерживает. Индивидуальная отрисовка 10000 прямоугольников занимает 0.8 секунды. Отрисовка того же количества батником займет менее 0.001 секунды.

    Класс Window


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

    import arcade
    
    SCREEN_WIDTH = 800
    SCREEN_HEIGHT = 600
    
    
    class MyGame(arcade.Window):
        """ Главный класс приложения. """
    
        def __init__(self, width, height):
            super().__init__(width, height)
    
            arcade.set_background_color(arcade.color.AMAZON)
    
        def setup(self):
            # Настроить игру здесь
            pass
    
        def on_draw(self):
            """ Отрендерить этот экран. """
            arcade.start_render()
            # Здесь код рисунка
    
        def update(self, delta_time):
            """ Здесь вся игровая логика и логика перемещения."""
            pass
    
    
    def main():
        game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
        game.setup()
        arcade.run()
    
    
    if __name__ == "__main__":
        main() 

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

    • on_draw: Весь код для отрисовки экрана находится здесь.
    • update: Весь код для перемещения объектов и отработки игровой логики находится здесь. Вызывается примерно 60 раз в секунду.
    • on_key_press: Обрабатывает события при нажатии кнопки, например, движение персонажа.
    • on_key_release: Обрабатывает события при отпускании кнопки, например, остановка персонажа.
    • on_mouse_motion: Вызывается каждый раз при движении мышки.
    • on_mouse_press: Вызывается при нажатии кнопки мыши.
    • set_viewport: Эта функция используется в скроллерах, когда мир значительно больше, чем то что видно на одном экране. Вызов set_viewport позволяет программисту задать ту часть экрана, которая будет видна.

    Спрайты


    Спрайты — простой способ создания 2D bitmap объектов в Arcade. В нем есть методы, позволяющие с легкостью рисовать, перемещать и анимировать спрайты. Также можно использовать спрайты для отслеживания коллизий между объектами.

    Создание спрайта


    Создать инстанс Sprite класса Arcade очень легко. Программисту необходимо только название файла изображения, на котором будет основываться спрайт, и, опционально, число раз для увеличения или уменьшения изображения. Например:

    SPRITE_SCALING_COIN = 0.2
    
    coin = arcade.Sprite("coin_01.png", SPRITE_SCALING_COIN)

    Этот код создает спрайт, используя изображение coin_01.png. Картинка уменьшится до 20% от исходной.

    Список спрайтов


    Спрайты обычно организуются в списки. Они помогают упростить их управление. Спрайты в списке будут использовать OpenGl для групповой batch-отрисовки. Нижеприведенный код настраивает игру, где есть игрок и множество монет, которые игрок должен собрать. Мы используем два списка — один для игрока и один для монеток.

    def setup(self):
        """ Настроить игру и инициализировать переменные. """
    
        # Создать список спрайтов
        self.player_list = arcade.SpriteList()
        self.coin_list = arcade.SpriteList()
    
        # Счет
        self.score = 0
    
        # Задать игрока и
        # Его изображение из kenney.nl
        self.player_sprite = arcade.Sprite("images/character.png", 
                                           SPRITE_SCALING_PLAYER)
        self.player_sprite.center_x = 50 # Стартовая позиция
        self.player_sprite.center_y = 50
        self.player_list.append(self.player_sprite)
    
        # Создать монетки
        for i in range(COIN_COUNT):
    
            # Создать инстанс монеток
            # и их изображение из kenney.nl
            coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
    
            # Задать положение монеток
            coin.center_x = random.randrange(SCREEN_WIDTH)
            coin.center_y = random.randrange(SCREEN_HEIGHT)
    
            # Добавить монетку к списку 
            self.coin_list.append(coin)


    Мы с легкостью можем отрисовать все монетки в списке монеток:

    def on_draw(self):
        """ Нарисовать все """
        arcade.start_render()
        self.coin_list.draw()
        self.player_list.draw()

    Отслеживание коллизий спрайтов


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

    def update(self, delta_time):
        # Сгенерировать список всех спрайтов монеток, которые пересекаются с игроком.
        coins_hit_list = arcade.check_for_collision_with_list(self.player_sprite, 
                                                              self.coin_list)
    
        # Пройтись циклом через все пересекаемые спрайты, удаляя их и увеличивая счет.
        for coin in coins_hit_list:
            coin.kill()
            self.score += 1

    С полным примером можно ознакомиться в collect_coins.py.

    Игровая физика


    Во многих играх есть физика в том или ином виде. Самые простое, например, что top-down игры не позволяют игроку проходить сквозь стены. Платформеры добавляют сложности с гравитацией и движущимися платформами. Некоторые игры используют полноценные физические 2D движки с массами, трением, пружинами и тд.

    Top-down игры




    Для простых игр с видом сверху программе на Arcade необходим список стен (или чего-то подобного), через которые игрок не сможет проходить. Обычно я называю это wall_list. Затем создается физический движок в установочном коде класса Window:

    self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)

    player_sprite получает вектор движения с двумя атрибутами change_x и change_y. Просто пример использования — перемещение игрока с помощью клавиатуры.

    MOVEMENT_SPEED = 5
    
    def on_key_press(self, key, modifiers):
        """Вызывается при нажатии пользователем клавиши"""
    
        if key == arcade.key.UP:
            self.player_sprite.change_y = MOVEMENT_SPEED
        elif key == arcade.key.DOWN:
            self.player_sprite.change_y = -MOVEMENT_SPEED
        elif key == arcade.key.LEFT:
            self.player_sprite.change_x = -MOVEMENT_SPEED
        elif key == arcade.key.RIGHT:
            self.player_sprite.change_x = MOVEMENT_SPEED
    
    def on_key_release(self, key, modifiers):
        """Вызывается, когда пользователь отпускает клавишу"""
    
        if key == arcade.key.UP or key == arcade.key.DOWN:
            self.player_sprite.change_y = 0
        elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
            self.player_sprite.change_x = 0

    Несмотря на то что этот код задает скорость игрока, он его не перемещает. Метод update в классе Window вызывает physics_engine.update(), что заставит игрока двигаться, но не через стены.

    def update(self, delta_time):
        """ Передвижение и игровая логика """
    
         self.physics_engine.update()

    Пример полностью можно посмотреть в sprite_move_walls.py.

    Платформеры




    Переход к платформеру с видом сбоку достаточно прост. Программисту необходимо переключить физический движок на PhysicsEnginePlatformer и добавить гравитационную константу.

    self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
                                                         self.wall_list, 
                                                         gravity_constant=GRAVITY)

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

    Пример доступен в sprite_tiled_map.py.

    Учитесь на примере


    Учиться на примере — один из лучших методов. В библиотеке Arcade есть большой список образцов программ, на которые можно ориентироваться при создании игры. Эти примеры раскрывают концепты игр, о которых спрашивали мои онлайн и оффлайн студенты в течение нескольких лет.

    Запускать демки при установленной Arcade совсем не сложно. В начале программы каждого примера есть комментарий с командой, которую нужно ввести в командную строку для запуска этого примера. Например:

    python -m arcade.examples.sprite_moving_platforms

    THE END

    Как всегда ждём ваши комментарии и вопросы, которые можно оставить тут или зайти к Стасу на день открытых дверей.

    Отус

    243,00

    Профессиональные онлайн-курсы для разработчиков

    Поделиться публикацией
    Комментарии 10
      +2
      Интересная статья, но:
      новые фичи Python 3, например, декораторы и тайм-хинтинг

      Декораторы — фича далеко не новая, потому что появились они в Python 2.4.

      И поправьте, пожалуйста «тайм-хинтинг». Если я правильно понял, имелось в виду «тайП-хинтинг». Но даже если так, то не очень понятно, так ли уж важно, использует библиотека тайп-хинтинг или нет?
        0
        Для автора статьи новая, можно сказать, учитывая сколько у него боли на предыдущую библиотеку.

        Поправил, спасибо. Опять же тут личное восприятие автора, которому может как раз именно такого не хватало для полного счастья :)

        P.S. внезапно только счас заметил, что пост вместо перевода стал публикацией, когда восстанавливал из автосохранения О_о
        opensource.com/article/18/4/easy-2d-game-creation-python-and-arcade — оригинал статьи, собственно.
          +2
          Сделайте вручную плашку «ПЕРЕВОД» в начале статьи, прямо в тексте.
            0
            Спасибо за подсказку. Сделал.
        +2

        В части про спрайты точно ли "Картинка уменьшится на 20% по сравнении с шириной и высотой оригинала." при параметре SPRITE_SCALING_COIN = 0.2 ???


        Может она будет составлять 20% от исходной?

          +1
          Упс. Да. Ошибочка вышла. Поправил, спасибо большое.
          +1
          Она чисто учебная или есть какие-то примеры использования на практике? На первый взгляд должно быть вполне приемлемо для мелких игр (например, на джемы), но что-то не видать на сайте сделанных с её помощью игр.
            0
            Скорее больше обучалка, поэтому в отличие от pygames, там особо ничего нет, но несколько инди-аркадок сделанных на Arcade я точно видел. И делались они как раз, грубо говоря, на конфах :)
            0
            Спасибо за кейс!
              0
              Выглядит как Phaser на Python

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

              Самое читаемое