Физика в Python с использованием Pymunk
Физика. Кто-то её любит, кто-то нет, но определённо это неотъемлемая часть нашего существования. В этой статье мы рассмотрим как самому создавать физические симуляции используя всего 2 библиотеки Python.
К концу статьи мы сделаем интерактивную симуляцию взаимодействия тел и поймём основы использования библиотеки Pymunk.
Моя терминология:
Раздел — кусок кода который я помечаю комментарием, чтобы описать это в статье
(В качестве термина не используется, но мне так проще вам объяснить)
Подготовимся для начала работы
И так, создадим новый файл Python в удобной для вас IDE
Теперь нам нужно установить Pymunk и Pygame. А так же настроить их для совместной работы.
Не забудьте установить их в консоли проекта через команду pip install
Вот код:
#импорт модулей
import pygame as pg
import pymunk.pygame_util
Система координат PyGame отличается от системы координат PyMunk, нам нужно подогнать их под одну систему координат
PyGamePyMunk
Добавим код в «импорт модулей» для исправления этого
pymunk.pygame_util.positive_y_is_up = False
Ура, мы подогнали систему координат и теперь наши библиотеки могут работать друг с другом.
Отрисовка в Pygame
Чтобы визуализировать нашу симуляцию мы будем использовать не сложный код
Зададим разрешение экрана и количество FPS.
#Настройки PyGame
RES = WIDTH, HEIGHT = 900, 720
FPS = 60
pg.init()
surface = pg.display.set_mode(RES)
clock = pg.time.Clock()
draw_options = pymunk.pygame_util.DrawOptions(surface)
WIDTH и HEIGHT это параметры разрешения окна Pygame.
FPS — это количество кадров обрисовываемые в секунду (стандартом считается 60)
Теперь создадим цикл отрисовки. Он будет всегда находиться в самом конце кода.
while True:
surface.fill(pg.Color('black'))
for i in pg.event.get():
if i.type == pg.QUIT:
exit()
pg.display.flip()
clock.tick(FPS)
Код на данный момент выглядит так:
#имопрт модулей
import pygame as pg
import pymunk.pygame_util
pymunk.pygame_util.positive_y_is_up = False
#Настройки PyGame
RES = WIDTH, HEIGHT = 900, 720
FPS = 60
pg.init()
surface = pg.display.set_mode(RES)
clock = pg.time.Clock()
draw_options = pymunk.pygame_util.DrawOptions(surface)
#Отрисовка PyGame
while True:
surface.fill(pg.Color('black'))
for i in pg.event.get():
if i.type == pg.QUIT:
exit()
pg.display.flip()
clock.tick(FPS)
Наконец мы можем посмотреть что выходит.
Наше окно готово к созданию симуляции.
Создаём Пространство
Но для начала разберёмся с основными понятиями библиотеки PyMunk.
Для начала нам нужно создать пространство (space)
Space — основная единица моделирования PyMunk. Вы добавляете к нему твердые тела, формы и суставы, а затем продвигаете их вперед вместе во времени.
Создаём новый комментарий «Переменные PyMunk» который будет находиться после нашего раздела «Настройки PyGame» и создаём там space
#переменные Pymunk
space = pymunk.Space()
Пространство есть, пора создать гравитацию.
space.gravity = 0, 8000
Теперь наш раздел «Переменные PyMunk» выглядит так:
#настройки Pymunk
space = pymunk.Space()
space.gravity = 0, 8000
После добавления нужных переменных важно сделать следующее:
Добавим пару строк в отрисовку чтобы она рисовала наши тела созданные в PyMunk:
#Отрисовка
while True:
surface.fill(pg.Color('black'))
for i in pg.event.get():
if i.type == pg.QUIT:
exit()
#Мы добавили вот эти 2 строки:
space.step(1 / FPS)
space.debug_draw(draw_options)
pg.display.flip()
clock.tick(FPS)
Код на данный момент выглядит так:
#импорт модулей:
import pygame as pg
from random import randrange
import pymunk.pygame_util
pymunk.pygame_util.positive_y_is_up = False
#параметры PyGame
RES = WIDTH, HEIGHT = 900, 720
FPS = 60
pg.init()
surface = pg.display.set_mode(RES)
clock = pg.time.Clock()
draw_options = pymunk.pygame_util.DrawOptions(surface)
#настройки Pymunk
space = pymunk.Space()
space.gravity = 0, 8000
#Отрисовка
while True:
surface.fill(pg.Color('black'))
for i in pg.event.get():
if i.type == pg.QUIT:
exit()
space.step(1 / FPS)
space.debug_draw(draw_options)
pg.display.flip()
clock.tick(FPS)
Создаём физические тела
В PyMunk есть 3 типа физических объектов: Динамические, Статические и Кинематические
Динамические (Dynamic) — Объекты реагируют на столкновения, на них действуют силы и гравитация, они имеют конечную массу.
Динамические тела взаимодействуют со всеми типами тел.Кинематические (Kinematic) — Это объекты, которые управляются из вашего кода, а не внутри физического движка. На них не действует гравитация. Хорошие примеры кинематических тел: летающие платформы в играх
Статические (Static) — Тела которые никогда не двигаются, но могут взаимодействовать с другими телами.
Хороший пример — стены и полы в играх.
Создадим Статическую платформу:
Пишем вот такой код после раздела «настройки Pymunk»
#платформа
segment_shape = pymunk.Segment(space.static_body, (1, HEIGHT), (WIDTH, HEIGHT), 26)
space.add(segment_shape)
segment_shape.elasticity = 0.4
segment_shape.friction = 1.0
space.add (названия объектов через запятую) — добавляет объекты в пространство
segment_shape.elasticity — Коэффициент упругости
segment_shape.friction — Коэффициент трения
Эта серая полоска — наш первый физический объект, оказалось сделать такое несложно.
Будем считать её полом для симуляции.
Интерактивные квадраты и их взаимодействие
Вы добрались до ключевого раздела статьи, поздравляю!
Сейчас мы разберёмся как создавать динамические тела и как сделать для них правильное взаимодействие.
Идея:
Кликая на экран программа будет спавнить квадраты поддающиеся законам физики.
Приступаем к реализации:
#квадратики
def create_square(space, pos):
square_mass, square_size = 1, (60, 60)
square_moment = pymunk.moment_for_box(square_mass, square_size)
square_body = pymunk.Body(square_mass, square_moment)
square_mass
- масса квадрата (в наше случае равняется одному)
square_size
- размер квадрата. Передаётся в формате (width, height)
square_moment = pymunk.moment_for_box —
автоматически рассчитываем момент инерции для полого круга зная массу и размер
quare_body = pymunk.Body —
экземпляр тела
Время для первой фичи: Cделаем так, чтобы квадраты появлялись на месте где стоит курсор
def create_square(space, pos):
square_mass, square_size = 1, (60, 60)
square_moment = pymunk.moment_for_box(square_mass, square_size)
square_body = pymunk.Body(square_mass, square_moment)
# появление на позиции курсора
square_body.position = pos
square_body.position
- позиция тела
def create_square(space, pos):
square_mass, square_size = 1, (60, 60)
square_moment = pymunk.moment_for_box(square_mass, square_size)
square_body = pymunk.Body(square_mass, square_moment)
square_body.position = pos
square_shape = pymunk.Poly.create_box(square_body, square_size)
square_shape.elasticity = 0.8
square_shape.friction = 1.0
Фича под номером два: цвет кубиков будет рандомен.
Сделать это можно так:
square_shape.color = [randrange(256) for i in range(4)]
Наконец дописываем функцию, в итоге она выглядит так:
def create_square(space, pos):
square_mass, square_size = 1, (60, 60)
square_moment = pymunk.moment_for_box(square_mass, square_size)
square_body = pymunk.Body(square_mass, square_moment)
square_body.position = pos
square_shape = pymunk.Poly.create_box(square_body, square_size)
square_shape.elasticity = 0.8
square_shape.friction = 1.0
square_shape.color = [randrange(256) for i in range(4)]
space.add(square_body, square_shape)
напоминаю что space.add добавляет тело в пространств
Мы на финишной прямой! Осталось только обновить отрисовку.
# спавн кубиков
if i.type == pg.MOUSEBUTTONDOWN:
if i.button == 1:
create_square(space, i.pos)
print(i.pos)
В итоге отрисовка выглядит так:
#Отрисовка
while True:
surface.fill(pg.Color('black'))
for i in pg.event.get():
if i.type == pg.QUIT:
exit()
# спавн мячиков
if i.type == pg.MOUSEBUTTONDOWN:
if i.button == 1:
create_square(space, i.pos)
print(i.pos)
space.step(1 / FPS)
space.debug_draw(draw_options)
pg.display.flip()
clock.tick(FPS)
print('end')
Проверяем
Считаю что получилось отлично :)
Результат
Весь исходный код:
#импорт модулей:
import pygame as pg
from random import randrange
import pymunk.pygame_util
pymunk.pygame_util.positive_y_is_up = False
#параметры PyGame
RES = WIDTH, HEIGHT = 900, 720
FPS = 60
pg.init()
surface = pg.display.set_mode(RES)
clock = pg.time.Clock()
draw_options = pymunk.pygame_util.DrawOptions(surface)
#настройки Pymunk
space = pymunk.Space()
space.gravity = 0, 8000
#платформа
segment_shape = pymunk.Segment(space.static_body, (2, HEIGHT), (WIDTH, HEIGHT), 26)
space.add(segment_shape)
segment_shape.elasticity = 0.8
segment_shape.friction = 1.0
#квадратики
body = pymunk.Body()
def create_square(space, pos):
square_mass, square_size = 1, (60, 60)
square_moment = pymunk.moment_for_box(square_mass, square_size)
square_body = pymunk.Body(square_mass, square_moment)
square_body.position = pos
square_shape = pymunk.Poly.create_box(square_body, square_size)
square_shape.elasticity = 0.4
square_shape.friction = 1.0
square_shape.color = [randrange(256) for i in range(4)]
space.add(square_body, square_shape)
#Отрисовка
while True:
surface.fill(pg.Color('black'))
for i in pg.event.get():
if i.type == pg.QUIT:
exit()
# спавн кубиков
if i.type == pg.MOUSEBUTTONDOWN:
if i.button == 1:
create_square(space, i.pos)
print(i.pos)
space.step(1 / FPS)
space.debug_draw(draw_options)
pg.display.flip()
clock.tick(FPS)
print('end')
Итог
Мы создали небольшую программу на PyGame и PyMunk с симуляцией физики.
Дальше вы можете сами изучать документацию, статьи и видео с ютюба.
Но я тоже не собираюсь заканчивать на одной статье по этой теме.
До связи)
Материалы: Standalone Coder, форумы, официальная документация.