Как анимировать отрисованные в QPainter объекты?

Рейтинг: 1Ответов: 1Опубликовано: 28.03.2023

Не могу разобраться, как добавить анимацию к уже отрисованным объектам в QPainter.

Есть одно условие, новые объекты могут добавляться в любое время и их перемещения тоже должны быть анимированы.

Пробовал реализовать этот пример, но он не подходит для динамического изменения количества объектов.

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtCore import pyqtSignal, QRect, QPoint, Qt, QThread
from PyQt5 import QtGui

import time
import random
import queue

class MyThread(QThread):
    task_update_coord = pyqtSignal(QPoint, int, name = "updateCoordinate")
    def __init__(self, queue, parent=None):
        super().__init__(parent)
        self.x = 0
        self.y = 0
        self.queue = queue
    
    
    def run(self):
        while True:
            self.x = random.randint(20, 200)
            self.y = random.randint(20, 200)
            self.id = random.randint(1, 2)
            self.task_update_coord.emit(QPoint(self.x, self.y), self.id)
            self.queue.put((self.x, self.y, self.id))
            time.sleep(1)
            print((self.x, self.y, self.id))
            

class Dialog(QWidget):
    def __init__(self):
        super().__init__()
        self.queue = queue.Queue()
        thread = MyThread(self.queue, parent=self)

        self.setFixedSize(600, 600)
        
        self.rect = QRect(0, 0, 10, 10)
        self.rect2 = QRect(0, 0, 10, 10)
        self.point = QPoint(10, 20)
        self.child = QWidget()
        thread.task_update_coord.connect(self.changing_coords, Qt.ConnectionType.QueuedConnection)
        thread.start()

    def changing_coords(self):
        msg = self.queue.get()
        print(msg)
        x = msg[0]
        y = msg[1]
        id = msg[2]
        self.rect = QRect(x, y, 30, 30)
        self.rect2 = QRect(x+30, y+30, 30, 30)
        self.update()
        
         
    def paintEvent(self, event):
        painter = QtGui.QPainter(self)

        painter.drawEllipse(self.rect)
        painter.drawText(self.rect, Qt.TextFlag.TextDontClip|Qt.AlignmentFlag.AlignCenter, str(1))
        
        painter.drawEllipse(self.rect2)
        painter.drawText(self.rect2, Qt.TextFlag.TextDontClip|Qt.AlignmentFlag.AlignCenter, str(2))
        

if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = Dialog()
    w.show()
    sys.exit(app.exec_())

Ответы

▲ 1Принят

Как вариант:

from random import randint
from typing import List
from PyQt5.Qt import *


class Ball:
    def __init__(self, x, y, r, v_x, v_y, color):
        self.x = x
        self.y = y
        self.r = r
        self.v_x = v_x
        self.v_y = v_y
        self.color = color

    def update(self):
        self.x += self.v_x
        self.y += self.v_y

    def draw(self, painter: QPainter):
        r, g, b = self.color
        color = QColor(r, g, b)
        painter.save()
        painter.setPen(Qt.black)
        painter.setBrush(color)
        painter.drawEllipse(self.x, self.y, self.r, self.r)
        painter.restore()

    @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


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

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        self._old_pos = None
        self.frame_color = Qt.darkCyan

        self.balls: List[Ball] = []

        timeout = 1000 // 60
        self.timer = QTimer()
        self.timer.timeout.connect(self.tick)
        self.timer.start(timeout)

        layout = QHBoxLayout(self)
        layout.addStretch()
        layout.addWidget(QPushButton(
            "Добавить пару шариков", 
            clicked=self._add), alignment=Qt.AlignRight | Qt.AlignBottom)
        layout.addWidget(QPushButton(
            "Закрыть окно", 
            clicked=self.close), alignment=Qt.AlignRight | Qt.AlignBottom)

    def _add(self):        
        for i in range(2):
            self.append_random_ball()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._old_pos = event.pos()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._old_pos = None

    def mouseMoveEvent(self, event):
        if not self._old_pos:
            return
        delta = event.pos() - self._old_pos
        self.move(self.pos() + delta)

    def tick(self):
        for ball in self.balls:
            ball.update()
            # Условия отскакивания шарика от левого и правого края
            if ball.left <= 0 or ball.right >= self.width():
                ball.v_x = -ball.v_x
            # Условия отскакивания шарика верхнего и нижнего края
            if ball.top <= 0 or ball.bottom >= self.height():
                ball.v_y = -ball.v_y
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setBrush(QColor(0, 0, 0, 1))
        painter.setPen(QPen(self.frame_color, 10))
        painter.drawRect(self.rect())
        for ball in self.balls:
            ball.draw(painter)

    def append_random_ball(self):
        def get_random_vector():
            pos = 0, 0
            # Если pos равен (0, 0), пересчитываем значения, т.к. шарик должен двигаться
            while pos == (0, 0):
                pos = randint(-3, 3), randint(-3, 3)
            return pos

        def get_random_color():
            return randint(0, 255), randint(0, 255), randint(0, 255)

        x = self.width() // 2 + randint(-self.width() // 4, self.width() // 4)
        y = self.height() // 2 + randint(-self.height() // 4, self.height() // 4)
        
        r = randint(22, 33)
        v_x, v_y = get_random_vector()
        color = get_random_color()

        ball = Ball(x, y, r, v_x, v_y, color)
        self.balls.append(ball)


if __name__ == '__main__':
    import sys
    
    app = QApplication(sys.argv)
    WIDTH = 600    
    HEIGHT = 600   
    BALL_NUMBER = 10 

    w = Widget()
    w.resize(WIDTH, HEIGHT)

    for i in range(BALL_NUMBER):
        w.append_random_ball()

    w.show()
    sys.exit(app.exec_())

введите сюда описание изображения