蛇年到,贪吃蛇还是要出马下的,不准备写完整的程序,就让蛇跑起来,尾巴的长起来吧,蛇头有点动画得了。
先讲讲一些原理,蛇的脑袋使用键盘控制,因此重写他的keyPressEvent
是势在必行的;
蛇身能够增长,运动,原来我计划是蛇身的每一块的坐标都会移动,可看见一个老哥说每次只要把尾巴移动的蛇脑袋那里,蛇脑袋再往前跑跑,蛇就动了,想想也是。蛇身是一块块组成的,很对的块形成一个组,变成蛇的身子,每次蛇要长长,只要在这个组里增加新的块即可。
因为使用了大量的图形,因此使用PyQt里面的带Graphics的那一堆类,重写QGraphicsItem
的类或者子类,完成特定的功能,作为基本的元素;使用QGraphicsScene
增加元素;QGraphicsView
显示。
想使用动画Animation,看了看里面需要QObject
来初始化,而QGraphicsItem
居然不是继承自QObject
的。因此我选择了QGraphicsObject
作为基类来完善需求,他继承自QGraphicsItem
和QObject
,可以使用动画的东西。
下面是完整的代码,先贴上:
资源文件:
1 class Segment(QGraphicsObject): 2 width = 50 3 def __init__(self,pos,parent=None): 4 super(Segment,self).__init__(parent) 5 self.setPixmap(QPixmap(":/img/book.jpg")) 6 self.setFlags(QGraphicsItem.ItemIsSelectable| 7 QGraphicsItem.ItemIsMovable) 8 self.setPos(pos) 9 # def setPos(self,pos):10 # super(Segment,self).setPos(pos)11 def pixmap(self):12 return self.pix13 def setPixmap(self,pix):14 self.pix = pix15 def paint(self,painter,option,widget):16 painter.drawPixmap(QRect(0,0,self.width,self.width),17 self.pixmap()) 18 def boundingRect (self):19 return QRectF(0,0,self.width,self.width)
boundingRect
是指明这个对象所占的矩形空间大小,因为有的时候你可以用鼠标选择图片,那么到底哪个区域呢,就是这个区域。
为了让蛇身蛇头的大小一致,设置了一个width作为蛇身的静态成员变量,并且在paint
方法中重绘了图片,这样,不管图片多么大,画出来都会变成指定的大小,保证了蛇身蛇头相同的大小。
1 class Head(Segment): 2 speed = Segment.width 3 UP = 0 4 DOWN = 1 5 LEFT = 2 6 RIGHT = 3 7 TimeGap = 500 8 CURVE_TYPES = [(n, c) for n, c in QEasingCurve.__dict__.items() 9 if isinstance(c, QEasingCurve.Type) and c != QEasingCurve.Custom]10 def _set_pos(self,pos):11 self.setPos(pos)12 def _get_pos(self):13 return self.pos()14 15 position = pyqtProperty(QPointF,fset = _set_pos,16 fget = _get_pos)17 def __init__(self,pos,parent=None):18 super(Head,self).__init__(pos,parent)19 self.setPixmap(QPixmap(":/img/person.jpg"))20 self.setFlags(QGraphicsItem.ItemIsSelectable|21 QGraphicsItem.ItemIsMovable|22 QGraphicsItem.ItemIsFocusable)23 self.curveType = QEasingCurve.OutBounce24 self.setPos(pos)25 self.setFocus(Qt.OtherFocusReason)26 self.direction = self.RIGHT27 self.timer = QTimer()28 self.timer.start(self.TimeGap)29 QObject.connect(self.timer, SIGNAL("timeout()"),30 self.move)31 def move(self):32 pos = self.pos()33 # add animation,i dont know why must do this34 # else it wont be move35 if self.direction == self.LEFT:36 position = pos + QPointF(-self.speed,0)37 elif self.direction == self.RIGHT:38 position = pos + QPointF(self.speed,0)39 elif self.direction == self.UP:40 position = pos + QPointF(0,-self.speed)41 elif self.direction == self.DOWN:42 position = pos + QPointF(0,self.speed)43 44 pre = pos = position45 46 rect = QRectF(self.boundingRect())47 if self.direction == self.LEFT:48 self.setPos(pos+QPointF(-self.speed,0))49 elif self.direction == self.RIGHT:50 self.setPos(pos+QPointF(self.speed,0))51 elif self.direction == self.UP:52 self.setPos(pos+QPointF(0,-self.speed))53 elif self.direction == self.DOWN:54 self.setPos(pos+QPointF(0,self.speed))55 current = self.pos()56 self.animationChanged(pre, current)57 58 def animationChanged(self,pre,current): 59 self.anim= QPropertyAnimation(self,'position')60 self.anim.setStartValue(pre)61 self.anim.setEndValue(current)62 63 self.anim.setEasingCurve(self.curveType)64 self.anim.setDirection(self.TimeGap-200)65 self.anim.start()66 def keyPressEvent (self,event):67 key = event.key()68 if key == Qt.Key_Left:69 self.direction = self.LEFT70 return71 if key == Qt.Key_Right:72 self.direction = self.RIGHT73 return74 if key == Qt.Key_Up:75 self.direction = self.UP76 return77 if key == Qt.Key_Down:78 self.direction = self.DOWN79 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
属性,代码如下:
1 def _set_pos(self,pos):2 self.setPos(pos)3 def _get_pos(self):4 return self.pos()5 6 position = pyqtProperty(QPointF,fset = _set_pos,7 fget = _get_pos)
构造方法的定时器让蛇身连续移动,这个在我之前的博客中有介绍,不在啰嗦。
1 class SegmentGroup(QObject): 2 def __init__(self,scene,parent = None): 3 super(SegmentGroup,self).__init__(parent) 4 self.scene = scene 5 self.items = deque() 6 7 def addSegment(self,segment): 8 self.scene.addItem(segment) 9 self.items.append(segment)10 11 def move(self,headPos):12 if not self.items:return 13 tail = self.items.pop()14 tail.setPos(headPos)15 self.items.appendleft(tail)16 17 def tail(self):18 if not self.items:return19 return self.items[-1]
这是为了方便管理,将蛇头和整段身体分离而做的一个容器,因为主要实在第一节蛇身和蛇尾之间进行插入删除操作,所以使用python的deque()来实现这个容器,并且这个容器直接将蛇身添加到scene中去。代码木有神马特别需要说明的。
1 class SnackScene(QGraphicsScene): 2 def __init__(self,parent=None): 3 super(SnackScene,self).__init__(parent) 4 self.setSceneRect(0,0,400,300) 5 self.segmentGroup = SegmentGroup(self) 6 self.segmentGroup.addSegment(Segment(QPointF(0,0))) 7 8 self.head = Head(QPointF(Segment.width,0)) 9 self.addItem(self.head)10 11 self.setFocusItem(self.head)12 self.timer = QTimer()13 self.timer.start(Head.TimeGap)14 15 self.connect(self.timer, SIGNAL("timeout()"),16 self.segmentMove)17 def segmentMove(self):18 self.segmentGroup.move(self.head.pos())19 def mousePressEvent (self, event):20 if event.button() == Qt.LeftButton:21 tail = self.segmentGroup.tail()22 self.segmentGroup.addSegment(23 Segment(tail.pos())24 )25 elif event.button() == Qt.RightButton:26 curveType = random.choice(Head.CURVE_TYPES)27 self.head.curveType = QEasingCurve(curveType[1])
在这个类中添加数据,蛇头,蛇身管理器,设置了简单的增长蛇身的事件——按下鼠标左键;而按下右键则是更改蛇头的动画效果。将蛇身的移动与蛇头的移动对应起来,用一个与蛇头定时器时间相同的定时器来实现。
最后的form则是使用mainwindow在实现的应用程序。
1 class Form(QMainWindow): 2 def __init__(self,parent=None): 3 super(Form,self).__init__(parent) 4 self.fileMenu = self.menuBar().addMenu("&File") 5 self.statusBar().showMessage("Ready", 5000) 6 self.scene = SnackScene() 7 8 self.view = QGraphicsView(self.scene) 9 self.view.setRenderHint(QPainter.SmoothPixmapTransform)10 self.view.setDragMode(QGraphicsView.RubberBandDrag)11 self.setCentralWidget(self.view)
后记:做这个依然是为了熟练qt,本来是想用图形界面做设计模式的,但是有些模式想不好写神马好,就像装饰者模式,用神马例子捏?
联系客服