You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pygame贪吃蛇:如何实现每帧仅处理一次转向按键?

贪吃蛇单帧仅处理一次转向的实现优化

核心思路

通过维护一个按键事件队列,每帧只处理队列中的第一个有效转向按键,剩余按键留到后续帧依次处理,从根本上避免同一帧内多次转向导致的撞身问题。同时优化原代码中的事件循环、方向判断等细节问题。

改进后的完整代码

import pygame
import sys
import random

# 定义蛇身节点类
class Body:
    def __init__(self, properties=(0, 0, 20, 20), colour=-1):
        self.properties = properties
        self.colour = colour
        self.next = None

# 定义果实类
class Fruit:
    def __init__(self, centre=(0, 0), size=10):
        self.centre = centre
        self.size = size

def draw_snake(window, snake):
    # 绘制蛇头
    pygame.draw.rect(window, (0, 0, 0), snake.properties)
    snake.colour *= -1
    temp = snake.next
    # 绘制蛇身,交替颜色
    while temp:
        colour = (0, 150, 0) if temp.colour == -1 else (0, 100, 0)
        temp.colour *= -1
        pygame.draw.rect(window, colour, temp.properties)
        temp = temp.next
    return snake

def draw_apple(window, snake, size):
    grid_size = size * 2
    max_pos = (500 // grid_size) - 1
    # 生成在网格上的果实位置
    while True:
        x = random.randint(0, max_pos) * grid_size + size
        y = random.randint(0, max_pos) * grid_size + size
        # 检查是否生成在蛇身上
        temp = snake
        overlap = False
        while temp:
            if temp.properties[0] == x - size and temp.properties[1] == y - size:
                overlap = True
                break
            temp = temp.next
        if not overlap:
            break
    apple = Fruit((x, y), size)
    pygame.draw.circle(window, "red", apple.centre, apple.size)
    return apple

def new_game(window):
    window.fill((255, 255, 255))
    snake = Body((240, 240, 20, 20), -1)
    draw_snake(window, snake)
    apple = draw_apple(window, snake, 10)
    return snake, apple

def die(window, snake):
    # 蛇头变红提示死亡
    pygame.draw.rect(window, (180, 0, 0), snake.properties)
    pygame.display.update()
    pygame.time.wait(1000)

def get_new_direction(current_dir, key):
    # 用元组存储方向,避免列表引用比较的问题
    if key == pygame.K_w and current_dir != (0, 20):
        return (0, -20)
    elif key == pygame.K_a and current_dir != (20, 0):
        return (-20, 0)
    elif key == pygame.K_s and current_dir != (0, -20):
        return (0, 20)
    elif key == pygame.K_d and current_dir != (-20, 0):
        return (20, 0)
    # 无效按键,返回原方向
    return current_dir

def move_snake(window, snake, apple, direction, length):
    head_x, head_y, _, _ = snake.properties
    new_head_x = head_x + direction[0]
    new_head_y = head_y + direction[1]

    # 边界碰撞检测
    if new_head_x < 0 or new_head_y < 0 or new_head_x > 480 or new_head_y > 480:
        die(window, snake)
        return new_game(window) + (0, True)

    # 创建新蛇头
    new_head = Body((new_head_x, new_head_y, 20, 20), snake.colour * -1)
    new_head.next = snake
    snake = new_head

    # 检查是否吃到果实
    if (new_head_x, new_head_y) == (apple.centre[0] - 10, apple.centre[1] - 10):
        length += 1
        apple = draw_apple(window, snake, 10)
    else:
        # 检查蛇身碰撞
        temp = snake.next
        while temp.next:
            if (temp.next.properties[0], temp.next.properties[1]) == (new_head_x, new_head_y):
                die(window, snake)
                return new_game(window) + (0, True)
            temp = temp.next
        # 删除蛇尾
        pygame.draw.rect(window, "white", temp.properties)
        temp.next = None

    return snake, apple, length, False

def main():
    pygame.init()
    window = pygame.display.set_mode((500, 500))
    pygame.display.set_caption("贪吃蛇")

    snake, apple = new_game(window)
    length = 0
    clock = pygame.time.Clock()
    game_started = False
    direction = (0, 0)
    key_queue = []  # 按键队列,存储未处理的转向按键

    while True:
        # 处理所有事件,将有效转向按键加入队列
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if not game_started:
                    # 第一次按键开始游戏,初始化方向
                    direction = get_new_direction((0,0), event.key)
                    game_started = True
                else:
                    # 仅将方向键加入队列
                    if event.key in [pygame.K_w, pygame.K_a, pygame.K_s, pygame.K_d]:
                        key_queue.append(event.key)

        if game_started:
            # 每帧只处理队列中的第一个有效按键
            if key_queue:
                direction = get_new_direction(direction, key_queue.pop(0))

            # 移动蛇并更新画面
            snake, apple, length, game_over = move_snake(window, snake, apple, direction, length)
            if game_over:
                game_started = False
                direction = (0,0)
                key_queue.clear()
            draw_snake(window, snake)
            pygame.display.update()

        # 控制游戏帧率(建议10-15帧,原1帧太慢)
        clock.tick(10)

if __name__ == "__main__":
    main()

关键改进点说明

  • 按键队列机制:用key_queue存储玩家按下的方向键,每帧只取出队列头部的一个按键处理,剩余按键留到后续帧,确保单帧仅一次转向。
  • 方向存储优化:用元组代替列表存储方向,避免引用类型比较的潜在问题,方向判断逻辑更可靠。
  • 事件循环重构:改成单循环结构,避免嵌套循环导致的事件堆积问题,游戏状态控制更清晰。
  • 帧率调整:将帧率从1调整为10,游戏体验更流畅,同时不影响转向逻辑。
  • 果实生成优化:新增果实与蛇身的碰撞检测,避免果实生成在蛇身上。

内容的提问来源于stack exchange,提问作者DumDog6

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 22:16:31