Физика в Python с использованием Pymunk

Физика. Кто-то её любит, кто-то нет, но определённо это неотъемлемая часть нашего существования. В этой статье мы рассмотрим как самому создавать физические симуляции используя всего 2 библиотеки Python.

К концу статьи мы сделаем интерактивную симуляцию взаимодействия тел и поймём основы использования библиотеки Pymunk.

image-loader.svg

Моя терминология:

Раздел — кусок кода который я помечаю комментарием, чтобы описать это в статье
(В качестве термина не используется, но мне так проще вам объяснить)

Подготовимся для начала работы

И так, создадим новый файл Python в удобной для вас IDE

image-loader.svg

Теперь нам нужно установить Pymunk и Pygame. А так же настроить их для совместной работы.
Не забудьте установить их в консоли проекта через команду pip install

Вот код:

#импорт модулей
import pygame as pg
import pymunk.pygame_util

Система координат PyGame отличается от системы координат PyMunk, нам нужно подогнать их под одну систему координат

PyGamePyGamePyMunkPyMunk

Добавим код в «импорт модулей» для исправления этого

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)

Наконец мы можем посмотреть что выходит.

image-loader.svg

Наше окно готово к созданию симуляции.

Создаём Пространство

Но для начала разберёмся с основными понятиями библиотеки 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 — Коэффициент трения

image-loader.svg

Эта серая полоска — наш первый физический объект, оказалось сделать такое несложно.
Будем считать её полом для симуляции.

Интерактивные квадраты и их взаимодействие

Вы добрались до ключевого раздела статьи, поздравляю!
Сейчас мы разберёмся как создавать динамические тела и как сделать для них правильное взаимодействие.

Идея:
Кликая на экран программа будет спавнить квадраты поддающиеся законам физики.

Приступаем к реализации:

#квадратики
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, форумы, официальная документация.

© Habrahabr.ru