Создаем 2D игру на Python с библиотекой Arcade
Всем привет!
Мы продолжаем делится с вами интересными найденными вещами про питончик. Сегодня вот решили разобраться с 2D играми. Это, конечно, немного попроще, чем то, что проходят у нас на курсе «Разработчик Python», но не менее интересно это уж точно.
Поехали.
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
Как всегда ждём ваши комментарии и вопросы, которые можно оставить тут или зайти к Стасу на день открытых дверей.