首页 > 编程 > Python > 正文

python3+PyQt5 动画和复杂形状--实现千足蛇动画程序

2019-11-06 06:52:25
字体:
来源:转载
供稿:网友

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

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

#!/usr/bin/env python3import mathimport randomimport sysfrom 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 = 500INTERVAL = 1class 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) def boundingRect(self): return Head.Rect def shape(self): path = QPainterPath() path.addEllipse(Head.Rect) return path def paint(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) def advance(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 def boundingRect(self): return self.path.boundingRect() def shape(self): return self.path def paint(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) def advance(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") def zoom(self, value): factor = value / 100.0 matrix=self.view.transform() matrix.reset() matrix.scale(factor, factor) self.view.setTransform(matrix) def pauSEOrResume(self): self.running = not self.running self.pauseButton.setText("Pa&use" if self.running else "Res&ume") def populate(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 def timerEvent(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 python3import mathimport randomimport sysfrom 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 = 500INTERVAL = 1Running = Falseclass 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) def boundingRect(self): return Head.Rect def shape(self): path = QPainterPath() path.addEllipse(Head.Rect) return path def paint(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) def timeout(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) def boundingRect(self): return self.path.boundingRect() def shape(self): return self.path def paint(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) def timeout(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") def zoom(self, value): factor = value / 100.0 matrix=self.view.transform() matrix.reset() matrix.scale(factor, factor) self.view.setTransform(matrix) def pauseOrResume(self): global Running Running = not Running self.pauseButton.setText("Pa&use" if Running else "Res&ume") def populate(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 def timerEvent(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 itemapp = 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_()

运行结果 这里写图片描述


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表