RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1273311
Accepted
gil9red
gil9red
Asked:2022-04-22 16:04:17 +0000 UTC2022-04-22 16:04:17 +0000 UTC 2022-04-22 16:04:17 +0000 UTC

如何解决球互相挂着的问题?

  • 772

可以通过点击场地来创建球,但为了说明问题,我已经提前创建了 2 个球。

运动时,球可以互相进入,斥力机构起作用,这会改变球的运动矢量,但球可能来不及退出,就会出现挂起。或者用户可以将球粘在一起,它们也将在彼此中。

挂断:

在此处输入图像描述

最小的例子:

import math
from random import randint

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtCore import Qt, QPointF, QTimer


class Ball:
    def __init__(self, pos, r=25):
        self.x = pos.x()
        self.y = pos.y()
        self.r = r

        self.dx = self.dy = 0
        while self.dx == 0 and self.dy == 0:
            self.dx = randint(-3, 3)
            self.dy = randint(-3, 3)

        self.color = QColor(randint(0, 255), randint(0, 255), randint(0, 255))

    @property
    def center(self):
        return self.x, self.y

    @property
    def top(self):
        return self.y - self.r

    @property
    def bottom(self):
        return self.y + self.r

    @property
    def left(self):
        return self.x - self.r

    @property
    def right(self):
        return self.x + self.r

    def move(self):
        self.x += self.dx
        self.y += self.dy

    def is_collide(self, other):
        dx = self.x - other.x
        dy = self.y - other.y
        return math.sqrt(dx * dx + dy * dy) < self.r + other.r

    def draw(self, painter):
        painter.setPen(Qt.black)
        painter.setBrush(self.color)

        painter.drawEllipse(QPointF(self.x, self.y), self.r, self.r)


class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.timer = QTimer()
        self.timer.timeout.connect(self._on_timeout)
        self.timer.setInterval(30)
        self.timer.start()

        self.balls = [
            Ball(QPointF(200, 200)),
            Ball(QPointF(220, 220))
        ]

    def _on_timeout(self):
        for ball in self.balls:
            ball.move()

            # Отталкивание от стены
            if ball.left <= 0 or ball.right >= self.width():
                # Возврат шарика, если тот в стене
                if ball.left < 0:
                    ball.x = 0 + ball.r
                if ball.right > self.width():
                    ball.x = self.width() - ball.r

                ball.dx *= -1

            # Отталкивание от стены
            if ball.top <= 0 or ball.bottom >= self.height():
                # Возврат шарика, если тот в стене
                if ball.top < 0:
                    ball.y = 0 + ball.r
                if ball.bottom > self.height():
                    ball.y = self.height() - ball.r

                ball.dy *= -1

        num = len(self.balls)
        # Проверка столкнования шариков
        for i in range(num):
            for j in range(i + 1, num):
                if self.balls[i].is_collide(self.balls[j]):
                    self.balls[i].dx, self.balls[j].dx = self.balls[j].dx, self.balls[i].dx
                    self.balls[i].dy, self.balls[j].dy = self.balls[j].dy, self.balls[i].dy

        self.update()

    def mouseReleaseEvent(self, event):
        ball = Ball(event.pos())
        self.balls.append(ball)
        self.update()
    
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        for ball in self.balls:
            ball.draw(painter)


if __name__ == '__main__':
    app = QApplication([])

    w = Window()
    w.show()

    app.exec()

我认为我们需要确保球在它们设法相互进入的距离内相互排斥,但是算法的想法存在问题。

我扔了它,大约和我想象的一样(红线是球相互进入的距离,箭头是它们矢量的方向):

在此处输入图像描述


UPD。当球在改变矢量之前发生碰撞时,我试图后退一步,但这没有帮助:

    def move_back(self):
        self.x -= self.dx
        self.y -= self.dy
#    ^^^^^^^^^^^^^^^^^^^^^^^
...

        for i in range(num):
            for j in range(i + 1, num):
                if self.balls[i].is_collide(self.balls[j]):
                    self.balls[i].move_back()
                    self.balls[j].move_back()
#                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^

                    self.balls[i].dx, self.balls[j].dx = self.balls[j].dx, self.balls[i].dx
                    self.balls[i].dy, self.balls[j].dy = self.balls[j].dy, self.balls[i].dy

UPD2。更改向量后更进一步也无济于事:

                    self.balls[i].dx, self.balls[j].dx = self.balls[j].dx, self.balls[i].dx
                    self.balls[i].dy, self.balls[j].dy = self.balls[j].dy, self.balls[i].dy

                    self.balls[i].move()
                    self.balls[j].move()

