蛇年到,贪吃蛇还是要出马下的,不准备写完整的程序,就让蛇跑起来,尾巴的长起来吧,蛇头有点动画得了。
先讲讲一些原理,蛇的脑袋使用键盘控制,因此重写他的keyPressEvent
是势在必行的;
蛇身能够增长,运动,原来我计划是蛇身的每一块的坐标都会移动,可看见一个老哥说每次只要把尾巴移动的蛇脑袋那里,蛇脑袋再往前跑跑,蛇就动了,想想也是。蛇身是一块块组成的,很对的块形成一个组,变成蛇的身子,每次蛇要长长,只要在这个组里增加新的块即可。
因为使用了大量的图形,因此使用PyQt里面的带Graphics的那一堆类,重写QGraphicsItem
的类或者子类,完成特定的功能,作为基本的元素;使用QGraphicsScene
增加元素;QGraphicsView
显示。
想使用动画Animation,看了看里面需要QObject
来初始化,而QGraphicsItem
居然不是继承自QObject
的。因此我选择了QGraphicsObject
作为基类来完善需求,他继承自QGraphicsItem
和QObject
,可以使用动画的东西。
下面是完整的代码,先贴上:
import sys
import random
from Queue import deque
from PyQt4.QtCore import (QObject,QRectF,QRect,QPointF,
QTimer,SIGNAL,Qt,pyqtProperty,
QPropertyAnimation,QEasingCurve )
from PyQt4.QtGui import (QMainWindow,QGraphicsScene,QGraphicsView,
QGraphicsPixmapItem,QGraphicsItem,
QFont,QPainter,QPixmap,
QPainter,QGraphicsItemGroup,
QApplication,QGraphicsObject)
import qrc_resources
class Segment(QGraphicsObject):
width = 50
def __init__(self,pos,parent=None):
super(Segment,self).__init__(parent)
self.setPixmap(QPixmap(":/img/book.jpg"))
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable)
self.setPos(pos)
def pixmap(self):
return self.pix
def setPixmap(self,pix):
self.pix = pix
def paint(self,painter,option,widget):
painter.drawPixmap(QRect(0,0,self.width,self.width),
self.pixmap())
def boundingRect (self):
return QRectF(0,0,self.width,self.width)
class Head(Segment):
speed = Segment.width
UP = 0
DOWN = 1
LEFT = 2
RIGHT = 3
TimeGap = 500
CURVE_TYPES = [(n, c) for n, c in QEasingCurve.__dict__.items()
if isinstance(c, QEasingCurve.Type) and c != QEasingCurve.Custom]
def _set_pos(self,pos):
self.setPos(pos)
def _get_pos(self):
return self.pos()
position = pyqtProperty(QPointF,fset = _set_pos,
fget = _get_pos)
def __init__(self,pos,parent=None):
super(Head,self).__init__(pos,parent)
self.setPixmap(QPixmap(":/img/person.jpg"))
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable|
QGraphicsItem.ItemIsFocusable)
self.curveType = QEasingCurve.OutBounce
self.setPos(pos)
self.setFocus(Qt.OtherFocusReason)
self.direction = self.RIGHT
self.timer = QTimer()
self.timer.start(self.TimeGap)
QObject.connect(self.timer, SIGNAL("timeout()"),
self.move)
def move(self):
pos = self.pos()
# add animation,i dont know why must do this
# else it wont be move
if self.direction == self.LEFT:
position = pos + QPointF(-self.speed,0)
elif self.direction == self.RIGHT:
position = pos + QPointF(self.speed,0)
elif self.direction == self.UP:
position = pos + QPointF(0,-self.speed)
elif self.direction == self.DOWN:
position = pos + QPointF(0,self.speed)
pre = pos = position
rect = QRectF(self.boundingRect())
if self.direction == self.LEFT:
self.setPos(pos+QPointF(-self.speed,0))
elif self.direction == self.RIGHT:
self.setPos(pos+QPointF(self.speed,0))
elif self.direction == self.UP:
self.setPos(pos+QPointF(0,-self.speed))
elif self.direction == self.DOWN:
self.setPos(pos+QPointF(0,self.speed))
current = self.pos()
self.animationChanged(pre, current)
def animationChanged(self,pre,current):
self.anim= QPropertyAnimation(self,'position')
self.anim.setStartValue(pre)
self.anim.setEndValue(current)
self.anim.setEasingCurve(self.curveType)
self.anim.setDirection(self.TimeGap-200)
self.anim.start()
def keyPressEvent (self,event):
key = event.key()
if key == Qt.Key_Left:
self.direction = self.LEFT
return
if key == Qt.Key_Right:
self.direction = self.RIGHT
return
if key == Qt.Key_Up:
self.direction = self.UP
return
if key == Qt.Key_Down:
self.direction = self.DOWN
return
class SegmentGroup(QObject):
def __init__(self,scene,parent = None):
super(SegmentGroup,self).__init__(parent)
self.scene = scene
self.items = deque()
def addSegment(self,segment):
self.scene.addItem(segment)
self.items.append(segment)
def move(self,headPos):
if not self.items:return
tail = self.items.pop()
tail.setPos(headPos)
self.items.appendleft(tail)
def tail(self):
if not self.items:return
return self.items[-1]
class SnackScene(QGraphicsScene):
def __init__(self,parent=None):
super(SnackScene,self).__init__(parent)
self.setSceneRect(0,0,400,300)
self.segmentGroup = SegmentGroup(self)
self.segmentGroup.addSegment(Segment(QPointF(0,0)))
self.head = Head(QPointF(Segment.width,0))
self.addItem(self.head)
self.setFocusItem(self.head)
self.timer = QTimer()
self.timer.start(Head.TimeGap)
self.connect(self.timer, SIGNAL("timeout()"),
self.segmentMove)
def segmentMove(self):
self.segmentGroup.move(self.head.pos())
def mousePressEvent (self, event):
if event.button() == Qt.LeftButton:
tail = self.segmentGroup.tail()
self.segmentGroup.addSegment(
Segment(tail.pos())
)
elif event.button() == Qt.RightButton:
curveType = random.choice(Head.CURVE_TYPES)
self.head.curveType = QEasingCurve(curveType[1])
class Form(QMainWindow):
def __init__(self,parent=None):
super(Form,self).__init__(parent)
self.fileMenu = self.menuBar().addMenu("&File")
self.statusBar().showMessage("Ready", 5000)
self.scene = SnackScene()
self.view = QGraphicsView(self.scene)
self.view.setRenderHint(QPainter.SmoothPixmapTransform)
self.view.setDragMode(QGraphicsView.RubberBandDrag)
self.setCentralWidget(self.view)
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
资源文件:
<RCC>
<qresource prefix="/">
<file>img/book.jpg</file>
<file>img/person.jpg</file>
<file>img/cat.jpg</file>
</qresource>
</RCC>
##开始讲解:
###蛇的身体:
class Segment(QGraphicsObject):
width = 50
def __init__(self,pos,parent=None):
super(Segment,self).__init__(parent)
self.setPixmap(QPixmap(":/img/book.jpg"))
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable)
self.setPos(pos)
def pixmap(self):
return self.pix
def setPixmap(self,pix):
self.pix = pix
def paint(self,painter,option,widget):
painter.drawPixmap(QRect(0,0,self.width,self.width),
self.pixmap())
def boundingRect (self):
return QRectF(0,0,self.width,self.width)
boundingRect
是指明这个对象所占的矩形空间大小,因为有的时候你可以用鼠标选择图片,那么到底哪个区域呢,就是这个区域。
为了让蛇身蛇头的大小一致,设置了一个width作为蛇身的静态成员变量,并且在paint
方法中重绘了图片,这样,不管图片多么大,画出来都会变成指定的大小,保证了蛇身蛇头相同的大小。
###蛇头
class Head(Segment):
speed = Segment.width
UP = 0
DOWN = 1
LEFT = 2
RIGHT = 3
TimeGap = 500
CURVE_TYPES = [(n, c) for n, c in QEasingCurve.__dict__.items()
if isinstance(c, QEasingCurve.Type) and c != QEasingCurve.Custom]
def _set_pos(self,pos):
self.setPos(pos)
def _get_pos(self):
return self.pos()
position = pyqtProperty(QPointF,fset = _set_pos,
fget = _get_pos)
def __init__(self,pos,parent=None):
super(Head,self).__init__(pos,parent)
self.setPixmap(QPixmap(":/img/person.jpg"))
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable|
QGraphicsItem.ItemIsFocusable)
self.curveType = QEasingCurve.OutBounce
self.setPos(pos)
self.setFocus(Qt.OtherFocusReason)
self.direction = self.RIGHT
self.timer = QTimer()
self.timer.start(self.TimeGap)
QObject.connect(self.timer, SIGNAL("timeout()"),
self.move)
def move(self):
pos = self.pos()
# add animation,i dont know why must do this
# else it wont be move
if self.direction == self.LEFT:
position = pos + QPointF(-self.speed,0)
elif self.direction == self.RIGHT:
position = pos + QPointF(self.speed,0)
elif self.direction == self.UP:
position = pos + QPointF(0,-self.speed)
elif self.direction == self.DOWN:
position = pos + QPointF(0,self.speed)
pre = pos = position
if self.direction == self.LEFT:
self.setPos(pos+QPointF(-self.speed,0))
elif self.direction == self.RIGHT:
self.setPos(pos+QPointF(self.speed,0))
elif self.direction == self.UP:
self.setPos(pos+QPointF(0,-self.speed))
elif self.direction == self.DOWN:
self.setPos(pos+QPointF(0,self.speed))
current = self.pos()
self.animationChanged(pre, current)
def animationChanged(self,pre,current):
self.anim= QPropertyAnimation(self,'position')
self.anim.setStartValue(pre)
self.anim.setEndValue(current)
self.anim.setEasingCurve(self.curveType)
self.anim.setDirection(self.TimeGap-200)
self.anim.start()
def keyPressEvent (self,event):
key = event.key()
if key == Qt.Key_Left:
self.direction = self.LEFT
return
if key == Qt.Key_Right:
self.direction = self.RIGHT
return
if key == Qt.Key_Up:
self.direction = self.UP
return
if key == Qt.Key_Down:
self.direction = self.DOWN
return
设置了四个方向,并且重写了keyPressEvent
方法,键盘按下的时候,改变self.direction
的方向。
CURVE_TYPES是动画的所有可能取值,有40多种,可查阅QEasingCurve的文档。
蛇头的运动,是在move
方法中实现的,如果不添加动画的话,第一段if else语句并不需要加,程序运行正常,可添加动画后,不知道为什么,每次运行到这里,pos的值与上一次运行move的值相同,不得已加了一段if else使得程序能正常运行。
animationChanged
方法实现了蛇头的动画,self.anim.setEasingCurve(self.curveType)
设置动画效果,为了让动画效果可以转变,因此保留了一个self.curveType
变量。另外在构造时self.anim= QPropertyAnimation(self,'position')
,第一个参数需要是QObject
对象,而第二个参数需要时Qt的属性,因此在上面我生命了一个position
属性,代码如下:
def _set_pos(self,pos):
self.setPos(pos)
def _get_pos(self):
return self.pos()
position = pyqtProperty(QPointF,fset = _set_pos,fget = _get_pos)
构造方法的定时器让蛇身连续移动,这个在我之前的博客中有介绍,不在啰嗦。
###蛇身Group class SegmentGroup(QObject): def init(self,scene,parent = None): super(SegmentGroup,self).init(parent) self.scene = scene self.items = deque()
def addSegment(self,segment):
self.scene.addItem(segment)
self.items.append(segment)
def move(self,headPos):
if not self.items:return
tail = self.items.pop()
tail.setPos(headPos)
self.items.appendleft(tail)
def tail(self):
if not self.items:return
return self.items[-1]
这是为了方便管理,将蛇头和整段身体分离而做的一个容器,因为主要实在第一节蛇身和蛇尾之间进行插入删除操作,所以使用python的deque()来实现这个容器,并且这个容器直接将蛇身添加到scene中去。代码木有神马特别需要说明的。
Scene
class SnackScene(QGraphicsScene):
def __init__(self,parent=None):
super(SnackScene,self).__init__(parent)
self.setSceneRect(0,0,400,300)
self.segmentGroup = SegmentGroup(self)
self.segmentGroup.addSegment(Segment(QPointF(0,0)))
self.head = Head(QPointF(Segment.width,0))
self.addItem(self.head)
self.setFocusItem(self.head)
self.timer = QTimer()
self.timer.start(Head.TimeGap)
self.connect(self.timer, SIGNAL("timeout()"),
self.segmentMove)
def segmentMove(self):
self.segmentGroup.move(self.head.pos())
def mousePressEvent (self, event):
if event.button() == Qt.LeftButton:
tail = self.segmentGroup.tail()
self.segmentGroup.addSegment(
Segment(tail.pos())
)
elif event.button() == Qt.RightButton:
curveType = random.choice(Head.CURVE_TYPES)
self.head.curveType = QEasingCurve(curveType[1])
在这个类中添加数据,蛇头,蛇身管理器,设置了简单的增长蛇身的事件——按下鼠标左键;而按下右键则是更改蛇头的动画效果。将蛇身的移动与蛇头的移动对应起来,用一个与蛇头定时器时间相同的定时器来实现。
最后的form则是使用mainwindow在实现的应用程序。
class Form(QMainWindow):
def __init__(self,parent=None):
super(Form,self).__init__(parent)
self.fileMenu = self.menuBar().addMenu("&File")
self.statusBar().showMessage("Ready", 5000)
self.scene = SnackScene()
self.view = QGraphicsView(self.scene)
self.view.setRenderHint(QPainter.SmoothPixmapTransform)
self.view.setDragMode(QGraphicsView.RubberBandDrag)
self.setCentralWidget(self.view)
**后记:**做这个依然是为了熟练qt,本来是想用图形界面做设计模式的,但是有些模式想不好写神马好,就像装饰者模式,用神马例子捏?