【原创】python3+PyQt5 动画和复杂形状

本文通过Python3+PyQt5实现《pythonQt Gui快速编程》这本书的千足蛇动画程序,采用QGraphicsView,QGraphicsScene,QGraphicsItem,这个程序包含有多个文本,图片和框的页面。有些图形类在PyQt5已过时,所以本代码改动幅度比较大。本文实现复杂形状动画内容,一种通过项自身定时器,另一种采用窗口超时处理程序来实现项目移动碰撞等功能。

1,通过自身定时器实现,完整代码如下:

#!/usr/bin/env python3

import math
import random
import sys
from PyQt5.QtCore import (QTimer, QPointF, QRectF, Qt)
from PyQt5.QtWidgets import (QApplication, QDialog,
       QGraphicsItem, QGraphicsScene, QGraphicsView, QHBoxLayout,
         QPushButton, QSlider,QVBoxLayout)
from PyQt5.QtGui import (QBrush,QColor,QPainter,QPainterPath,QPolygonF)

SCENESIZE = 500
INTERVAL = 1

class Head(QGraphicsItem):

Rect =QRectF(-30, -20, 60, 40)

def__init__(self, color, angle, position):
       super(Head, self).__init__()
       self.color = color
       self.angle = angle
       self.setPos(position)

defboundingRect(self):
       return Head.Rect

defshape(self):
       path = QPainterPath()
       path.addEllipse(Head.Rect)
       return path

defpaint(self, painter, option, widget=None):
       painter.setPen(Qt.NoPen)
       painter.setBrush(QBrush(self.color))
       painter.drawEllipse(Head.Rect)
       if option.levelOfDetailFromTransform(self.transform()) > 0.5: #Outer eyes
           painter.setBrush(QBrush(Qt.yellow))
           painter.drawEllipse(-12, -19, 8, 8)
           painter.drawEllipse(-12, 11, 8, 8)
           if option.levelOfDetailFromTransform(self.transform())> 0.8: #Inner eyes
               painter.setBrush(QBrush(Qt.darkBlue))
               painter.drawEllipse(-12, -19, 4, 4)
               painter.drawEllipse(-12, 11, 4, 4)
               if option.levelOfDetailFromTransform(self.transform()) > 0.9: #Nostrils
                   painter.setBrush(QBrush(Qt.white))
                   painter.drawEllipse(-27, -5, 2, 2)
                   painter.drawEllipse(-27, 3, 2, 2)

defadvance(self, phase):
       if phase == 0:
           angle = self.angle
           while True:
               flipper = 1
               angle += random.random() * random.choice((-1, 1))
               offset = flipper * random.random()
               x = self.x() + (offset * math.sin(math.radians(angle)))
               y = self.y() + (offset * math.cos(math.radians(angle)))
               if 0 <= x <= SCENESIZE and 0 <= y <= SCENESIZE:
                   break
               else:
                   flipper = -1 if flipper == 1 else 1
           self.angle = angle
           self.position = QPointF(x, y)
       else:
           self.setRotation(random.random() * random.choice((-1, 1)))
           self.setPos(self.position)
           if self.scene():
               for item in self.scene().collidingItems(self):
                   if isinstance(item, Head):
                       self.color.setRed(min(255, self.color.red() + 1))
                   else:
                       item.color.setBlue(min(255, item.color.blue() + 1))

class Segment(QGraphicsItem):

def__init__(self, color, offset, parent):
       super(Segment, self).__init__(parent)
       self.color = color
       self.rect = QRectF(offset, -20, 30, 40)
       self.path = QPainterPath()
       self.path.addEllipse(self.rect)
       x = offset + 15
       y = -20
       self.path.addPolygon(QPolygonF([QPointF(x, y),
               QPointF(x - 5, y - 12), QPointF(x - 5, y)]))
       self.path.closeSubpath()
       y = 20
       self.path.addPolygon(QPolygonF([QPointF(x, y),
               QPointF(x - 5, y + 12), QPointF(x - 5, y)]))
       self.path.closeSubpath()
       self.change = 1
       self.angle = 0

