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

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

截图

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

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

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

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

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

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

原文见我的博客,博客园里的

转载于:https://my.oschina.net/duoduo3369/blog/109023

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值