UPD3。可以部分解决问题,但这是一个狂野的拐杖,当它们相互进入时,球会移动很远,这可能会导致连锁反应。在这里,我试图找出球之间的距离有多大,如果它小于一个半径的值,那么两个球都被赋予了运动:

        # Проверка столкнования шариков
        for i in range(num):
            for j in range(i + 1, num):
                if self.balls[i].is_collide(self.balls[j]):
                    dx = self.balls[i].x - self.balls[j].x
                    dy = self.balls[i].y - self.balls[j].y
                    d = math.sqrt(dx * dx + dy * dy)
                    
                    self.balls[i].dx, self.balls[j].dx = self.balls[j].dx, self.balls[i].dx
                    self.balls[i].dy, self.balls[j].dy = self.balls[j].dy, self.balls[i].dy

                    if d < self.balls[i].r * 1.8:
                        self.balls[i].x += -d * (1 if self.balls[i].dx < 0 else -1)
                        self.balls[i].y += -d * (1 if self.balls[i].dy < 0 else -1)

                        self.balls[j].x += -d * (1 if self.balls[j].dx < 0 else -1)
                        self.balls[j].y += -d * (1 if self.balls[j].dy < 0 else -1)

UPD4。也是一个旁路,但这次是由用户创建球。在这里,在鼠标单击事件中,我们检查新球是否与其他球不相交:

    def mouseReleaseEvent(self, event):
        ball = Ball(event.pos())
        if any(x.has_collide(ball) for x in self.balls):
            return

        self.balls.append(ball)
        self.update()