defboundingRect(self):
       return self.path.boundingRect()

defshape(self):
       return self.path

defpaint(self, painter, option, widget=None):
       painter.setPen(Qt.NoPen)
       painter.setBrush(QBrush(self.color))
       if option.levelOfDetailFromTransform(self.transform()) <0.9:
           painter.drawEllipse(self.rect)
       else:
           painter.drawPath(self.path)

defadvance(self, phase):
       if phase == 0:
           matrix = self.transform()
           matrix.reset()
           self.setTransform(matrix)
           self.angle += self.change * random.random()
           if self.angle > 4.5:
               self.change = -1
               self.angle -= 0.00001
           elif self.angle < -4.5:
               self.change = 1
               self.angle += 0.00001
       elif phase == 1:
           self.setRotation(self.angle)

class MainForm(QDialog):

def__init__(self, parent=None):
       super(MainForm, self).__init__(parent)

self.running = False
       self.scene = QGraphicsScene(self)
       self.scene.setSceneRect(0, 0, SCENESIZE, SCENESIZE)
       self.scene.setItemIndexMethod(QGraphicsScene.NoIndex)
       self.view = QGraphicsView()
       self.view.setRenderHint(QPainter.Antialiasing)
       self.view.setScene(self.scene)
       self.view.setFocusPolicy(Qt.NoFocus)
       zoomSlider = QSlider(Qt.Horizontal)
       zoomSlider.setRange(5, 200)
       zoomSlider.setValue(100)
       self.pauseButton = QPushButton("Pa&use")
       quitButton = QPushButton("&Quit")
       quitButton.setFocusPolicy(Qt.NoFocus)

layout = QVBoxLayout()
       layout.addWidget(self.view)
       bottomLayout = QHBoxLayout()
       bottomLayout.addWidget(self.pauseButton)
       bottomLayout.addWidget(zoomSlider)
       bottomLayout.addWidget(quitButton)
       layout.addLayout(bottomLayout)
       self.setLayout(layout)

zoomSlider.valueChanged[int].connect(self.zoom)
       quitButton.clicked.connect(self.accept)

self.populate()
       self.startTimer(INTERVAL)
       self.setWindowTitle("Multipedes")

defzoom(self, value):
       factor = value / 100.0
       matrix=self.view.transform() 
       matrix.reset()
       matrix.scale(factor, factor)
       self.view.setTransform(matrix)

defpauseOrResume(self):
       self.running = not self.running
       self.pauseButton.setText("Pa&use" if self.running else"Res&ume")

