介绍
贪吃蛇是我们小时候的一款经典游戏,毕竟在那个Nokia称霸的年代,这款游戏可是经典中的经典啊!而用Python(蛇)玩Snake(贪吃蛇),那再合适不过了。
需求分析
我们先来回顾下贪吃蛇中的游戏元素及游戏规则:
游戏开发运行环境
分析
我们首先需要初始化一些参数:游戏界面大小、蛇和食物的参数以及一些游戏边界和障碍的设置等等
代码实现
import pygame
if __name__ == "__main__":
# 初始化pygame
pygame.init()
# 游戏界面大小
GAME_SIZE = [900,900]
SIZE = [GAME_SIZE[0],GAME_SIZE[1]+100]
# 字体设置
FONT_S = pygame.font.SysFont('Times', 50)
FONT_M = pygame.font.SysFont('Times', 90)
# 方向列表
DIRECTION = ['up','right','down','left']
# X 和 Y 坐标列表
X_LIST = [x for x in range(GAME_SIZE[0])]
Y_LIST = [y for y in range(GAME_SIZE[1])]
# 食物颜色
FOOD_COLORS = ((46,139,87),(199,21,133),(25,25,112),(255,215,0))
# 墙
wall_list = [[100,200],[600,500],[350,200],[500,800]]
WALL_WIDTH,WALL_HEIGHT = 30,30
# 食物
food_list = [(150,200,1),(300,500,1),(740,542,1),(300,600,1),(700,600,1)]
FOOD_WIDTH,FOOD_HEIGHT = 14,14
# 创建游戏窗口 900*1000
screen = pygame.display.set_mode(SIZE)
# 蛇的参数
snake_list = [[100+12*4,100],[100+12*3,100],[100+12*2,100],[100+12*1,100],[100,100]]
SNAKE_WIDTH,SNAKE_HEIGHT = 12,12
snake_v = 0
count_time = 0
# 等级
frame = 0.05
level = 1
# 主循环
running = True
pause = False
dead = False
head = 'right'
分析
定义一个名为 draw_background()
的函数,它用来绘制游戏的背景。
具体的绘制过程如下:
screen.fill(COLORS['white'])
使用白色填充背景屏幕,将屏幕背景设置为白色。pygame.draw.rect(screen,COLORS['black'],(-100,GAME_SIZE[1],3000,200),0)
绘制一个长方形作为地面。该矩形的位置 (x, y)
是 (-100, GAME_SIZE[1])
,宽度为 3000
,高度为 200
。矩形的颜色是 COLORS 字典中的 black
。通过调用 draw_background()
函数,可以在游戏屏幕上绘制出白色的背景以及一个黑色的长方形作为地面
代码
def draw_background():
# white background
screen.fill(COLORS['white'])
pygame.draw.rect(screen,COLORS['black'],(-100,GAME_SIZE[1],3000,200),0)
分析
定义一个名为 draw_wall()
的函数,它用来绘制游戏中的墙壁。
具体的绘制过程如下:
for
循环遍历墙壁列表 wall_list
中的每个元素,每个元素表示一个墙壁的位置。pygame.draw.rect()
函数绘制一个矩形,位置 (x, y)
是 (xy[0]-WALL_WIDTH/2, xy[1]-WALL_WIDTH/2)
,宽度为 WALL_WIDTH
,高度为 WALL_HEIGHT
。矩形的颜色是 COLORS 字典中的 darkgray
。通过调用 draw_wall()
函数,可以在游戏屏幕上绘制出墙壁。每个墙壁由一个矩形组成,矩形的位置和大小均在调用时指定。注意,墙壁的位置需要减去一半的墙壁宽度和高度,以便将矩形的中心对齐到墙壁位置上。
代码
def draw_wall():
for xy in wall_list:
pygame.draw.rect(screen,COLORS['darkgray'],(xy[0]-WALL_WIDTH/2,xy[1]-WALL_WIDTH/2,WALL_WIDTH,WALL_HEIGHT),0)
分析
定义一个名为 draw_snake()
的函数,它用来绘制游戏中的蛇。
具体的绘制过程如下:
snake_list
列表中的第一个元素作为蛇头,调用 pygame.draw.circle()
函数绘制一个圆作为蛇头。圆心的位置 (x, y)
是 (head[0], head[1])
,半径为 SNAKE_WIDTH/2
。圆的颜色是 COLORS 字典中的 darkred
。for
循环遍历 snake_list
中除了第一个元素以外的其他元素。对于每个元素,调用 pygame.draw.rect()
函数绘制一个矩形,位置 (x, y)
是 (xy[0]-SNAKE_WIDTH/2, xy[1]-SNAKE_WIDTH/2)
,宽度为 SNAKE_WIDTH
,高度为 SNAKE_HEIGHT
,边框宽度为 2
。矩形的颜色与圆的颜色相同。通过调用 draw_snake()
函数,可以在游戏屏幕上绘制出蛇。蛇头使用一个圆表示,蛇身使用一系列矩形拼接而成,每个矩形的位置和大小均在调用时指定。注意,蛇头和蛇身分别使用了不同的绘制方式,因为它们形状不同。
代码
def draw_snake():
head = snake_list[0]
pygame.draw.circle(screen,COLORS['darkred'],(head[0],head[1]),int(SNAKE_WIDTH/2),0)
for xy in snake_list[1:]:
pygame.draw.rect(screen,COLORS['darkred'],(xy[0]-SNAKE_WIDTH/2,xy[1]-SNAKE_WIDTH/2,SNAKE_WIDTH,SNAKE_HEIGHT),2)
分析
定义一个名为 draw_food()
的函数,它用来绘制食物物品。
具体的绘制过程如下:
for
循环遍历食物列表 food_list
中的每个元素,每个元素表示一个食物的位置和类型。pygame.draw.rect()
函数绘制一个矩形,位置 (x, y)
是 (xyz[0]-FOOD_WIDTH/2, xyz[1]-FOOD_WIDTH/2)
,宽度为 FOOD_WIDTH
,高度为 FOOD_HEIGHT
。矩形的颜色是 FOOD_COLORS
列表中根据食物类型 xyz[2]
所对应的颜色值。通过调用 draw_food()
函数,可以在游戏屏幕上绘制出各种类型的食物。每个食物由一个矩形组成,矩形的位置和大小均在调用时指定。注意,食物的位置需要减去一半的食物宽度和高度,以便将矩形的中心对齐到食物位置上。
代码
# 绘制食物
def draw_food():
# 遍历食物列表
for xyz in food_list:
# 获取食物位置和类型,绘制食物矩形
pygame.draw.rect(screen, FOOD_COLORS[xyz[2]-1], (xyz[0] - FOOD_WIDTH/2, xyz[1] - FOOD_WIDTH/2, FOOD_WIDTH, FOOD_HEIGHT), 0)
分析
定义一个名为 draw_context()
的函数,它用来绘制游戏的上下文信息。
具体的绘制过程如下:
txt
,文本内容是 "Snake length: " 连接上蛇身长度 len(snake_list)-1
。使用 FONT_M
字体来渲染文本,文本颜色是 COLORS 字典中的 lightblue
。(x, y)
,其中 x
的初始值为 10
,y
的初始值为 GAME_SIZE[1]+(int((SIZE[1]-GAME_SIZE[1])/2))
。接下来,将 y
的值减去文本字体尺寸的一半,以实现垂直居中。screen.blit()
函数将文本对象绘制到屏幕上,位置为 (x, y)
。通过调用 draw_context()
函数,可以在游戏屏幕上绘制出游戏的上下文信息,包括蛇的长度
代码
# 绘制游戏上下文信息
def draw_context():
# 创建文本对象,包括蛇的长度信息
txt = FONT_M.render('Snake length: '+str(len(snake_list)-1),True,COLORS['lightblue'])
# 计算文本位置
x,y = 10,GAME_SIZE[1]+(int((SIZE[1]-GAME_SIZE[1])/2))
y = int(y-FONT_M.size('Count')[1]/2)
# 绘制文本
screen.blit(txt,(x,y))
分析
定义一个名为 draw_pause()
的函数,它用于在游戏暂停时绘制暂停界面。
具体的绘制过程如下:
s
,大小与 SIZE
相同,并填充为带有半透明的白色 (255,255,255,220)
。screen.blit()
函数将表面 s
绘制到屏幕上的位置 (0, 0)
,实现背景的半透明效果。txt
,文本内容为 “PAUSE”,使用 FONT_M
字体渲染文本,文本颜色为 COLORS 字典中的 darkgray
。(x, y)
,其中 x
的初始值为 SIZE[0]/2
,y
的初始值为 SIZE[1]/2
。然后,将 x
和 y
的值减去文本字体尺寸的一半,以实现水平和垂直居中。screen.blit()
函数将文本对象绘制到屏幕上的位置 (x, y)
。通过调用 draw_pause()
函数,可以在游戏屏幕上绘制暂停界面。呈现为一个半透明的背景,并显示 “PAUSE” 文本在中间位置。注意,需要使用透明度为 220
的色彩值来填充表面,以实现半透明效果。
代码
# 绘制暂停界面
def draw_pause():
# 创建一个带有半透明的透明表面
s = pygame.Surface(SIZE, pygame.SRCALPHA)
s.fill((255,255,255,220))
# 将透明表面绘制到屏幕上
screen.blit(s, (0,0))
# 创建文本对象,显示 "PAUSE"
txt = FONT_M.render('PAUSE',True,COLORS['darkgray'])
# 计算文本的位置
x,y = SIZE[0]/2,SIZE[1]/2
x,y = int(x-FONT_M.size('PAUSE')[0]/2),int(y-FONT_M.size('PAUSE')[1]/2)
# 绘制文本
screen.blit(txt,(x,y))
分析
定义一个名为 draw_dead()
的函数,用于在游戏中玩家死亡时绘制死亡界面。
具体的绘制过程如下:
s
,大小与 SIZE
相同,并填充为带有半透明的白色 (255,255,255,240)
。screen.blit()
函数将表面 s
绘制到屏幕上的位置 (0, 0)
,实现背景的半透明效果。txt
,文本内容为 “YOU DEAD”,使用 FONT_M
字体渲染文本,文本颜色为 COLORS 字典中的 black
。(x, y)
,其中 x
的初始值为 SIZE[0]/2
,y
的初始值为 SIZE[1]/2
。然后,将 x
和 y
的值减去文本字体尺寸的一半,以实现水平和垂直居中。screen.blit()
函数将文本对象绘制到屏幕上的位置 (x, y)
。通过调用 draw_dead()
函数,可以在游戏屏幕上绘制死亡界面。呈现为一个更加明亮的背景,并显示 “YOU DEAD” 文本在中间位置。注意,需要使用透明度为 240
的色彩值来填充表面,以实现更明显的半透明效果。
代码
# 绘制死亡界面
def draw_dead():
# 创建一个更明亮的带有半透明的透明表面
s = pygame.Surface(SIZE, pygame.SRCALPHA)
s.fill((255,255,255,240))
# 将透明表面绘制到屏幕上
screen.blit(s, (0,0))
# 创建文本对象,显示 "YOU DEAD"
txt = FONT_M.render('YOU DEAD',True,COLORS['black'])
# 计算文本的位置
x,y = SIZE[0]/2,SIZE[1]/2
x,y = int(x-FONT_M.size('YOU DEAD')[0]/2),int(y-FONT_M.size('YOU DEAD')[1]/2)
# 绘制文本
screen.blit(txt,(x,y))
分析
定义一个名为 rect_cover(rect1,rect2)
的函数,用于判断两个矩形是否相交。
具体的判断过程如下:
left1
表示矩形 rect1
左边界,right1
表示矩形 rect1
右边界,up1
表示矩形 rect1
上边界,down1
表示矩形 rect1
下边界;同理,left2
、right2
、up2
、down2
分别为矩形 rect2
的四个边界值。rect1
和矩形 rect2
相交,则它们的边界值应该满足下面的四种情况之一:rect2
的右边界 right2
小于或等于 rect1
的左边界 left1
rect2
的左边界 left2
大于或等于 rect1
的右边界 right1
rect2
的下边界 down2
小于或等于 rect1
的上边界 up1
rect2
的上边界 up2
大于或等于 rect1
的下边界 down1
如果不满足上述四种情况,则认为它们相交,此时函数返回值为 True
;否则,返回值为 False
。
代码
# 判断两个矩形是否相交
def rect_cover(rect1, rect2):
# 提取矩形1的边界值
left1 = int(rect1[0])
right1 = int(rect1[0] + rect1[2])
up1 = int(rect1[1])
down1 = int(rect1[1] + rect1[3])
# 提取矩形2的边界值
left2 = int(rect2[0])
right2 = int(rect2[0] + rect2[2])
up2 = int(rect2[1])
down2 = int(rect2[1] + rect2[3])
# 判断两个矩形是否相交
if not (right2 <= left1 or left2 >= right1 or down2 <= up1 or up2 >= down1):
return True
return False
分析
定义一个名为 add_food()
的函数,用于向食物列表 food_list
中添加食物。
具体的过程如下:
while(True)
。X_LIST
中的元素、随机选择的 Y_LIST
中的元素和随机选择的 [1,2,3,4]
中的元素组成的列表 xyz
。xyz
是否存在于墙壁列表 wall_list
中。如果不存在,说明 xyz
没有被墙壁占据,可以作为食物的位置。xyz
添加到食物列表 food_list
中,并使用 break
语句跳出循环。通过调用 add_food()
函数,可以在游戏中不断添加新的食物位置。在添加食物之前,会进行检查,确保新位置不与墙壁重叠。
代码
import random
# 向食物列表中添加食物
def add_food():
while True:
# 随机选择X、Y和数值
xyz = [
random.choice(X_LIST),
random.choice(Y_LIST),
random.choice([1, 2, 3, 4])
]
# 检查食物位置是否与墙壁重叠
if xyz not in wall_list:
# 将食物位置添加到食物列表
food_list.append(xyz)
break # 跳出循环
分析
一个名为 add_body()
的函数,用于向蛇的身体列表 snake_list
中添加身体节。
具体的过程如下:
length
决定,默认为1。snake_list
中的倒数第二个和最后一个元素,并分别赋值给 last2
和 last1
。snake_list
中。通过调用 add_body()
函数,可以向蛇的身体列表中添加指定数量的身体节,以延长蛇的长度。
代码
# 向蛇的身体列表中添加身体节
def add_body(length=1):
for c in range(length):
# 获取最后两个身体节的坐标
last2, last1 = snake_list[-2], snake_list[-1]
if last2[0] == last1[0]: # 如果最后两节身体在垂直方向上相等
if last2[1] > last1[1]: # 朝下
# 向下添加一节身体
snake_list.append([last1[0], last1[1] - SNAKE_WIDTH])
else:
# 向上添加一节身体
snake_list.append([last1[0], last1[1] + SNAKE_WIDTH])
else: # 如果最后两节身体在水平方向上相等
if last2[0] > last1[0]: # 朝右
# 向右添加一节身体
snake_list.append([last1[0] - SNAKE_WIDTH, last1[1]])
else:
# 向左添加一节身体
snake_list.append([last1[0] + SNAKE_WIDTH, last1[1]])
分析
定义一个检查蛇头是否与食物碰撞的函数。具体的实现逻辑如下:
snake_list[0]
,并计算出蛇头的矩形区域 snake_head_rect
。矩形区域的坐标为 (蛇头x坐标-SNAKE_WIDTH/2, 蛇头y坐标-SNAKE_WIDTH/2, SNAKE_WIDTH, SNAKE_HEIGHT)
。food_list
,获取每个食物的坐标 xyz
。再计算出食物的矩形区域 food_rect
。矩形区域的坐标为 (食物x坐标-FOOD_WIDTH/2, 食物y坐标-FOOD_WIDTH/2, FOOD_WIDTH, FOOD_HEIGHT)
。rect_cover
函数判断蛇头的矩形区域是否与当前食物的矩形区域重叠。如果重叠,则表示蛇头与该食物碰撞了。add_body
函数将食物的类型 xyz[2]
加入到蛇的身体中,并从食物列表中移除该食物。代码
# 检查是否有食物被吃掉
def check_food():
# 获取蛇头的位置
first = snake_list[0]
# 创建蛇头的矩形区域
snake_head_rect = (first[0] - SNAKE_WIDTH/2, first[1] - SNAKE_WIDTH/2, SNAKE_WIDTH, SNAKE_HEIGHT)
# 遍历食物列表
for i in range(len(food_list)):
xyz = food_list[i]
# 创建食物的矩形区域
food_rect = (xyz[0] - FOOD_WIDTH/2, xyz[1] - FOOD_WIDTH/2, FOOD_WIDTH, FOOD_HEIGHT)
# 检查蛇头是否与食物重叠
if rect_cover(snake_head_rect, food_rect):
# 添加食物对应的身体节
add_body(xyz[2])
# 从食物列表中删除已被吃掉的食物
del food_list[i]
return True
return False
分析
定义一个check_dead()
函数用于检查蛇是否死亡,即判断蛇头是否与边缘、墙壁或自身的身体发生碰撞。具体过程如下:
True
。True
。True
。False
。代码
# 检查蛇是否死亡
def check_dead():
# 获取蛇头的位置
first = snake_list[0]
# 创建蛇头的矩形区域
snake_head_rect = (first[0] - SNAKE_WIDTH/2, first[1] - SNAKE_WIDTH/2, SNAKE_WIDTH, SNAKE_HEIGHT)
# 检查蛇头是否与边缘发生碰撞
if first[0] < 0 or first[0] > GAME_SIZE[0] or first[1] < 0 or first[1] > GAME_SIZE[1]:
return True
# 检查蛇头是否与墙壁发生碰撞
for xy in wall_list:
wall_rect = (xy[0] - WALL_WIDTH/2, xy[1] - WALL_WIDTH/2, WALL_WIDTH, WALL_HEIGHT)
if rect_cover(snake_head_rect, wall_rect):
return True
# 检查蛇头是否与自身的身体发生碰撞
for xy in snake_list[1:]:
body_rect = (xy[0] - SNAKE_WIDTH/2, xy[1] - SNAKE_WIDTH/2, SNAKE_WIDTH, SNAKE_HEIGHT)
if rect_cover(snake_head_rect, body_rect):
return True
return False
分析
编写一个处理事件的循环,它的作用是根据用户的输入来控制游戏的行为。主要包含以下几个事件处理:
pygame.QUIT
事件:当用户点击窗口的关闭按钮时,这个事件会触发。在这段代码中,会将 running
变量设置为 False
,从而退出游戏循环。pygame.MOUSEBUTTONDOWN
事件:当用户点击鼠标按钮时,这个事件会触发。在这段代码中,每次点击鼠标按钮会切换 pause
变量的状态,用来暂停游戏。pygame.KEYUP
事件:当用户松开键盘上的按键时,这个事件会触发。在这段代码中,根据松开的按键来控制蛇头的移动方向。具体的处理逻辑如下:
K_LEFT
(即左箭头键),并且当前蛇头方向不是向右(head
不为 ‘right’),则将新的蛇头方向设置为向左(‘left’)。K_RIGHT
(即右箭头键),并且当前蛇头方向不是向左(head
不为 ‘left’),则将新的蛇头方向设置为向右(‘right’)。K_UP
(即上箭头键),并且当前蛇头方向不是向下(head
不为 ‘down’),则将新的蛇头方向设置为向上(‘up’)。K_DOWN
(即下箭头键),并且当前蛇头方向不是向上(head
不为 ‘up’),则将新的蛇头方向设置为向下(‘down’)。代码
# 事件循环
while running:
# 获取所有事件并逐个处理
for event in pygame.event.get():
# 处理关闭窗口事件
if event.type == pygame.QUIT:
# 退出游戏循环
running = False
break
# 处理鼠标点击事件
elif event.type == pygame.MOUSEBUTTONDOWN:
# 切换暂停状态(pause变量为True时暂停,为False时正常运行)
pause = not pause
# 处理按键松开事件
elif event.type == pygame.KEYUP:
# 如果松开的键是左箭头键,并且当前蛇头方向不是向右,则将新的蛇头方向设置为向左
if event.key == K_LEFT:
if head in ['up','down']:
head = 'left'
# 如果松开的键是右箭头键,并且当前蛇头方向不是向左,则将新的蛇头方向设置为向右
elif event.key == K_RIGHT:
if head in ['up','down']:
head = 'right'
# 如果松开的键是上箭头键,并且当前蛇头方向不是向下,则将新的蛇头方向设置为向上
elif event.key == K_UP:
if head in ['left','right']:
head = 'up'
# 如果松开的键是下箭头键,并且当前蛇头方向不是向上,则将新的蛇头方向设置为向下
elif event.key == K_DOWN:
if head in ['left','right']:
head = 'down'
分析
在游戏不暂停且蛇没有死亡的情况下,更新蛇的位置。具体操作如下:
frame
和游戏难度 level
来计算游戏进行的时间。head
的值,更新蛇头的位置。如果头部方向是 ‘up’,则将头部位置的纵坐标减去一个单位;如果方向是 ‘down’,则将纵坐标加上一个单位;如果方向是 ‘left’,则将横坐标减去一个单位;如果方向是 ‘right’,则将横坐标加上一个单位。代码
# 如果游戏没有暂停,并且蛇没有死亡,则执行以下操作
if not pause and not dead:
# 计算时间
count_time += frame * level
# 获取蛇的头部位置
first = snake_list[0]
# 将蛇身的每个部分向前移动一个单位
snake_list[1:] = snake_list[:-1]
# 根据当前头部的方向更新蛇头的位置
if head == 'up':
snake_list[0] = [first[0], first[1] - SNAKE_WIDTH]
elif head == 'down':
snake_list[0] = [first[0], first[1] + SNAKE_WIDTH]
elif head == 'left':
snake_list[0] = [first[0] - SNAKE_WIDTH, first[1]]
elif head == 'right':
snake_list[0] = [first[0] + SNAKE_WIDTH, first[1]]
分析
负责更新游戏画面和处理用户输入,具体操作如下:
代码
# 绘制背景
draw_background()
# 绘制隧道
draw_wall()
# 选择物品
draw_snake()
# 绘制食物
draw_food()
# 绘制得分
draw_context()
# 如果游戏暂停且蛇没有死亡,绘制暂停界面
if not dead and pause:
draw_pause()
# 如果蛇死亡,绘制游戏结束界面
if dead:
draw_dead()
# 刷新显示
pygame.display.flip()
# 暂停 20 毫秒
pygame.time.delay(int(frame/level*1000))
# 检查是否死亡
dead = check_dead()
# 检查是否吃到食物
if check_food():
add_food()
# 关闭 pygame
pygame.quit()
需要源码留言