python
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. R7RBD
    2022-04-23T08:50:57Z2022-04-23T08:50:57Z

    我冒昧地对与您的问题相关的代码部分进行了现代化改造,因此我发布了整个代码,并重复并评论了我的更改

    # Убрал библиотеку math так как считаю нецелосообразным её использование
    from random import randint
    
    from PyQt5.QtWidgets import QApplication, QWidget
    from PyQt5.QtGui import QPainter, QColor
    from PyQt5.QtCore import Qt, QPointF, QTimer
    
    
    class Ball:
        def __init__(self, pos, r=25):
            self.x = pos.x()
            self.y = pos.y()
            self.r = r
    
            self.dx = self.dy = 0
            while self.dx == 0 and self.dy == 0:
                self.dx = randint(-3, 3)
                self.dy = randint(-3, 3)
    
            self.color = QColor(randint(0, 255), randint(0, 255), randint(0, 255))
    
        @property
        def center(self):
            return self.x, self.y
    
        @property
        def top(self):
            return self.y - self.r
    
        @property
        def bottom(self):
            return self.y + self.r
    
        @property
        def left(self):
            return self.x - self.r
    
        @property
        def right(self):
            return self.x + self.r
    
        def move(self):
            self.x += self.dx
            self.y += self.dy
    
        def is_collide(self, other):
            dx = self.x - other.x
            dy = self.y - other.y
            # Переписано выражение return (убрана библиотека math, несколько ускорилось вычесление)
            return (dx ** 2 + dy ** 2) ** .5 <= self.r + other.r
    
        def crash_move(self, other):
            # Метод обменивающий ускорения при столкновении
            # Почситал его перенос в класс шаров здравой идеей
            self.dx, other.dx = other.dx, self.dx
            self.dy, other.dy = other.dy, self.dy
    
        def crash_corrector(self, other, speed_drop=1):
            """
            :param other: ball class
            :param speed_drop: Коэффициент уменьшения силы отталкивания
            :return: добавочные x и y
            """
            x = self.x - other.x
            x /= speed_drop
            y = self.y - other.y
            y /= speed_drop
            return {'x': x, 'y': y}
    
        def draw(self, painter):
            painter.setPen(Qt.black)
            painter.setBrush(self.color)
    
            painter.drawEllipse(QPointF(self.x, self.y), self.r, self.r)
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
    
            self.timer = QTimer()
            self.timer.timeout.connect(self._on_timeout)
            self.timer.setInterval(30)
            self.timer.start()
    
            self.balls = [
                Ball(QPointF(200, 200)),
                Ball(QPointF(200, 210))
            ]
    
        def _on_timeout(self):
            for ball in self.balls:
                ball.move()
    
                # Отталкивание от стены
                if ball.left <= 0 or ball.right >= self.width():
                    # Возврат шарика, если тот в стене
                    if ball.left < 0:
                        ball.x = 0 + ball.r
                    elif ball.right > self.width():  
                        # if замена на elif [ради оптимизации скорости выполнения]
                        ball.x = self.width() - ball.r
    
                    ball.dx *= -1
    
                # Отталкивание от стены
                if ball.top <= 0 or ball.bottom >= self.height():
                    # Возврат шарика, если тот в стене
                    if ball.top < 0:
                        ball.y = 0 + ball.r
                    elif ball.bottom > self.height(): 
                        # if замена на elif [ради оптимизации скорости выполнения]
                        ball.y = self.height() - ball.r
    
                    ball.dy *= -1
                # Новый метод для обработки столкновений
                self.ball_collide(ball=ball)
                """
                Целиком убран дополнительный for с проверкой 
                в силу того что аналогичную пробежку 
                по шарикам выполняем выше [опять оптимизация]
                """
    
            self.update()
    
        def mouseReleaseEvent(self, event):
            ball = Ball(event.pos())
            self.balls.append(ball)
            self.update()
    
        def paintEvent(self, event):
            painter = QPainter(self)
            painter.setRenderHint(QPainter.Antialiasing)
    
            for ball in self.balls:
                ball.draw(painter)
    
        def ball_collide(self, ball):
            # Проверяем коллизию с каждым шаром от текущего присланного из главного цикла
            for check_ball in self.balls:
                # Проверяем колизию и проверяем что шар не отношению к самому себе
                if check_ball.is_collide(ball) and check_ball != ball:
                    check_ball.crash_move(ball) # производим обмен "силами"
                    k = check_ball.crash_corrector(ball, speed_drop=10)  # Считаем поправки
                    # Двигаем шарик и перерисовываем (Условно защита от диких разгонов)
                    check_ball.move()
                    self.update()
                    if check_ball.is_collide(ball):
                        check_ball.dx += k['x'] # Применяем поправки и опять перерисовываем
                        check_ball.dy += k['y']
                        ball.dx -= k['x']
                        ball.dy -= k['y']
                        ball.move()
                        check_ball.move()
                        self.update()
    
    
    if __name__ == '__main__':
        app = QApplication([])
        w = Window()
        w.show()
        app.exec()
    
    
    • 4
  2. Best Answer
    Stanislav Volodarskiy
    2022-04-24T04:42:21Z2022-04-24T04:42:21Z

    纠正你的代码并不难:只有当球接近时,你才需要改变球的速度。这也适用于球之间的碰撞以及球与墙壁的碰撞。

    如果用户创建了重叠的球,则无需执行任何操作。过一会,他们就会自己从路口出来。

    在下面的代码中,对墙壁的撞击和球之间的相互撞击是固定的:

    import math
    from random import randint
    
    from PyQt5.QtWidgets import QApplication, QWidget
    from PyQt5.QtGui import QPainter, QColor
    from PyQt5.QtCore import Qt, QPointF, QTimer
    
    
    class Ball:
        def __init__(self, pos, r=25):
            self.x = pos.x()
            self.y = pos.y()
            self.r = r
    
            self.dx = self.dy = 0
            while self.dx == 0 and self.dy == 0:
                self.dx = randint(-3, 3)
                self.dy = randint(-3, 3)
    
            self.color = QColor(randint(0, 255), randint(0, 255), randint(0, 255))
    
        @property
        def center(self):
            return self.x, self.y
    
        @property
        def top(self):
            return self.y - self.r
    
        @property
        def bottom(self):
            return self.y + self.r
    
        @property
        def left(self):
            return self.x - self.r
    
        @property
        def right(self):
            return self.x + self.r
    
        def move(self):
            self.x += self.dx
            self.y += self.dy
    
        def is_collide(self, other):
            dx = self.x - other.x
            dy = self.y - other.y
            return math.sqrt(dx * dx + dy * dy) < self.r + other.r
    
        def draw(self, painter):
            painter.setPen(Qt.black)
            painter.setBrush(self.color)
    
            painter.drawEllipse(QPointF(self.x, self.y), self.r, self.r)
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
    
            self.timer = QTimer()
            self.timer.timeout.connect(self._on_timeout)
            self.timer.setInterval(30)
            self.timer.start()
    
            self.balls = [
                Ball(QPointF(200, 200)),
                Ball(QPointF(220, 220))
            ]
    
        def _on_timeout(self):
            for ball in self.balls:
                ball.move()
    
                # Отталкивание от левой стены
                if ball.left <= 0:
                    if ball.dx < 0:
                        ball.dx = -ball.dx
    
                # Отталкивание от правой стены
                if ball.right >= self.width():
                    if ball.dx > 0:
                        ball.dx = -ball.dx
    
                # Отталкивание от верхней стены
                if ball.top <= 0:
                    if ball.dy < 0:
                        ball.dy = -ball.dy
    
                # Отталкивание от нижней стены
                if ball.bottom >= self.height():
                    if ball.dy > 0:
                        ball.dy = -ball.dy
    
            num = len(self.balls)
            # Проверка столкнования шариков
            for i in range(num):
                for j in range(i + 1, num):
                    if self.balls[i].is_collide(self.balls[j]):
                        # вектор из центра шара i в центр шара j
                        pij = [self.balls[j].x - self.balls[i].x, self.balls[j].y - self.balls[i].y]
                        # скорость шара j относительно шара i
                        vij = [self.balls[j].dx - self.balls[i].dx, self.balls[j].dy - self.balls[i].dy]
                        # скалярное произведение векторов
                        # если оно отрицательно, шары сближаются
                        # только в этом случае надо менять скорости
                        if pij[0] * vij[0] + pij[1] * vij[1] < 0:
                            self.balls[i].dx, self.balls[j].dx = self.balls[j].dx, self.balls[i].dx
                            self.balls[i].dy, self.balls[j].dy = self.balls[j].dy, self.balls[i].dy
    
            self.update()
    
        def mouseReleaseEvent(self, event):
            ball = Ball(event.pos())
            self.balls.append(ball)
            self.update()
        
        def paintEvent(self, event):
            painter = QPainter(self)
            painter.setRenderHint(QPainter.Antialiasing)
    
            for ball in self.balls:
                ball.draw(painter)
    
    
    if __name__ == '__main__':
        app = QApplication([])
    
        w = Window()
        w.show()
    
        app.exec()
    
    • 2

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5