如何利用python开发一个贪吃蛇
文章目录
前言
今天将用python开发一个贪吃蛇游戏,主要是面向对象开发和pygame的基本使用
提示:以下是本篇文章正文内容,下面案例可供参考
一、pygame是什么
本次python运用PyCharm2020,3x64版本编写,python的版本为3.9
pygame是转为开发电子游戏而设计的跨平台的Python包(Package)所谓模块,是指能够独立完成一定功能的程序集合,程序员利用pygame提供的API,可以方便地实现,比如说你可以创建图形界面,监听用户键盘或鼠标操作,播放音频等等,这三个在本章中都有运用到。
关于pygame的安装,一开始费了很多时间,在python的官网下载,并看教程,反反复复失败了n次,最后发现在python3.9版本中安装的方法很简单只需要一行指令即可
虽然版本不对,但是仍可运行。。。
验证:在命令提示符输入以下命令,如果能够看到aliens游戏正常启动并运行,就说明已经安装成功了。
python3 -m pygame.examples.aliens
二、贪吃蛇游戏规则
2.1开始和结束
1.贪吃蛇一开始出生在左上角,只有一节身体一个头
2.蛇如果碰到了自己的身体或者碰到了游戏边界,那么就直接死亡
3.如果死亡或者想要暂停可以按下空格键
2.2怎么运动和控制
1.我们使用监听键盘上的方向键(↑、↓、←、→)来控制蛇的运动轨迹
2.3得分
1.得分设为吃到一个豆子得5分,初始为0分
2.食物必须满足是在游戏窗口随机生成的,如果蛇头跟食物碰到了,那就代表蛇吃到了食物,然后食物再次刷新随机位置
3.食物出现30s内,贪吃蛇没吃到,那么食物就刷新
4.游戏会随着你蛇的增长,也会变快
2.4 创建四个类
根据游戏的规则我们要创建4种对象分别是游戏对象、蛇、标签、食物
三、开发过程
3.1主要模块
新建项目及文件准备
本文创立了两个模块,一个是game.py,还有一个是game_items.py,并且还需要将需要的音乐素材和图片素材都放在同一个文件夹中,
pygame的初始化和推出
pygame为了程序员更加方便地使用包中模块,提供了两个方法——init和quit
·init一次性初始化pygame的所有模块,后续开发可以直接使用。
·quit方法可以取消初始化之前已经初始化过的模块,所以quit方法不是必须调用的
代码如下
import pygame #导入pygame
from game_items import *
if __name__ == '__main__':
pygame.init() # 初始化所有模块
# 游戏代码
Game().start() # 创建游戏对象并且启动游戏
pygame.quit() # 取消初始化所有模块
pygame.display.update()
for event in pygame.event.get():
# 判断事件类型是否是退出事件
if event.type == pygame.QUIT:
print("游戏退出...")
# quit 卸载所有的模块
pygame.quit()
# exit() 直接终止当前正在执行的程序
exit()
游戏主窗口
因为要实现交互界面,所以进入贪吃蛇游戏前,我设置了一个开始界面,点击了开始游戏即可进入游戏,进入游戏的过程就是刷新窗口用到pygame中的pygame.display.update()
使用set_mode方法可以非常方便的创建一个游戏主窗口
window=pygame.display.set_mode((800,600))
pygame.display.set_caption("Greedy Snake")
clock =pygame.time.Clock()
start_window = pygame.Surface(window.get_size()) # 充当开始界面的画布
start_window2 = pygame.Surface(window.get_size()) # 充当第一关的画布界面暂时占位(可以理解为游戏开始了)
start_window = start_window.convert()
start_window2 = start_window2.convert()
start_window.fill((255,255,255)) # 白色画布
start_window2.fill((0,0,0))
游戏界面的图形插入
我们可以使用如下代码将图片插入
# 加载各个素材图片 并且赋予变量名
i1 = pygame.image.load("InitialStart.png")
i1.convert()
i11 = pygame.image.load("ChosenStart.png")
i11.convert()
游戏循环和游戏时钟
要做到游戏程序启动执行之后,不会立即退出,需要在游戏程序中加入一个游戏循环,即无限循环
def start(self):
while True:
pass
当我们不设置一个游戏时钟时,我们会发现,cpu 的占用率极高,大约有20%,刷新帧率只要能够达到每秒60帧,就能达到一定效果,pygame的time模块中专门提供了一个Clock类,可以非常方便地设置刷新帧率pygame.time.Clock
事件监听
游戏循环中需要做四件事情:
事件监听、绘制游戏元素、更新显示、设置刷新率
在同一时刻,可能发生多个时间,因此应该使用for来遍历事件列表;使用event.type可以判断时间地类型:例如退出时间、按键事件等等;如果按键事件,使用event.key可以判断具体的按键
# 事件监听
for event in pygame.event.get(): # 遍历同一时刻发生的事件列表
if event.type == pygame.QUIT: # 判断退出事件
return
elif event.type == pygame.KEYDOWN: # 判断按键事件
if event.key == pygame.K_ESCAPE:
return
elif event.key == pygame.K_SPACE:
if self.is_game_over:
self.reset_game()
else:
self.is_pause = not self.is_pause
绘制图形
pygame的draw模块中专门提供了一系列方法,可以绘制图形以蛇类为例
def draw(self, window):
# 遍历绘制每一节身体
for idx, rect in enumerate(self.body_list):
pygame.draw.rect(window,
self.color,
rect.inflate(-2, -2), # 缩小矩形区域
idx == 0) # 蛇头绘制边框不填充
随机食物
在game_item.py模块的顶部,导入random模块,以方便使用随机数代码如下import random
在Food类中定义random_rect方法,随机确定游戏窗口的任意小个子设置食物出现的位置,代码如下:
class Food(object):
"""食物类"""
def __init__(self):
self.color = (0, 255, 0) # 绿色食物
self.score = 5 # 每颗食物得分 5 分
self.rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE) # 食物位置
self.random_rect() # 设置食物随机位置
def random_rect(self):
col = SCREEN_RECT.w / CELL_SIZE - 1 # 屏幕上小格子的列数
row = SCREEN_RECT.h / CELL_SIZE - 1 # 屏幕上小格子的行数
x = random.randint(0, col) * CELL_SIZE
y = random.randint(0, row) * CELL_SIZE
self.rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
# 食物初始不可见
self.rect.inflate_ip(-CELL_SIZE, -CELL_SIZE)
pygame.time.set_timer(FOOD_UPDATE_EVENT, 30000) # 设置更新食物事件
定时器的设置
按照先前的设计,如果30s内贪吃蛇没有吃到食物,那么食物要再次刷新。现在如果暂时不考虑贪吃蛇吃食物的情况,要实现游戏规则需求,我们只要每隔30s调用一下random_rect方法即可,这时候我们就可以用到pygame 的time模块中提供的set_timer方法,就是专门用来定时器事件的。
FOOD_UPDATE_EVENT = pygame.USEREVENT # 食物更新事件
蛇死亡判定思路
既要判断舍得死亡又要记录死亡时的状态
①在Snake类定义is_dead方法:如果碰到边界或者身体测返回True
②完善update方法:一旦发现移动身体之后贪吃蛇挂了,则回复之前的身体数据,并且返回Flase,表示无法移动
def is_dead(self):
"""判断贪吃蛇是否死亡
:return: 死亡返回 True,否则返回 False
"""
# 1. 记录蛇头的矩形区域
head = self.body_list[0]
# 2. 判断蛇头是否移出屏幕
if not SCREEN_RECT.contains(head):
return True
# 3. 判断是否与身体其他部分重叠
for body in self.body_list[1:]:
if head.contains(body):
return True
return False
加入背景音乐
①先将所需要的背景音乐下载至同一个文件夹,通过pygame 的模块调用,代码如下
# 音乐的路径
file=r'浪子康 - 小星星(咚鼓版)(翻自 汪苏泷).mp3'
file1=r'林俊杰 - 交换余生.mp3'
# 初始化
pygame.mixer.init()
# 加载音乐文件
track = pygame.mixer.music.load(file)
pygame.mixer.music.queue(file1)
# 开始播放音乐流
pygame.mixer.music.play()
3.2源代码
game.py代码如下:
import pygame
from game_items import *
# 音乐的路径
file=r'浪子康 - 小星星(咚鼓版)(翻自 汪苏泷).mp3'
file1=r'林俊杰 - 交换余生.mp3'
# 初始化
pygame.mixer.init()
# 加载音乐文件
track = pygame.mixer.music.load(file)
pygame.mixer.music.queue(file1)
# 开始播放音乐流
pygame.mixer.music.play()
pygame.init()
window=pygame.display.set_mode((800,600))
pygame.display.set_caption("Greedy Snake")
clock =pygame.time.Clock()
start_window = pygame.Surface(window.get_size()) # 充当开始界面的画布
start_window2 = pygame.Surface(window.get_size()) # 充当第一关的画布界面暂时占位(可以理解为游戏开始了)
start_window = start_window.convert()
start_window2 = start_window2.convert()
start_window.fill((255,255,255)) # 白色画布
start_window2.fill((0,0,0))
# 加载各个素材图片 并且赋予变量名
i1 = pygame.image.load("InitialStart.png")
i1.convert()
i11 = pygame.image.load("ChosenStart.png")
i11.convert()
# 以下为选择开始界面鼠标检测结构。
n1 = True
while n1:
clock.tick(30)
buttons = pygame.mouse.get_pressed()
x1, y1 = pygame.mouse.get_pos()
if x1 >= 227 and x1 <= 555 and y1 >= 261 and y1 <=327:
start_window.blit(i11, (200, 240))
if buttons[0]:
n1 = False
window.blit(start_window,(0,0))
pygame.display.update()
# 下面是监听退出动作
# 监听事件
for event in pygame.event.get():
# 判断事件类型是否是退出事件
if event.type == pygame.QUIT:
print("游戏退出...")
# quit 卸载所有的模块
pygame.quit()
# exit() 直接终止当前正在执行的程序
exit()
# 以下可以写贪吃蛇的代码了
n2 = True
while n2:
clock.tick(30)
from game_items import * # 导入游戏元素模块中的所有类和全局变量
class Game(object):
"""游戏类"""
def __init__(self):
self.main_window = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Greedy Snake")
self.score_label = Label() # 得分文本标签
self.tip_label = Label(24, False) # 提示标签
self.is_game_over = False # 游戏结束标记
self.is_pause = False # 游戏暂停标记
self.food = Food() # 食物
self.snake = Snake() # 贪吃蛇
print(self.snake.body_list)
def reset_game(self):
"""游戏复位"""
self.is_game_over = False # 游戏结束标记
self.is_pause = False # 游戏暂停标记
self.food.random_rect() # 重新设置食物位置
self.snake.reset_snake() # 设置蛇属性
def start(self):
"""开始贪吃蛇游戏"""
clock = pygame.time.Clock() # 游戏时钟
while True:
# 事件监听
for event in pygame.event.get(): # 遍历同一时刻发生的事件列表
if event.type == pygame.QUIT: # 判断退出事件
return
elif event.type == pygame.KEYDOWN: # 判断按键事件
if event.key == pygame.K_ESCAPE:
return
elif event.key == pygame.K_SPACE:
if self.is_game_over:
self.reset_game()
else:
self.is_pause = not self.is_pause
# 仅在游戏状态才会更新食物、移动蛇的位置以及改变蛇的运动方向
if not self.is_pause and not self.is_game_over:
if event.type == FOOD_UPDATE_EVENT: # 判断更新食物事件
self.food.random_rect()
if event.type == SNAKE_UPDATE_EVENT: # 判断贪吃蛇移动事件
self.is_game_over = not self.snake.update()
if event.type == pygame.KEYDOWN: # 判断按键事件
# 判断是否方向键
if event.key in (pygame.K_LEFT, pygame.K_RIGHT,
pygame.K_UP, pygame.K_DOWN):
self.snake.change_dir(event.key)
# 依次绘制游戏元素
self.main_window.fill(BACKGROUND_COLOR)
# 判断游戏状态
if self.is_game_over:
self.tip_label.draw(self.main_window, "游戏结束,按空格键开启新游戏...")
elif self.is_pause:
self.tip_label.draw(self.main_window, "游戏暂停,按空格键继续...")
else:
# 判断是否吃到食物
if self.snake.has_eat(self.food):
self.food.random_rect()
self.score_label.draw(self.main_window, "得分:%d" % self.snake.score)
self.food.draw(self.main_window)
self.snake.draw(self.main_window)
# 更新显示
pygame.display.update()
clock.tick(60) # 刷新帧率
if __name__ == '__main__':
pygame.init() # 初始化所有模块
# 游戏代码
Game().start() # 创建游戏对象并且启动游戏
pygame.quit() # 取消初始化所有模块
pygame.display.update()
for event in pygame.event.get():
# 判断事件类型是否是退出事件
if event.type == pygame.QUIT:
print("游戏退出...")
# quit 卸载所有的模块
pygame.quit()
# exit() 直接终止当前正在执行的程序
exit()
game_items.py代码如下:
import random
import pygame
# 全局变量定义
SCREEN_RECT = pygame.Rect(0, 0, 640, 480) # 游戏窗口矩形区域
CELL_SIZE = 20 # 小格子大小
BACKGROUND_COLOR = (255,255, 255) # 主窗口背景颜色
SCORE_TEXT_COLOR = (0, 0, 0) # 分数文字颜色
TIP_TEXT_COLOR = (0, 0, 255) # 提示文字颜色
FOOD_UPDATE_EVENT = pygame.USEREVENT # 食物更新事件
SNAKE_UPDATE_EVENT = pygame.USEREVENT + 1 # 贪吃蛇移动事件
class Label(object):
"""文字标签类"""
def __init__(self, size=48, is_score=True):
"""初始化方法
:param size: 字体大小
:param is_score: 是否分数
"""
self.font = pygame.font.SysFont("simhei", size) # 黑体字
self.is_score = is_score
def draw(self, window, text):
"""在窗口中绘制文本内容
:param window: 游戏主窗口
:param text: 要显示的文本内容
"""
# 1. 使用字体渲染文本内容
color = SCORE_TEXT_COLOR if self.is_score else TIP_TEXT_COLOR
text_surface = self.font.render(text, True, color)
# 获得文本图像的矩形区域
text_rect = text_surface.get_rect()
# 获得主窗口的矩形区域
window_rect = window.get_rect()
# 设置位置
if self.is_score: # 分数文字显示在右下角
text_rect.bottomright = window_rect.bottomright
else: # 其他文字显示在中间
text_rect.center = window_rect.center
# 2. 在游戏窗口中绘制渲染结果
window.blit(text_surface, text_rect)
class Food(object):
"""食物类"""
def __init__(self):
self.color = (0, 255, 0) # 绿色食物
self.score = 5 # 每颗食物得分 5 分
self.rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE) # 食物位置
self.random_rect() # 设置食物随机位置
def random_rect(self):
col = SCREEN_RECT.w / CELL_SIZE - 1 # 屏幕上小格子的列数
row = SCREEN_RECT.h / CELL_SIZE - 1 # 屏幕上小格子的行数
x = random.randint(0, col) * CELL_SIZE
y = random.randint(0, row) * CELL_SIZE
self.rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
# 食物初始不可见
self.rect.inflate_ip(-CELL_SIZE, -CELL_SIZE)
pygame.time.set_timer(FOOD_UPDATE_EVENT, 30000) # 设置更新食物事件
def draw(self, window):
# # 判断宽度是否达到小格子宽度
if self.rect.w < CELL_SIZE:
self.rect.inflate_ip(2, 2) # 向四周各自放大 1 个像素
pygame.draw.ellipse(window, self.color, self.rect)
class Snake(object):
"""贪吃蛇类"""
def __init__(self):
self.dir = pygame.K_RIGHT # 初始向右运动
self.score = 0 # 初始得分
self.time_interval = 250 # 运动间隔时间
self.color = (255, 144, 255) # 身体颜色-紫粉
self.body_list = [] # 身体列表
self.reset_snake()
def reset_snake(self):
"""重置蛇属性"""
self.dir = pygame.K_RIGHT
self.score = 0
self.time_interval = 250
self.body_list.clear() # 清空身体列表
for i in range(2): # 添加一节身体
self.add_node()
def add_node(self):
"""在蛇的运动方向上,增加一节身体"""
# 1. 判断是否有身体
if self.body_list:
head = self.body_list[0].copy()
else:
head = pygame.Rect(-CELL_SIZE, 0, CELL_SIZE, CELL_SIZE)
# 2. 根据运动方向,调整 head 的位置
if self.dir == pygame.K_RIGHT:
head.x += CELL_SIZE
elif self.dir == pygame.K_LEFT:
head.x -= CELL_SIZE
elif self.dir == pygame.K_UP:
head.y -= CELL_SIZE
elif self.dir == pygame.K_DOWN:
head.y += CELL_SIZE
# 3. 将蛇头插入到身体列表第 0 项
self.body_list.insert(0, head)
# 4. 设置贪吃蛇移动定时器
pygame.time.set_timer(SNAKE_UPDATE_EVENT, self.time_interval)
def draw(self, window):
# 遍历绘制每一节身体
for idx, rect in enumerate(self.body_list):
pygame.draw.rect(window,
self.color,
rect.inflate(-2, -2), # 缩小矩形区域
idx == 0) # 蛇头绘制边框不填充
def update(self):
"""移动贪吃蛇的整个身体
一旦发现贪吃蛇移动后会死亡,则恢复整个身体数据
:return: 移动成功返回 True,贪吃蛇死亡表示不能移动,返回 False
"""
# 1. 备份身体列表
body_list_copy = self.body_list.copy()
# 2. 移动身体
self.add_node() # 沿着运动方向在蛇头位置增加一节身体
self.body_list.pop() # 删除蛇尾
# 3. 判断是否死亡,如果是,恢复备份的身体
if self.is_dead():
self.body_list = body_list_copy
return False
return True
def change_dir(self, to_dir):
"""改变贪吃蛇的运动方向
:param to_dir: 要变化的方向
"""
hor_dirs = (pygame.K_RIGHT, pygame.K_LEFT) # 水平方向
ver_dirs = (pygame.K_UP, pygame.K_DOWN) # 垂直方向
# 判断当前运动方向及要修改的方向
if ((self.dir in hor_dirs and to_dir not in hor_dirs) or
(self.dir in ver_dirs and to_dir not in ver_dirs)):
self.dir = to_dir
def has_eat(self, food):
"""判断蛇头是否与食物相遇 - 吃到食物
:param food: 食物对象
:return: 是否吃到食物
"""
if self.body_list[0].contains(food.rect):
self.score += food.score # 增加分数
# 修改运动时间间隔
if self.time_interval > 100:
self.time_interval -= 50
self.add_node() # 增加一节身体
return True
return False
def is_dead(self):
"""判断贪吃蛇是否死亡
:return: 死亡返回 True,否则返回 False
"""
# 1. 记录蛇头的矩形区域
head = self.body_list[0]
# 2. 判断蛇头是否移出屏幕
if not SCREEN_RECT.contains(head):
return True
# 3. 判断是否与身体其他部分重叠
for body in self.body_list[1:]:
if head.contains(body):
return True
return False
总结
此次由于时间的仓促,在开发贪吃蛇游戏的过程中,虽然游戏整体可以运行,但是交互仍有待提高,其中存在了一些问题还没有来得及去解决,比如死亡时候bgm要暂停,或者是玩家自己暂停游戏,bgm也要停止,这些都没有时间去完善,再就是游戏过于单一,没有设计出游戏难度的选择,只有开始游戏这一选项,需要日后学习完善。
以下是展示视频