复杂贪吃蛇游戏的实现(python)---第二部分

上文

详细介绍了贪吃蛇游戏的基本规则、移动逻辑、食物生成机制、碰撞检测和游戏结束条件。通过分析关键的代码实现,如蛇的移动控制、食物的随机生成和边界处理,我们揭示了游戏背后的技术细节。文章还探讨了如何通过代码优化提升游戏性能和用户体验,为读者提供了一个全面的游戏开发视角。

让我们开始贪吃蛇游戏---第二部分:

        正文:

            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()

总结,本篇文章主要讲解了高级贪吃蛇的一些附加功能,哈哈

感谢大家的阅读,敬请期待第三部分吧!

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值