使用300行实现贪吃蛇的简单游戏(详细教程)
上效果图:
简单介绍
编译器:
PyCharm
环境:
python 3.7
使用的库:
Pygame,需要自己安装,或者在PyCharm中自动安装。
模式介绍:
包含简单模式和普通模式。
简单模式:只有触碰到墙体才判断游戏失败,吃到自己的身体则会丢失后面一节(这个可能有点小问题)
普通模式:触碰到墙体或者自己的身体判断游戏失败
游戏规则:
这个就不用详细介绍了吧,通过移动去拾取食物从而增加自己的长度,由于时间紧迫没有设置分数之类的,游戏只有失败没有成功-----“狗头”
游戏使用的资源(图片和音频)
头部图片
身体和食物图片
尾部图片
失败的图片
背景音效
失败音效
吃到食物音效
关于蛇和食物的设计:
这两个部分都是通过一个列表去存储坐标然后进行绘画出来的。碰撞也是根据两个列表是否有重复的坐标进行判断的。
# 储存龙的位置, 并初始位置
self.dragon_location = [(25, 20), (25, 21), (25, 22)]
# 火球位置
self.ball_location = [(30, 10)]
废话说完了(小二)上代码:
导包
import pygame
import sys
import os
import random
初始化窗口
# 初始化并创建一个屏幕对象
pygame.init()
# 设置窗体位置
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d, %d" % (300, 50)
# 引入全局配置设置
settings = Settings()
# 初始化屏幕
screen = pygame.display.set_mode((settings.screen_with, settings.screen_height))
# 设置游戏名
pygame.display.set_caption("贪吃(龙王)")
# 创建开始按钮
play_button = Button(screen, settings, 'PLAY')
# 初始化Clock
my_clock = pygame.time.Clock()
# 创建火球
ball = Ball(screen)
# 创建龙
dragon = Dragon(screen, ball)
pygame.mixer.init()
# 加载背景音乐
sound = pygame.mixer.Sound('music/background.wav')
sound.play(-1)
注意:这里加载音效的时候只能用Sound并且是wav格式的音频,不使用wav的格式会报错!
全局配置类(配置了游戏的基本设置)
class Settings:
"""游戏的全局配置类"""
def __init__(self):
"""初始化游戏"""
# 屏幕宽高
self.screen_with = 1000
self.screen_height = 800
# 背景颜色
self.bg_color = (255, 255, 255)
# 帧数
self.FPS = 10
# 游戏是否结束
self.fail = True
# 判断是否是第一次打开窗口,
self.flag = True
# 是否是简单模式
self.isSimple = False
初始化食物的位置和绘制食物
这里火球根据判断蛇头部和食物两个列表中坐标是否重复来判断是否被吃掉,如果吃掉了就随机在窗口内重置位置。
class Ball:
"""火球设置"""
def __init__(self, screen):
self.screen = screen
# 火球图片
self.ball_image = pygame.image.load('images/ball.png')
# 火球位置
self.ball_location = [(30, 10)]
# 火球是否被吃了
self.flag = False
# 每格大小
self.cell = 20
# 绘制火球
def drawing(self):
width = self.ball_location[0][0] * self.cell
height = self.ball_location[0][1] * self.cell
# 绘制位置
rect = pygame.Rect(width, height, self.cell, self.cell)
# 火球位置
self.screen.blit(self.ball_image, rect)
# 火球被吃了更新
def update(self):
location_x = random.randint(0, 49)
location_y = random.randint(0, 39)
self.ball_location.insert(0, (location_x, location_y))
self.flag = False
初始化蛇的位置,移动和绘制
initialize函数是为了游戏结束后可以调用并且初始化位置和方向信息。
蛇因为使用三张图片进行绘制的,所有分为头,身体和尾部。
而方向为用UP DOWN LEFT TIGHT进行区分。
class Dragon:
"""龙的设置"""
def __init__(self, screen, ball):
self.screen = screen
# 火球
self.ball = ball
# 龙的速度
self.dragon_speed = 10
# 龙头的图片
self.dragon_image = pygame.image.load('images/dragon.png')
# 龙身体图片
self.dragon_body = pygame.image.load('images/ball.png')
# 龙尾部图片
self.dragon_tail = pygame.image.load('images/tail.png')
# 每格大小
self.cell = 20
def initialize(self):
# 龙的方向 UP DOWN LEFT TIGHT
self.dragon_direction = 'UP'
# 储存龙的位置, 并初始位置
self.dragon_location = [(25, 20), (25, 21), (25, 22)]
键盘的控制
本游戏使用,上下左右键(非WSAD)进行控制,如果自己需要自动更改键值就好了。通过点击窗口的关闭进行游戏退出。上下左右只是改变了蛇的方向,具体的渲染在蛇的类里面。
elif event.type == pygame.MOUSEBUTTONDOWN:
这个下面是判断按钮的点击,判断是简单模式还是普通模式,从而对相应的参数进行初始化。
至于按钮的绘制后面直接放到源码里面吧。
# 监视键盘事件
for event in pygame.event.get():
# 按下窗体关闭键退出游戏
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and dragon.dragon_direction != 'DOWN':
dragon.dragon_direction = 'UP'
elif event.key == pygame.K_DOWN and dragon.dragon_direction != 'UP':
dragon.dragon_direction = 'DOWN'
elif event.key == pygame.K_LEFT and dragon.dragon_direction != 'RIGHT':
dragon.dragon_direction = 'LEFT'
elif event.key == pygame.K_RIGHT and dragon.dragon_direction != 'LEFT':
dragon.dragon_direction = 'RIGHT'
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
# 点击开始游戏
# 简单模式被点击
simple_button_click = play_button.rect.collidepoint(mouse_x, mouse_y)
# 普通模式被点击
ordinary_button_click = play_button.rect_ordinary.collidepoint(mouse_x, mouse_y)
# 如果简单模式被点击则设置 isSimple 为True
settings.isSimple = True if simple_button_click else False
print(settings.isSimple)
if (simple_button_click or ordinary_button_click) and settings.fail:
play_button.is_show = False
settings.fail = False
settings.flag = False
# 初始化数据
dragon.initialize()
绘制蛇
通过判断蛇列表的下标从而进行相应的绘画,下标为0则是头部,为最后一个则是尾部,其他的都是身体你。绘画的每个格子的大小看自己的窗口大小从而进行改变就好了。
我这里的时1000*800把每个格子分为了20,所有食物和蛇的坐标移动范围是在 X轴(0, 49)Y轴(0, 39)之间
# 绘画龙
def drawing(self):
dragon_list = self.dragon_location
for i in dragon_list:
# 龙的长度坐标
index = dragon_list.index(i)
# 龙的位置
width = i[0] * self.cell
height = i[1] * self.cell
# 尾部下标
index_tail = len(dragon_list) - 1
# 绘制位置
rect = pygame.Rect(width, height, self.cell, self.cell)
# 龙头位置
if index == 0:
self.screen.blit(self.dragon_image, rect)
# 龙尾巴位置
elif index == index_tail:
self.screen.blit(self.dragon_tail, rect)
# 龙身体位置
else:
self.screen.blit(self.dragon_body, rect)
蛇的移动
移动方法:通过在蛇的列表的头部进行增加下一个位置的坐标。例如当前列表为 [(25, 20), (25, 21), (25, 22)],而向上移动则变为[(25, 19), (25, 20), (25, 21)]
注意:如果没有吃到食物要将最后一个坐标进行删除,不然的话每次刷新都会自动增加一节。这样岂不是就无敌了。
# 火球没有被吃 去掉后面一节
if not self.ball.flag:
self.dragon_location.pop()
# 移动
def move(self):
# x,y坐标
location_x = self.dragon_location[0][0]
location_y = self.dragon_location[0][1]
if self.dragon_direction == 'UP':
self.dragon_location.insert(0, (location_x, location_y - 1))
elif self.dragon_direction == 'DOWN':
self.dragon_location.insert(0, (location_x, location_y + 1))
elif self.dragon_direction == 'RIGHT':
self.dragon_location.insert(0, (location_x + 1, location_y))
elif self.dragon_direction == 'LEFT':
self.dragon_location.insert(0, (location_x - 1, location_y))
# 火球没有被吃 去掉后面一节
if not self.ball.flag:
self.dragon_location.pop()
碰撞检测—超出边缘
通过蛇头的坐标和边缘进行比较,从而判断是否超出边缘。
这里的49,39 都是根据自己设置的窗体大小/每个的大小得出的(1000/20 = 50),因为从0开始所有是49
# 龙超出窗体
def beyond_form(settings, dragon, button):
if dragon.dragon_location[0][0] > 49 or dragon.dragon_location[0][0] < 0 or dragon.dragon_location[0][1] > 39 or \
dragon.dragon_location[0][1] < 0:
death(settings, button)
碰撞检测—吃到食物
使用蛇与食物的列表进行判断,collisions 是新的列表如果有相同的坐标则这个列表的长度为1。
然后播放音效和相应的操作(重置食物位置)
注意:这里的播放音效的要和背景音效的区分这里使用pygame.mixer.music.load
# 龙与球碰撞
def collision(dragon, ball):
collisions = [x for x in dragon.dragon_location if x in ball.ball_location]
if len(collisions) == 1:
pygame.mixer.music.load('music/eat.mp3')
pygame.mixer.music.play()
ball.ball_location = []
ball.flag = True
碰撞检测—碰到自己的身体
这里就是两种模式的区分。两种模式主要在这里进行判断。
这里用了两种判断方法去判断是否重复(两种方法作用一样的):
collisions = [x for x in dragon.dragon_location if x in new_location]
def get_same_element_index(ob_list, word):
return [i for (i, v) in enumerate(ob_list) if v == word]
在普通模式如果触碰了直接触发失败函数,而简单模式则是把后面的一段进行截取。
# 龙触碰到自己
def touch_self(dragon, settings, button):
# 普通模式 吃到自己就死亡
new_location = [dragon.dragon_location[0]]
collisions = [x for x in dragon.dragon_location if x in new_location]
if len(collisions) > 1 and not settings.isSimple:
death(settings, button)
# 获取头部在龙列表中的索引 如果超过两个则从第二个位置进行截取
index = get_same_element_index(dragon.dragon_location, (new_location[0][0], new_location[0][1]))
print(index)
# 简单模式 吃到自己则会失去后面的部分
if len(index) >= 2 and settings.isSimple:
print(dragon.dragon_location[0:index[1]])
# 将截取的列表赋值到原有的列表中
dragon.dragon_location = dragon.dragon_location[0:index[1]]
def get_same_element_index(ob_list, word):
return [i for (i, v) in enumerate(ob_list) if v == word]
基本就这样的了如果还有不懂的可以评论区讨论下哈
还有就是游戏的速度可以在全局设置那里设置帧数,bug这东西就不要说了,谁还没有点bug呢
上源码
import pygame
import sys
import os
import random
# 游戏主函数
def run_game():
# 初始化并创建一个屏幕对象
pygame.init()
# 设置窗体位置
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d, %d" % (300, 50)
# 引入全局配置设置
settings = Settings()
# 初始化屏幕
screen = pygame.display.set_mode((settings.screen_with, settings.screen_height))
# 设置游戏名
pygame.display.set_caption("贪吃(龙王)")
# 创建开始按钮
play_button = Button(screen, settings, 'PLAY')
# 初始化Clock
my_clock = pygame.time.Clock()
# 创建火球
ball = Ball(screen)
# 创建龙
dragon = Dragon(screen, ball)
pygame.mixer.init()
# 加载背景音乐
sound = pygame.mixer.Sound('music/background.wav')
sound.play(-1)
while True:
# 设置屏幕背景颜色
screen.fill(settings.bg_color)
# 监视键盘事件
for event in pygame.event.get():
# 按下窗体关闭键退出游戏
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and dragon.dragon_direction != 'DOWN':
dragon.dragon_direction = 'UP'
elif event.key == pygame.K_DOWN and dragon.dragon_direction != 'UP':
dragon.dragon_direction = 'DOWN'
elif event.key == pygame.K_LEFT and dragon.dragon_direction != 'RIGHT':
dragon.dragon_direction = 'LEFT'
elif event.key == pygame.K_RIGHT and dragon.dragon_direction != 'LEFT':
dragon.dragon_direction = 'RIGHT'
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
# 点击开始游戏
# 简单模式被点击
simple_button_click = play_button.rect.collidepoint(mouse_x, mouse_y)
# 普通模式被点击
ordinary_button_click = play_button.rect_ordinary.collidepoint(mouse_x, mouse_y)
# 如果简单模式被点击则设置 isSimple 为True
settings.isSimple = True if simple_button_click else False
print(settings.isSimple)
if (simple_button_click or ordinary_button_click) and settings.fail:
play_button.is_show = False
settings.fail = False
settings.flag = False
# 初始化数据
dragon.initialize()
if not settings.fail:
# 绘制火球
ball.drawing()
# 绘制龙
dragon.drawing()
# 龙吃到火球
collision(dragon, ball)
# 龙吃到自己
touch_self(dragon, settings, play_button)
# 龙超出边界
beyond_form(settings, dragon, play_button)
# 龙移动
dragon.move()
if ball.flag:
ball.update()
# 绘制按钮
play_button.draw_button()
# 让最近绘制的屏幕可见
pygame.display.flip()
my_clock.tick(settings.FPS)
# 龙与球碰撞
def collision(dragon, ball):
collisions = [x for x in dragon.dragon_location if x in ball.ball_location]
if len(collisions) == 1:
pygame.mixer.music.load('music/eat.mp3')
pygame.mixer.music.play()
ball.ball_location = []
ball.flag = True
# 龙触碰到自己
def touch_self(dragon, settings, button):
# 普通模式 吃到自己就死亡
new_location = [dragon.dragon_location[0]]
collisions = [x for x in dragon.dragon_location if x in new_location]
if len(collisions) > 1 and not settings.isSimple:
death(settings, button)
# 获取头部在龙列表中的索引 如果超过两个则从第二个位置进行截取
index = get_same_element_index(dragon.dragon_location, (new_location[0][0], new_location[0][1]))
print(index)
# 简单模式 吃到自己则会失去后面的部分
if len(index) >= 2 and settings.isSimple:
print(dragon.dragon_location[0:index[1]])
# 将截取的列表赋值到原有的列表中
dragon.dragon_location = dragon.dragon_location[0:index[1]]
def get_same_element_index(ob_list, word):
return [i for (i, v) in enumerate(ob_list) if v == word]
# 龙超出窗体
def beyond_form(settings, dragon, button):
if dragon.dragon_location[0][0] > 49 or dragon.dragon_location[0][0] < 0 or dragon.dragon_location[0][1] > 39 or \
dragon.dragon_location[0][1] < 0:
death(settings, button)
def death(settings, button):
pygame.mixer.music.load('music/death.mp3')
pygame.mixer.music.play()
settings.fail = True
# 显示按钮
button.is_show = True
class Settings:
"""游戏的全局配置类"""
def __init__(self):
"""初始化游戏"""
# 屏幕宽高
self.screen_with = 1000
self.screen_height = 800
# 背景颜色
self.bg_color = (255, 255, 255)
# 帧数
self.FPS = 10
# 游戏是否结束
self.fail = True
# 判断是否是第一次打开窗口,
self.flag = True
# 是否是简单模式
self.isSimple = False
class Ball:
"""火球设置"""
def __init__(self, screen):
self.screen = screen
# 火球图片
self.ball_image = pygame.image.load('images/ball.png')
# 火球位置
self.ball_location = [(30, 10)]
# 火球是否被吃了
self.flag = False
# 每格大小
self.cell = 20
# 绘制火球
def drawing(self):
width = self.ball_location[0][0] * self.cell
height = self.ball_location[0][1] * self.cell
# 绘制位置
rect = pygame.Rect(width, height, self.cell, self.cell)
# 火球位置
self.screen.blit(self.ball_image, rect)
# 火球被吃了更新
def update(self):
location_x = random.randint(0, 49)
location_y = random.randint(0, 39)
self.ball_location.insert(0, (location_x, location_y))
self.flag = False
class Dragon:
"""龙的设置"""
def __init__(self, screen, ball):
self.screen = screen
# 火球
self.ball = ball
# 龙的速度
self.dragon_speed = 10
# 龙头的图片
self.dragon_image = pygame.image.load('images/dragon.png')
# 龙身体图片
self.dragon_body = pygame.image.load('images/ball.png')
# 龙尾部图片
self.dragon_tail = pygame.image.load('images/tail.png')
# 每格大小
self.cell = 20
def initialize(self):
# 龙的方向 UP DOWN LEFT TIGHT
self.dragon_direction = 'UP'
# 储存龙的位置, 并初始位置
self.dragon_location = [(25, 20), (25, 21), (25, 22)]
# 绘画龙
def drawing(self):
dragon_list = self.dragon_location
for i in dragon_list:
# 龙的长度坐标
index = dragon_list.index(i)
# 龙的位置
width = i[0] * self.cell
height = i[1] * self.cell
# 尾部下标
index_tail = len(dragon_list) - 1
# 绘制位置
rect = pygame.Rect(width, height, self.cell, self.cell)
# 龙头位置
if index == 0:
self.screen.blit(self.dragon_image, rect)
# 龙尾巴位置
elif index == index_tail:
self.screen.blit(self.dragon_tail, rect)
# 龙身体位置
else:
self.screen.blit(self.dragon_body, rect)
# 移动
def move(self):
# x,y坐标
location_x = self.dragon_location[0][0]
location_y = self.dragon_location[0][1]
if self.dragon_direction == 'UP':
self.dragon_location.insert(0, (location_x, location_y - 1))
elif self.dragon_direction == 'DOWN':
self.dragon_location.insert(0, (location_x, location_y + 1))
elif self.dragon_direction == 'RIGHT':
self.dragon_location.insert(0, (location_x + 1, location_y))
elif self.dragon_direction == 'LEFT':
self.dragon_location.insert(0, (location_x - 1, location_y))
# 火球没有被吃 去掉后面一节
if not self.ball.flag:
self.dragon_location.pop()
# Button 按钮
class Button:
def __init__(self, screen, settings, msg):
"""初始化按钮属性"""
self.screen = screen
self.settings = settings
self.screen_rect = screen.get_rect()
# 按钮宽高
self.width, self.height = 200, 50
# 背景颜色
self.button_color = (0, 255, 255)
# 字体颜色
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
self.rect = pygame.Rect(100, 200, self.width, self.height)
self.rect.center = self.screen_rect.center
# 普通模式按钮
self.rect_ordinary = pygame.Rect(0, 0, self.width, self.height)
# 设置中间 + y轴100像素
self.rect_ordinary.centery = self.screen_rect.centery + 100
self.rect_ordinary.centerx = self.screen_rect.centerx
# 背景颜色
self.button_color_ordinary = (0, 250, 154)
# 字体颜色
self.text_color_ordinary = (255, 255, 255)
# 失败的时候显示失败的背景图片
self.is_show = True
# 简单按钮标签
self.prep_msg(msg)
# 普通模式按钮标签
self.prep_msg_ordinary()
# 简单模式
def prep_msg(self, msg):
"""将msg渲染后成图像并放到按钮中间"""
self.msg_image = self.font.render('Play Simple', True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.screen_rect.center
# 普通模式的按钮渲染
def prep_msg_ordinary(self):
"""将msg渲染后成图像并放到按钮中间"""
self.msg_image_ordinary = self.font.render('Play Normal', True, self.text_color_ordinary,
self.button_color_ordinary)
self.msg_image_rect_ordinary = self.msg_image_ordinary.get_rect()
self.msg_image_rect_ordinary.centery = self.screen_rect.centery + 100
self.msg_image_rect_ordinary.centerx = self.screen_rect.centerx
def draw_button(self):
if self.is_show:
# 绘制按钮
if self.settings.fail and not self.settings.flag:
# 游戏结束
image = pygame.image.load('images/gameover.png')
image_rect = image.get_rect()
image_rect.centerx = self.screen_rect.centerx
self.screen.blit(image, image_rect)
# 简单模式按钮
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
# 普通模式按钮
self.screen.fill(self.button_color_ordinary, self.rect_ordinary)
self.screen.blit(self.msg_image_ordinary, self.msg_image_rect_ordinary)
# 运行主函数
run_game()