截至目前,我们可以通过面向对象思想,做一个属于自己的贪吃蛇游戏啦
尺寸相关
开发环境准备
安装依赖的库
pip install PyQt5
拷贝需要的资源
img.zip
将如上压缩包下载,并将里边的图片解压到工程根目录的img文件夹中。
准备Game游戏类
新建game.py文件,拷贝如下内容到文件
from abc import abstractmethodfrom PyQt5 import QtCore, QtGui, QtWidgetsimport sysCOLOR_GRAY = QtGui.QColor(128, 128, 128)COLOR_RED = QtGui.QColor(255, 0, 0)COLOR_GREEN = QtGui.QColor(0, 255, 0)COLOR_BLUE = QtGui.QColor(0, 0, 255)COLOR_WHITE = QtGui.QColor(255, 255, 255)class Game(QtWidgets.QWidget):def __init__(self):super().__init__()self.setWindowTitle("Game")self.timer = QtCore.QBasicTimer()self.delay = 500self.is_game_over = Falsedef set_fps(self, fps):self.delay = 1000 // fpsif self.timer.isActive():self.timer.stop()self.timer.start(self.delay, self)def timerEvent(self, event):if self.is_game_over:self.timer.stop()else:self.on_time_event(event)self.repaint()@abstractmethoddef on_time_event(self):pass@abstractmethoddef draw_content(self, qp: QtGui.QPainter):passdef paintEvent(self, event: QtGui.QPaintEvent):qp = QtGui.QPainter()qp.begin(self)self.draw_content(qp)qp.end()def keyPressEvent(self, event: QtGui.QKeyEvent):if event.key() == QtCore.Qt.Key_Escape:self.close()returnelif event.key() == QtCore.Qt.Key_Space:if self.is_game_over:self.start_game()returnsuper().keyPressEvent(event)def start_game(self):self.is_game_over = Falseself.timer.start(self.delay, self)@classmethoddef start(cls):app = QtWidgets.QApplication(sys.argv)# 创建cls的对象game = cls()game.start_game()game.show()# while (1)sys.exit(app.exec_())
继承Game游戏类(模板)
"""继承了Game的空类"""from game import *from PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *SCREEN_WIDTH = 640SCREEN_HEIGHT = 480class SnakeGame(Game):def __init__(self):super().__init__()# 设置标题self.setWindowTitle("SnakeGame")# 设置窗口尺寸, 并禁止拖拽修改窗口尺寸self.setFixedSize(SCREEN_WIDTH, SCREEN_HEIGHT)# 设置窗口图标self.setWindowIcon(QIcon("img/icon.png"))def keyPressEvent(self, event: QKeyEvent):"""处理按键事件"""super().keyPressEvent(event)def on_time_event(self, event):"""处理每帧时间事件"""passdef draw_content(self, qp: QPainter):"""绘制界面内容:param qp: 画笔"""passdef start_game(self):"""游戏开始前的初始化"""super().start_game()if __name__ == '__main__':SnakeGame.start()
编写游戏逻辑
完整代码逻辑如下:
import randomfrom PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *from game import *SCREEN_WIDTH = 640SCREEN_HEIGHT = 480BLOCK_SIZE = 20# 记录了每个方向对应的蛇头移动距离MOVE_DICT = {Qt.Key_Left: (-1, 0),Qt.Key_Right: (1, 0),Qt.Key_Up: (0, -1),Qt.Key_Down: (0, 1)}# 记录了每个方向对应的蛇头图片旋转角度DIRECTION_HEAD_ANGLE = {Qt.Key_Left: 90,Qt.Key_Right: 270,Qt.Key_Up: 180,Qt.Key_Down: 0}class Snake:def __init__(self) -> None:# 当前蛇头运动方向self.direction = Qt.Key_Right# 根据按键获取的最新蛇头运动方向self.direction_temp = None# 加载并缩放蛇头self.head_img: QImage = QImage("img/head-red.png").scaled(BLOCK_SIZE, BLOCK_SIZE)# 初始化蛇self.snake_body = [[3 * BLOCK_SIZE, 3 * BLOCK_SIZE],]# 生长2节self.score = 0self.grow()self.grow()def handle_key_event(self, event: QKeyEvent) -> bool:"""判定按钮是否是方向的按键并且判定能否改变到此方向:param event: 按键事件:return: True 能转向"""key = event.key()LR = Qt.Key_Left , Qt.Key_RightUD = Qt.Key_Up, Qt.Key_Downif key not in LR and key not in UD:return Falseif key in LR and self.direction in LR:return Falseif key in UD and self.direction in UD:return Falseself.direction_temp = keyreturn Truedef draw(self, qp: QPainter):"""绘制蛇头蛇身:param qp: 画笔"""qp.setBrush(COLOR_BLUE)for node in self.snake_body[1:]:# 绘制圆角矩形# qp.drawRect(node[0], node[1], BLOCK_SIZE, BLOCK_SIZE)qp.drawRoundedRect(node[0], node[1], BLOCK_SIZE, BLOCK_SIZE, 5, 5)qp.setBrush(QColor(0, 255, 0))head = self.snake_body[0]# qp.drawRect(self.snake_body[0][0], head[1], BLOCK_SIZE, BLOCK_SIZE)# 根据direction修改head_img旋转方向rotated_head = self.head_img.transformed(QTransform().rotate(DIRECTION_HEAD_ANGLE[self.direction]))qp.drawImage(head[0], head[1], rotated_head)def move(self):"""移动一格"""if self.direction_temp:self.direction = self.direction_tempself.direction_temp = None# 修改头部坐标new_head = self.snake_body[0][:]new_move = MOVE_DICT[self.direction]new_head[0] += new_move[0] * BLOCK_SIZEnew_head[1] += new_move[1] * BLOCK_SIZEself.snake_body.insert(0, new_head)# 删除尾部坐标self.snake_body.pop()def grow(self):# 取出尾部坐标, 并复制一份new_tail = self.snake_body[-1][:]# 插入到尾部self.snake_body.append(new_tail)class Apple:def __init__(self, x, y) -> None:self.node = [x * BLOCK_SIZE, y * BLOCK_SIZE]def draw(self, qp: QPainter):qp.setBrush(QColor(255, 0, 0))qp.drawRect(self.node[0], self.node[1], BLOCK_SIZE, BLOCK_SIZE)class SnakeGame(Game):def __init__(self):super().__init__()# 设置窗口尺寸, 并禁止拖拽修改窗口尺寸self.setFixedSize(SCREEN_WIDTH, SCREEN_HEIGHT)# 设置窗口标题self.setWindowTitle("Snake Game")# 图标self.setWindowIcon(QIcon("img/icon.png"))# 加载背景图, (缩放)self.background_img = QImage("img/bg.png").scaled(SCREEN_WIDTH, SCREEN_HEIGHT)# 设置fps,每秒5帧, 1000ms / 5 = 200msself.set_fps(5)self.snake = Noneself.apple = None# 构建地图格点的二维数组self.map = []for i in range(SCREEN_WIDTH // BLOCK_SIZE):for j in range(SCREEN_HEIGHT // BLOCK_SIZE):self.map.append([i, j])def keyPressEvent(self, event: QKeyEvent):if self.snake.handle_key_event(event):returnsuper().keyPressEvent(event)def on_time_event(self, event: QTimerEvent):"""处理每帧时间事件:param event: 事件"""self.snake.move()self.check_collision()def draw_content(self, qp: QPainter):# 绘制背景图qp.drawImage(0, 0, self.background_img)# 绘制网格线,间隔为BLOCK_SIZE# 设置画笔为灰色qp.setPen(COLOR_GRAY)# 绘制横线.# (0, 0) -> (SCREEN_WIDTH, 0)# (0,20) -> (SCREEN_WIDTH, 20)for y in range(0, SCREEN_HEIGHT, BLOCK_SIZE):qp.drawLine(0, y, SCREEN_WIDTH, y)# 绘制纵线for x in range(0, SCREEN_WIDTH, BLOCK_SIZE):qp.drawLine(x, 0, x, SCREEN_HEIGHT)self.snake.draw(qp)self.apple.draw(qp)# 绘制文字qp.setPen(COLOR_WHITE)# 设置字号22, 字体 Arialqp.setFont(QFont("Arial", 18))# 左上角得分 Score: xxxqp.drawText(20, 30, f"Score: {self.snake.score}")if self.is_game_over:# 设置字号20, 字体 Microsoft YaHeiqp.setFont(QFont("Microsoft YaHei", 20, weight=QFont.Bold))# Game Overqp.drawText(SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2, "游戏结束")qp.setFont(QFont("Microsoft YaHei", 14))qp.drawText(SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 + 40, "得分:" + str(self.snake.score))qp.drawText(SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 + 70, "按空格重新开始")def check_collision(self):"""碰撞检测"""snake_head = self.snake.snake_body[0]if snake_head == self.apple.node:print("吃到苹果")self.generate_new_apple()self.snake.grow()self.snake.score += 1# 如果蛇身占满地图,游戏结束if len(self.snake.snake_body) >= len(self.map):print("游戏胜利")self.is_game_over = True# 碰到自己if snake_head in self.snake.snake_body[1:]:self.is_game_over = Trueprint("碰到自己")# 碰到墙head_x, head_y = snake_headif head_x < 0 or head_x >= SCREEN_WIDTH or head_y < 0 or head_y >= SCREEN_HEIGHT:self.is_game_over = Trueprint("碰到墙")def generate_new_apple(self):"""生成新的苹果坐标"""# 复制一份self.map为新的temp_maptemp_map = self.map[:]# 将 temp_map 中snake的坐标删除for node in self.snake.snake_body:node_pos = [node[0] // BLOCK_SIZE, node[1] // BLOCK_SIZE]if node_pos in temp_map:temp_map.remove(node_pos)if len(temp_map) == 0:return# 生成新的苹果坐标new_pos = random.choice(temp_map)print("new_pos: ", new_pos)self.apple = Apple(new_pos[0], new_pos[1])def start_game(self):self.snake = Snake()# self.apple = Apple(2, 2)self.generate_new_apple()super().start_game()if __name__ == "__main__":SnakeGame.start()