defpopulate(self):
       red, green, blue = 0, 150, 0
       for i in range(random.randint(6, 10)):
           angle = random.randint(0, 360)
           offset = random.randint(0, SCENESIZE // 2)
           half = SCENESIZE / 2
           x = half + (offset * math.sin(math.radians(angle)))
           y = half + (offset * math.cos(math.radians(angle)))
           color = QColor(red, green, blue)
           head = Head(color, angle, QPointF(x, y))
           color = QColor(red, green + random.randint(10, 60), blue)
           offset = 25
           segment = Segment(color, offset, head)
           for j in range(random.randint(3, 7)):
               offset += 25
               segment = Segment(color, offset, segment)
           head.setRotation(random.randint(0, 360))
           self.scene.addItem(head)
       self.running = True

deftimerEvent(self, event):
       if not self.running:
           return
       dead = set()
       items = self.scene.items()
       if len(items) == 0:
           self.populate()
           return
       heads = set()
       for item in items:
           if isinstance(item, Head):
               heads.add(item)
               if item.color.red() == 255 and random.random() > 0.75:
                   dead.add(item)
       if len(heads) == 1:
           dead = heads
       del heads
       while dead:
           item = dead.pop()
           self.scene.removeItem(item)
           del item
       self.scene.advance()

app = QApplication(sys.argv)
form = MainForm()
rect = QApplication.desktop().availableGeometry()
form.resize(int(rect.width() * 0.75), int(rect.height() *0.9))
form.show()
app.exec_()

2,通过窗口超时处理程序实现,完整代码如下:

#!/usr/bin/env python3

import math
import random
import sys
from PyQt5.QtCore import (QTimer, QPointF, QRectF, Qt)
from PyQt5.QtWidgets import (QApplication, QDialog,
                            QGraphicsItem, QGraphicsScene, QGraphicsView, QHBoxLayout,
                            QPushButton, QSlider,QVBoxLayout)
from PyQt5.QtGui import (QBrush,QColor,QPainter,QPainterPath,QPolygonF)

SCENESIZE = 500
INTERVAL = 1

Running = False

class Head(QGraphicsItem):

Rect =QRectF(-30, -20, 60, 40)

def__init__(self, color, angle, position):
       super(Head, self).__init__()
       self.color = color
       self.angle = angle
       self.setPos(position)
       self.timer = QTimer()
       self.timer.timeout.connect(self.timeout)
       
       self.timer.start(INTERVAL)

defboundingRect(self):
       return Head.Rect

defshape(self):
       path = QPainterPath()
       path.addEllipse(Head.Rect)
       return path

defpaint(self, painter, option, widget=None):
       painter.setPen(Qt.NoPen)
       painter.setBrush(QBrush(self.color))
       painter.drawEllipse(Head.Rect)
       ifoption.levelOfDetailFromTransform(self.transform()) > 0.5: # Outer eyes
           painter.setBrush(QBrush(Qt.yellow))
           painter.drawEllipse(-12, -19, 8, 8)
           painter.drawEllipse(-12, 11, 8, 8)
           ifoption.levelOfDetailFromTransform(self.transform()) > 0.8: # Inner eyes
               painter.setBrush(QBrush(Qt.darkBlue))
               painter.drawEllipse(-12, -19, 4, 4)
               painter.drawEllipse(-12, 11, 4, 4)
               ifoption.levelOfDetailFromTransform(self.transform()) > 0.9: # Nostrils
                   painter.setBrush(QBrush(Qt.white))
                   painter.drawEllipse(-27, -5, 2, 2)
                   painter.drawEllipse(-27, 3, 2, 2)

deftimeout(self):
       if not Running:
           return
       angle = self.angle
       while True:
           flipper = 1
           angle += random.random() * random.choice((-1, 1))
           offset = flipper * random.random()
           x = self.x() + (offset * math.sin(math.radians(angle)))
           y = self.y() + (offset * math.cos(math.radians(angle)))
           if 0 <= x <= SCENESIZE and 0 <= y <= SCENESIZE:
               break
           else:
               flipper = -1 if flipper == 1 else 1
       self.angle = angle
       self.setRotation(random.random() * random.choice((-1, 1)))
       self.setPos(QPointF(x, y))
       if self.scene():
           for item in self.scene().collidingItems(self):
               if isinstance(item, Head):
                   self.color.setRed(min(255, self.color.red() + 1))
               else:
                   item.color.setBlue(min(255, item.color.blue() + 1))

class Segment(QGraphicsItem):

def__init__(self, color, offset, parent):
       super(Segment, self).__init__(parent)
       self.color = color
       self.rect = QRectF(offset, -20, 30, 40)
       self.path = QPainterPath()
       self.path.addEllipse(self.rect)
       x = offset + 15
       y = -20
       self.path.addPolygon(QPolygonF([QPointF(x, y),
                                       QPointF(x - 5, y - 12), QPointF(x - 5, y)]))
       self.path.closeSubpath()
       y = 20
       self.path.addPolygon(QPolygonF([QPointF(x, y),
                                       QPointF(x - 5, y + 12), QPointF(x - 5, y)]))
       self.path.closeSubpath()
       self.change = 1
       self.angle = 0
       self.timer = QTimer()
       self.timer.timeout.connect(self.timeout)
       self.timer.start(INTERVAL)

defboundingRect(self):
       return self.path.boundingRect()

defshape(self):
       return self.path

defpaint(self, painter, option, widget=None):
       painter.setPen(Qt.NoPen)
       painter.setBrush(QBrush(self.color))
       ifoption.levelOfDetailFromTransform(self.transform()) < 0.9:
           painter.drawEllipse(self.rect)
       else:
           painter.drawPath(self.path)

deftimeout(self):
       if not Running:
           return
       matrix = self.transform()
       matrix.reset()
       self.setTransform(matrix)
       self.angle += self.change * random.random()
       if self.angle > 4.5:
           self.change = -1
           self.angle -= 0.00001
       elif self.angle < -4.5:
           self.change = 1
           self.angle += 0.00001
       self.setRotation(self.angle)

class MainForm(QDialog):

def__init__(self, parent=None):
       super(MainForm, self).__init__(parent)

self.scene = QGraphicsScene(self)
       self.scene.setSceneRect(0, 0, SCENESIZE, SCENESIZE)
       self.scene.setItemIndexMethod(QGraphicsScene.NoIndex)
       self.view = QGraphicsView()
       self.view.setRenderHint(QPainter.Antialiasing)
       self.view.setScene(self.scene)
       self.view.setFocusPolicy(Qt.NoFocus)
       zoomSlider = QSlider(Qt.Horizontal)
       zoomSlider.setRange(5, 200)
       zoomSlider.setValue(100)
       self.pauseButton = QPushButton("Pa&use")
       quitButton = QPushButton("&Quit")
       quitButton.setFocusPolicy(Qt.NoFocus)

layout = QVBoxLayout()
       layout.addWidget(self.view)
       bottomLayout = QHBoxLayout()
       bottomLayout.addWidget(self.pauseButton)
       bottomLayout.addWidget(zoomSlider)
       bottomLayout.addWidget(quitButton)
       layout.addLayout(bottomLayout)
       self.setLayout(layout)

zoomSlider.valueChanged[int].connect(self.zoom)
       self.pauseButton.clicked.connect(self.pauseOrResume)
       quitButton.clicked.connect(self.accept)

self.populate()
       self.startTimer(INTERVAL)
       self.setWindowTitle("Multipedes")

defzoom(self, value):
       factor = value / 100.0
       matrix=self.view.transform() 
       matrix.reset()
       matrix.scale(factor, factor)
       self.view.setTransform(matrix)

defpauseOrResume(self):
       global Running
       Running = not Running
       self.pauseButton.setText("Pa&use" if Running else"Res&ume")

defpopulate(self):
       red, green, blue = 0, 150, 0
       for i in range(random.randint(6, 10)):
           angle = random.randint(0, 360)
           offset = random.randint(0, SCENESIZE // 2)
           half = SCENESIZE / 2
           x = half + (offset * math.sin(math.radians(angle)))
           y = half + (offset * math.cos(math.radians(angle)))
           color = QColor(red, green, blue)
           head = Head(color, angle, QPointF(x, y))
           color = QColor(red, green + random.randint(10, 60), blue)
           offset = 25
           segment = Segment(color, offset, head)
           for j in range(random.randint(3, 7)):
               offset += 25
               segment = Segment(color, offset, segment)
           head.setRotation(random.randint(0, 360))
           self.scene.addItem(head)
       global Running
       Running = True

deftimerEvent(self, event):
       if not Running:
           return
       dead = set()
       items = self.scene.items()
       if len(items) == 0:
           self.populate()
           return
       heads = set()
       for item in items:
           if isinstance(item, Head):
               heads.add(item)
               if item.color.red() == 255 and random.random() > 0.75:
                   dead.add(item)
       if len(heads) == 1:
           dead = heads
       del heads
       while dead:
           item = dead.pop()
           self.scene.removeItem(item)
           del item

app = QApplication(sys.argv)
form = MainForm()
rect = QApplication.desktop().availableGeometry()
form.resize(int(rect.width() * 0.75), int(rect.height() *0.9))
form.show()
app.exec_()

运行结果:
(0)

相关推荐