分享

蛇年当然贪吃蛇 PyQt版贪吃蛇引子

 学海无涯GL 2014-10-24

蛇年到,贪吃蛇还是要出马下的,不准备写完整的程序,就让蛇跑起来,尾巴的长起来吧,蛇头有点动画得了。

 

先讲讲一些原理,蛇的脑袋使用键盘控制,因此重写他的keyPressEvent是势在必行的;

蛇身能够增长,运动,原来我计划是蛇身的每一块的坐标都会移动,可看见一个老哥说每次只要把尾巴移动的蛇脑袋那里,蛇脑袋再往前跑跑,蛇就动了,想想也是。蛇身是一块块组成的,很对的块形成一个组,变成蛇的身子,每次蛇要长长,只要在这个组里增加新的块即可。

因为使用了大量的图形,因此使用PyQt里面的带Graphics的那一堆类,重写QGraphicsItem的类或者子类,完成特定的功能,作为基本的元素;使用QGraphicsScene增加元素;QGraphicsView显示。

想使用动画Animation,看了看里面需要QObject来初始化,而QGraphicsItem居然不是继承自QObject的。因此我选择了QGraphicsObject作为基类来完善需求,他继承自QGraphicsItemQObject,可以使用动画的东西。

下面是完整的代码,先贴上:

View Code

 

资源文件:

View Code

 

开始讲解:

蛇的身体:

复制代码
 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.pix
13     def setPixmap(self,pix):
14         self.pix = pix
15     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.OutBounce
24         self.setPos(pos)
25         self.setFocus(Qt.OtherFocusReason)
26         self.direction = self.RIGHT
27         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 this
34         # else it wont be move
35         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 = position
45         
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.LEFT
70             return
71         if key == Qt.Key_Right:
72             self.direction = self.RIGHT
73             return
74         if key == Qt.Key_Up:
75             self.direction = self.UP
76             return
77         if key == Qt.Key_Down:
78             self.direction = self.DOWN
79             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)
复制代码

 

构造方法的定时器让蛇身连续移动,这个在我之前的博客中有介绍,不在啰嗦。

蛇身Group

复制代码
 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:return
19         return self.items[-1]
复制代码

 

这是为了方便管理,将蛇头和整段身体分离而做的一个容器,因为主要实在第一节蛇身和蛇尾之间进行插入删除操作,所以使用python的deque()来实现这个容器,并且这个容器直接将蛇身添加到scene中去。代码木有神马特别需要说明的。

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,本来是想用图形界面做设计模式的,但是有些模式想不好写神马好,就像装饰者模式,用神马例子捏?

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多