可以通过点击场地来创建球,但为了说明问题,我已经提前创建了 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()


我冒昧地对与您的问题相关的代码部分进行了现代化改造,因此我发布了整个代码,并重复并评论了我的更改
纠正你的代码并不难:只有当球接近时,你才需要改变球的速度。这也适用于球之间的碰撞以及球与墙壁的碰撞。
如果用户创建了重叠的球,则无需执行任何操作。过一会,他们就会自己从路口出来。
在下面的代码中,对墙壁的撞击和球之间的相互撞击是固定的: