Глава 23 · Приложение

Домашний проект D: отскакивающие мячи с Pygame

Позиция и скорость хранятся в Vector2, а движение учитывает dt. Скорость измеряется в единицах за секунду, а не за кадр.

Мяч описан классом Myach. Позиция и скорость хранятся как pygame.Vector2, а скорость измеряется в пикселях в секунду, а не в пикселях за кадр:

bouncing_balls.py
class Myach:
    def __init__(self, x, y, vx, vy, radius, cvet):
        self.pos = Vector2(x, y)
        self.velocity = Vector2(vx, vy)  # пикселей в секунду
        self.radius = radius
        self.cvet = cvet
        self.otskokov = 0

    def shag(self, dt: float) -> None:
        self.pos += self.velocity * dt
        if self.pos.x - self.radius < 0 or self.pos.x + self.radius > SHIRINA:
            self.velocity.x = -self.velocity.x
            self.otskokov += 1
        # ...то же самое для self.pos.y и VYSOTA
Учитываем dt при изменении позиции
Глава 20 уже показывала эту ошибку: если прибавлять скорость к позиции на каждый кадр без учёта dt (реального времени, прошедшего с прошлого кадра), мяч на экране с частотой 120 кадров в секунду улетит вчетверо дальше за одну и ту же секунду, чем на 30 кадрах в секунду. Умножение на dt выражает скорость в единицах за секунду и сильно уменьшает зависимость от FPS. Свободное равномерное движение совпадает при разных FPS в пределах численной точности, но дискретное обнаружение столкновений всё ещё может зависеть от размера шага времени.

Окно и цикл отрисовки создаются только внутри main(). импорт файла bouncing_balls.py не открывает окно pygame, а значит, Myach.shag() можно проверить тестом без дисплея:

bouncing_balls.py
def main() -> None:
    pygame.init()
    screen = pygame.display.set_mode((SHIRINA, VYSOTA))
    clock = pygame.time.Clock()
    myachi = sozdat_myachi()
    while rabotaet:
        dt = clock.tick(FPS) / 1000
        for myach in myachi:
            myach.shag(dt)
        narisovat_kadr(screen, myachi)

Полный код: projects/pygame/bouncing-balls-oop/bouncing_balls.py

Задание

★★ Самостоятельная задача
Упрощённое лобовое столкновение

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

Почему ограничение обязательно
При произвольном 2D-столкновении меняются нормальные компоненты скоростей вдоль линии центров, а касательные сохраняются; также нужны проверка сближения и коррекция проникновения. Обмен полных векторов корректен только в сформулированной лобовой модели одинаковых масс.
GitHubGitHub-практика

Issue «Add bouncing balls project», ветка feat/bouncing-balls, тест на независимость движения от FPS (30/60/120 кадров дают одинаковый результат за одну и ту же секунду), Pull Request.

Практика: класс Myach и несколько мячей сразу
Pygame открывает нативное окно Python. Выполните практику локально в VS Code, PyCharm или Jupyter
Практика выполняется локально
Открыть практику →