上文
详细介绍了贪吃蛇游戏的基本规则、移动逻辑、食物生成机制、碰撞检测和游戏结束条件。通过分析关键的代码实现,如蛇的移动控制、食物的随机生成和边界处理,我们揭示了游戏背后的技术细节。文章还探讨了如何通过代码优化提升游戏性能和用户体验,为读者提供了一个全面的游戏开发视角。
让我们开始贪吃蛇游戏---第二部分:
正文:
1.背景图片的动态添加与替换
a.描述如何使用Pygame加载和显示背景图片。
下面是图片按比例缩放的函数(因为素材的图片尺寸可能于游戏中窗口大小不同)和一些背景图片的导入
def scale_image(image, scale_factor):
pygame.init()
original_width, original_height = image.get_size()
new_width = int(original_width * scale_factor)
new_height = int(original_height * scale_factor)
scaled_image = pygame.transform.scale(image, (new_width, new_height))
return scaled_image
#加载背景图片
background_type = 0
background_mountion = pygame.image.load(os.path.join('image_bkg', 'background_1.png')).convert()
background_sea = pygame.image.load(os.path.join('image_bkg', 'background_2.png')).convert()
background_sand = pygame.image.load(os.path.join('image_bkg', 'background_3.png')).convert()
background_beach = pygame.image.load(os.path.join('image_bkg', 'background_4.png')).convert()
background_jungle = pygame.image.load(os.path.join('image_bkg', 'background_5.png')).convert()
background = [background_mountion, background_sea, background_sand, background_beach, background_jungle]
bkg = [scale_image(bkg,0.2) for bkg in background]
b.展示如何根据用户选择动态替换背景,增加游戏的可定制性。
def change_skin_backgound():
global background_type
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
for button in bkg_buttons:
if button.is_clicked(): # 检查是否点击了按钮
button.command() # 执行按钮的 command 函数
show_bkg(screen,is_small = True)
for button in bkg_buttons:
button.update(screen) # 更新按钮状态(选中或未选中)
global background_type
text = font_large.render("背景选择", True, BLUE)
screen.blit(text, (screen_width / 2 - text.get_width()/2,0))
pygame.display.flip()
2.难度选择机制
介绍难度选择对游戏速度的影响,以及如何通过界面供玩家选择不同难度级别。
展示难度级别对应的代码实现,以及如何根据选择调整游戏速度。
def easy_f():
SPEED = 10
print(SPEED)
def medium_f():
SPEED = 20
print(SPEED)
def hard_f():
SPEED = 30
print(SPEED)
def show_difficulty():
while True:
show_bkg(screen,background_type)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 遍历按钮,只在鼠标按下时检查是否点击了按钮
for button in difficulty_buttons:
if button.is_clicked() and event.button == 1: # 检查是否是左键
button.command()
for button in difficulty_buttons:
button.update(screen)
pygame.display.flip()
difficulty_buttons = [
Button(easy_button, easy_selected_button, (screen_width / 2 - easy_button.get_width() / 2, screen_height / 2 - easy_button.get_height()), easy_f),
Button(medium_button,medium_selected_button, (screen_width / 2- medium_button.get_width() / 2, screen_height / 2), medium_f),
Button(hard_button,hard_selected_button, (screen_width / 2- hard_button.get_width() / 2, screen_height / 2 + medium_button.get_height()), hard_f),
Button(exit_button,exit_selected_button, (screen_width / 2- exit_button.get_width() / 2, screen_height / 2 + medium_button.get_height() + exit_button.get_height()), main_menu)
]
3.历史分数统计
讨论历史分数统计对于玩家的意义,包括激励和回顾。
展示如何在游戏中实现分数的记录、保存到文件以及从文件读取。
以下是代码实现:
history_records = [
{'date': '2023-03-01', 'score': 120},
{'date': '2023-03-02', 'score': 150},
]
def save_score(score):
"""将分数添加到历史记录中。"""
try:
with open('scores.txt', 'a') as file:
file.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {score}\n")
except IOError:
print("Error saving score.")
def show_history():
"""从文件读取并显示历史分数,并按分数排序,支持滚动查看,且时间显示完整。"""
history_button = Button(exit_button, exit_selected_button, (screen_width - easy_button.get_width(), screen_height - exit_button.get_height()), main_menu)
try:
with open('scores.txt', 'r') as file:
lines = file.readlines()
scores = []
for line in lines:
score = int(line.strip().split(' : ')[-1]) # 提取分数并转换为整数
date = line.strip().split(' : ')[0] # 提取日期
scores.append((score, date))
# 分数排序
scores.sort(reverse=True)
# 滚动变量
scroll_offset = 0
max_displayed_scores = 15 # 屏幕上最多显示的分数数量
total_scores = len(scores)
score_height = font_mini.get_height() # 每行分数的高度
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
return
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if history_button.is_clicked():
history_button.command()
return
elif event.type == pygame.MOUSEWHEEL:
# 更新滚动位置,限制在范围内
scroll_offset -= event.y * score_height
scroll_offset = max(0, min(scroll_offset, (total_scores - max_displayed_scores) * score_height))
# 绘制背景
show_bkg(screen)
y = 100 # 开始绘制的y坐标
# 绘制分数
for i in range(max(0, scroll_offset // score_height), min(total_scores, scroll_offset // score_height + max_displayed_scores)):
score, date = scores[i]
# 调整字体大小以适应显示
text = font_mini.render(f"{score} - {date}", True, BLACK, WHITE)
screen.blit(text, [0, y])
y += text.get_height()
text = font_medium.render("历史记录", True, PURPLE)
screen.blit(text, [0, 0])
history_button.update(screen)
pygame.display.flip()
except IOError:
print("No scores to display.")
玩家可以通过滚轮查看自己的分数,且分数都是由高到低排序好的,储存在score.txt中!
废话不多说,上代码(snake.py)
def exit_f():
return 0
# 历史记录数据结构
history_records = [
{'date': '2023-03-01', 'score': 120},
{'date': '2023-03-02', 'score': 150},
]
# 难度选择界面函数
def show_difficulty():
while True:
show_bkg(screen,background_type)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 遍历按钮,只在鼠标按下时检查是否点击了按钮
for button in difficulty_buttons:
if button.is_clicked() and event.button == 1: # 检查是否是左键
button.command()
for button in difficulty_buttons:
button.update(screen)
pygame.display.flip()
def show_history():
"""从文件读取并显示历史分数,并按分数排序,支持滚动查看,且时间显示完整。"""
history_button = Button(exit_button, exit_selected_button, (screen_width - easy_button.get_width(), screen_height - exit_button.get_height()), main_menu)
try:
with open('scores.txt', 'r') as file:
lines = file.readlines()
scores = []
for line in lines:
score = int(line.strip().split(' : ')[-1]) # 提取分数并转换为整数
date = line.strip().split(' : ')[0] # 提取日期
scores.append((score, date))
# 分数排序
scores.sort(reverse=True)
# 滚动变量
scroll_offset = 0
max_displayed_scores = 15 # 屏幕上最多显示的分数数量
total_scores = len(scores)
score_height = font_mini.get_height() # 每行分数的高度
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
return
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if history_button.is_clicked():
history_button.command()
return
elif event.type == pygame.MOUSEWHEEL:
# 更新滚动位置,限制在范围内
scroll_offset -= event.y * score_height
scroll_offset = max(0, min(scroll_offset, (total_scores - max_displayed_scores) * score_height))
# 绘制背景
show_bkg(screen)
y = 100 # 开始绘制的y坐标
# 绘制分数
for i in range(max(0, scroll_offset // score_height), min(total_scores, scroll_offset // score_height + max_displayed_scores)):
score, date = scores[i]
# 调整字体大小以适应显示
text = font_mini.render(f"{score} - {date}", True, BLACK, WHITE)
screen.blit(text, [0, y])
y += text.get_height()
text = font_medium.render("历史记录", True, PURPLE)
screen.blit(text, [0, 0])
history_button.update(screen)
pygame.display.flip()
except IOError:
print("No scores to display.")
# 游戏区域参数
GAME_AREA_WIDTH = 600
GAME_AREA_HEIGHT = 600
game_area_x = (screen_width - GAME_AREA_WIDTH) // 2
game_area_y = (screen_height - GAME_AREA_HEIGHT) // 2
food = pygame.image.load('food.png')
# 创建一个字典存储不同方向的蛇头图像
snake_head_images = [pygame.image.load('snake_head_up.png'),
pygame.image.load('snake_head_down.png'),
pygame.image.load('snake_head_left.png'),
pygame.image.load('snake_head_right.png')]
snake_body_images = [pygame.image.load('snake_body.png')]
snake_tail_images = [pygame.image.load('snake_tail_up.png'),
pygame.image.load('snake_tail_down.png'),
pygame.image.load('snake_tail_left.png'),
pygame.image.load('snake_tail_right.png')]
is_grid = False
grid_color = WHITE
# 绘制游戏区域和网格
def draw_game_area():
if is_grid:
for x in range(game_area_x, game_area_x + GAME_AREA_WIDTH, SNAKE_SIZE):
pygame.draw.line(screen, grid_color, (x, game_area_y), (x, game_area_y + GAME_AREA_HEIGHT))
for y in range(game_area_y, game_area_y + GAME_AREA_HEIGHT, SNAKE_SIZE):
pygame.draw.line(screen, grid_color, (game_area_x, y), (game_area_x + GAME_AREA_WIDTH, y))
s=SNAKE_SIZE
pygame.draw.rect(screen, grid_color, pygame.Rect(game_area_x-s, game_area_y-s, GAME_AREA_WIDTH+2*s, GAME_AREA_HEIGHT+2*s), s)
def generate_food():
global food_pos
while food_pos is None or food_pos in snake_body:
food_pos = (random.randint(0, (screen_width-FOOD_SIZE)//SNAKE_SIZE)*SNAKE_SIZE,
random.randint(0, (screen_height-FOOD_SIZE)//SNAKE_SIZE)*SNAKE_SIZE)
DIRECTION_UP = 0
DIRECTION_DOWN = 1
DIRECTION_LEFT = 2
DIRECTION_RIGHT = 3
# 游戏主函数
def game():
global SPEED
# 蛇的位置和速度
score = 3
snake_pos = [game_area_x + 40, game_area_y + 40]
snake_body = [[game_area_x + 40, game_area_y + 40], [game_area_x + 20, game_area_y + 40], [game_area_x, game_area_y + 40]]
direction = 'RIGHT'
change_to = direction
# 食物的位置
food_pos = [random.randrange(game_area_x, game_area_x + GAME_AREA_WIDTH - SNAKE_SIZE, SNAKE_SIZE),
random.randrange(game_area_y, game_area_y + GAME_AREA_HEIGHT - SNAKE_SIZE, SNAKE_SIZE)]
food_spawn = True
# 游戏主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP or event.key == ord('w'):
change_to = 'UP'
if event.key == pygame.K_DOWN or event.key == ord('s'):
change_to = 'DOWN'
if event.key == pygame.K_LEFT or event.key == ord('a'):
change_to = 'LEFT'
if event.key == pygame.K_RIGHT or event.key == ord('d'):
change_to = 'RIGHT'
# 如果改变方向,则更新方向
if change_to == 'UP' and direction != 'DOWN':
direction = 'UP'
elif change_to == 'DOWN' and direction != 'UP':
direction = 'DOWN'
elif change_to == 'LEFT' and direction != 'RIGHT':
direction = 'LEFT'
elif change_to == 'RIGHT' and direction != 'LEFT':
direction = 'RIGHT'
# 根据方向移动蛇头
if direction == 'UP':
snake_pos[1] -= SNAKE_SIZE
if snake_pos[1] < game_area_y:
snake_pos[1] = game_area_y + GAME_AREA_HEIGHT - SNAKE_SIZE
elif direction == 'DOWN':
snake_pos[1] += SNAKE_SIZE
if snake_pos[1] >= game_area_y + GAME_AREA_HEIGHT:
snake_pos[1] = game_area_y
elif direction == 'LEFT':
snake_pos[0] -= SNAKE_SIZE
if snake_pos[0] < game_area_x:
snake_pos[0] = game_area_x + GAME_AREA_WIDTH - SNAKE_SIZE
elif direction == 'RIGHT':
snake_pos[0] += SNAKE_SIZE
if snake_pos[0] >= game_area_x + GAME_AREA_WIDTH:
snake_pos[0] = game_area_x
# 蛇的身体逻辑
snake_body.insert(0, list(snake_pos))
if snake_pos[0] == food_pos[0] and snake_pos[1] == food_pos[1]:
food_spawn = False
score += 1
else:
snake_body.pop()
# 食物重生
if not food_spawn:
food_pos = [random.randrange(game_area_x, game_area_x + GAME_AREA_WIDTH - 2*FOOD_SIZE, FOOD_SIZE),
random.randrange(game_area_y, game_area_y + GAME_AREA_HEIGHT - 2*FOOD_SIZE, FOOD_SIZE)]
food_spawn = True
# 清屏并重新绘制
show_bkg(screen)
draw_game_area()
# 绘制蛇
for i, segment in enumerate(snake_body):
if i == 0: # 头部
head_index = ['UP', 'DOWN', 'LEFT', 'RIGHT'].index(direction)
screen.blit(snake_head_images[head_index], (segment[0], segment[1]))
elif i < len(snake_body) - 1: # 身体
screen.blit(snake_body_images[0], (segment[0], segment[1]))
else: # 尾部
next_segment = snake_body[i - 1]
if segment[0] == next_segment[0]: # 垂直
tail_index = DIRECTION_UP if segment[1] < next_segment[1] else DIRECTION_DOWN
elif segment[1] == next_segment[1]: # 水平
tail_index = DIRECTION_LEFT if segment[0] < next_segment[0] else DIRECTION_RIGHT
screen.blit(snake_tail_images[tail_index], (segment[0], segment[1]))
# 绘制食物
screen.blit(food, (food_pos[0], food_pos[1]))
score_text = font_medium.render("分数: " + str(score), True, BLACK)
screen.blit(score_text, [0, 0])
# 刷新屏幕
pygame.display.flip()
clock.tick(SPEED)
# 检查蛇是否碰到了边界或自身
for block in snake_body[1:]:
if snake_pos[0] == block[0] and snake_pos[1] == block[1]:
return score
def main_menu():
"""显示主菜单并获取用户的选择。"""
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
for button in start_buttons:
if button.is_clicked(): # 检查是否点击了按钮
button.command() # 执行按钮的 command 函数
return # 如果点击了开始游戏按钮,执行游戏逻辑后返回
show_bkg(screen)
for button in start_buttons:
button.update(screen) # 更新按钮状态(选中或未选中)
text = font_large.render("贪吃蛇", True, BLACK)
screen.blit(text, (screen_width / 2 - text.get_width() / 2, 100))
pygame.display.flip()
# main_start_f 函数是 start_buttons 中开始按钮的 command 函数
def main_start_f():
final_score = game() # 启动游戏
show_score(final_score) # 显示分数
save_score(final_score) # 保存分数
main_menu() # 游戏结束后返回主菜单
def change_skin_backgound():
global background_type
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
for button in bkg_buttons:
if button.is_clicked(): # 检查是否点击了按钮
button.command() # 执行按钮的 command 函数
show_bkg(screen,is_small = True)
for button in bkg_buttons:
button.update(screen) # 更新按钮状态(选中或未选中)
global background_type
text = font_large.render("背景选择", True, BLUE)
screen.blit(text, (screen_width / 2 - text.get_width()/2,0))
pygame.display.flip()
def show_setting():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
for button in setting_buttons:
if button.is_clicked(): # 检查是否点击了按钮
button.command() # 执行按钮的 command 函数
show_bkg(screen)
for button in setting_buttons:
button.update(screen) # 更新按钮状态(选中或未选中)
i = 0
y = 0
for text in setting_text:
screen.blit(text, (0, y))
i+=1
y+=i*text.get_height()
set_border(screen, BLACK, [0,40], [200,400], 128)
pygame.display.flip()
start_buttons = [
Button(start_button, start_selected_button, (screen_width / 2 - start_button.get_width()/2, screen_height / 2 - difficulty_button.get_height()), main_start_f),
Button(difficulty_button, difficulty_selected_button, (screen_width / 2- medium_button.get_width()/2, screen_height / 2), show_difficulty),
Button(history_button, history_selected_button, (screen_width / 2- history_button.get_width()/2, screen_height / 2 + difficulty_button.get_height()), show_history),
Button(bkg_button, bkg_selected_button, (screen_width- bkg_button.get_width(), screen_height / 2 + bkg_button.get_height()), change_skin_backgound),
Button(setting_button, setting_selected_button, (screen_width- setting_button.get_width(), screen_height / 2), show_setting),
Button(exit_button, exit_selected_button, (screen_width / 2.- exit_button.get_width()/2, screen_height / 2 + difficulty_button.get_height() * 2),quit)
]
difficulty_buttons = [
Button(easy_button, easy_selected_button, (screen_width / 2 - easy_button.get_width() / 2, screen_height / 2 - easy_button.get_height()), easy_f),
Button(medium_button,medium_selected_button, (screen_width / 2- medium_button.get_width() / 2, screen_height / 2), medium_f),
Button(hard_button,hard_selected_button, (screen_width / 2- hard_button.get_width() / 2, screen_height / 2 + medium_button.get_height()), hard_f),
Button(exit_button,exit_selected_button, (screen_width / 2- exit_button.get_width() / 2, screen_height / 2 + medium_button.get_height() + exit_button.get_height()), main_menu)
]
bkg_buttons = [
Button(next_button, next_selected_button, (screen_width / 2 + bkg[2].get_width()/2,screen_height/2-next_button.get_height()/2), bkg_n),
Button(previous_button, previous_selected_button, (screen_width / 2 - bkg[2].get_width()/2 - previous_button.get_width(), screen_height/2-previous_button.get_height()/2), bkg_p),
Button(exit_button, exit_selected_button, (screen_width - exit_button.get_width(), screen_height- exit_button.get_height()),main_menu)
]
setting_buttons = [
Button(exit_button, exit_selected_button, (screen_width - exit_button.get_width(), screen_height- exit_button.get_height()),main_menu)
]
setting_text = [
font_large.render("设置", True, BLACK),
font_small.render("1.是否显示网格", True, BLACK),
font_small.render("2.设置边框、网格颜色:", True, grid_color),
font_small.render("3.设置", True, BLACK)
]
def show_score(score):
"""显示游戏结束画面和分数。"""
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
return
show_bkg(screen,background_type)
text_1 = font_small.render(f"你撞到了自己!你的分数: {score}", True, BLACK)
text_2 = font_small.render(f"按下enter再试一次", True, BLACK)
screen.blit(text_1, [screen_width / 2 - text_1.get_width() / 2, screen_height / 2 - text_1.get_height() / 2])
screen.blit(text_2, [screen_width / 2 - text_2.get_width() / 2, screen_height / 2 - text_2.get_height() / 2 - 50])
pygame.display.flip()
def save_score(score):
"""将分数添加到历史记录中。"""
try:
with open('scores.txt', 'a') as file:
file.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {score}\n")
except IOError:
print("Error saving score.")
if __name__ == '__main__':
main_menu()
总结,本篇文章主要讲解了高级贪吃蛇的一些附加功能,哈哈
感谢大家的阅读,敬请期待第三部分吧!