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

Pygame蛇形游戏无用户输入时主循环阻塞问题求助

蛇形游戏主循环冻结问题排查与修复

问题描述

我用Python+Pygame编写了一款蛇形游戏原型,在手机Pydroid和在线解释器中测试时,游戏仅运行2次迭代后就冻结,只有触摸屏幕(触发事件)时才会恢复运行。

原代码

主游戏文件

import sys
import pygame
from pygame.locals import *

from libs.Touch import Touch

def sortPairs(*pairs):
    result = {}
    for pair in pairs:
        result[pair[0]] = pair[1]
        result[pair[1]] = pair[0]
    return result

DIR = {
    'LEFT':  (-1, +0),
    'RIGHT': (+1, +0),
    'UP':    (+0, -1),
    'DOWN':  (+0, +1)
}
DIR['INVERSE'] = sortPairs(
    (DIR['LEFT'], DIR['RIGHT']),
    (DIR['UP'],   DIR['DOWN']))

class Snake:
    def __init__(self,x, y):
        self.dir   = DIR['RIGHT']
        self.nodes = [[x, y]]
    
    def move(self):
        for i in range(len(self.nodes) - 1, 0, -1):
            self.nodes[i][0] = self.nodes[i - 1][0]
            self.nodes[i][1] = self.nodes[i - 1][1]
            
        self.nodes[0][0] += self.dir[0]
        self.nodes[0][1] += self.dir[1]
    
    def turn(self, turn):
        dir = DIR[turn]
        if len(self.nodes) == 1 or dir != DIR['INVERSE'][turn]:
            self.dir = dir
    
    def grow(self):
        self.nodes.appendLeft([
            self.nodes[0][0] + self.dir[0],
            self.nodes[0][1] + self.dir[1]])
    
    def draw(self, surface,x, y, node_size):
        rect = pygame.Rect(
            0, 0, node_size, node_size)
        
        for node in self.nodes:
            rect.left   = x + node[0] * node_size
            rect.top    = y + node[1] * node_size
            rect.right  = rect.left + node_size     
            rect.bottom = rect.top + node_size
            
            pygame.draw.rect(
                surface,
                pygame.Color("#FF0000"),
                rect)

class Game:
    def __init__(self, size, tick):
        self.size = size
        self.tick = tick
        self.snake = Snake(0, 0)
        
    def draw(self, surface):
        surface.fill(pygame.Color('#161616'))
        
        cell_size = min(
            surface.get_width(),
            surface.get_height()) / self.size
        
        bounds_stroke = int(cell_size / 10)
        
        bounds = pygame.Rect(
            surface.get_width() / 2,
            surface.get_height() / 2,
            cell_size * self.size - bounds_stroke,
            cell_size * self.size - bounds_stroke)
        bounds.center = (bounds.x, bounds.y)
        
        self.snake.draw(
            surface,
            int(bounds.left),
            int(bounds.top),
            int(cell_size))
        
        pygame.draw.rect(
            surface,
            pygame.Color('#6f91b3'),
            bounds,
            bounds_stroke)
        
    def update(self):
        self.snake.move()
    
    def handle_event(self, event):
        if event.type == MOUSEBUTTONDOWN:
            pygame.mouse.get_rel()
        if event.type == MOUSEBUTTONUP:
            swipe = Touch.reg_swipe(*pygame.mouse.get_rel())
            if swipe != -1:
                self.snake.turn(swipe)
            
        if event.type == QUIT:
            pygame.quit()

def main():
    game = Game(30, 100)
    
    pygame.init()
    
    # Resolution is ignored on Android
    surface = pygame.display.set_mode((640, 480))
    
    clock = pygame.time.Clock()
    
    delta = 0
    
    while True:
        for ev in pygame.event.get():
            game.handle_event(ev)
            
            delta += clock.tick(60)
            
            if delta >= game.tick:
                game.update()
                delta = 0
            
            game.draw(surface)
            pygame.display.flip()

if __name__ == '__main__':
    main()

./libs/Touch.py

class Touch:
    max_touch_dist = 15
    min_swipe_dist = 50
    
    @staticmethod
    def reg_swipe(rel_x, rel_y):
        min = Touch.min_swipe_dist
        if abs(rel_y) < min:
            if rel_x <= -min:
                return 'LEFT'
            elif rel_x >= min:
                return 'RIGHT'
        if rel_y <= -min:
            return 'UP'
        elif rel_y >= min:
            return 'DOWN'
            
        return -1

问题根源

核心问题是主循环的逻辑缩进错误:
所有游戏核心逻辑(计时累加、状态更新、画面绘制、屏幕刷新)都被嵌套在了for ev in pygame.event.get()的事件循环内部。这意味着只有当有事件触发时(比如触摸、点击、窗口事件),这些逻辑才会执行;当没有事件发生时,游戏就会停止所有更新和绘制,表现为冻结状态。

Pygame的主循环正确结构应该是:

  1. 处理所有事件(在事件循环内)
  2. 执行游戏状态更新、计时、绘制(在事件循环外,主循环的每一轮迭代都要执行)

修复方案

1. 修正主循环缩进

将计时、更新、绘制逻辑移出事件循环,放到主循环的顶层:

def main():
    game = Game(30, 100)
    
    pygame.init()
    
    # Resolution is ignored on Android
    surface = pygame.display.set_mode((640, 480))
    
    clock = pygame.time.Clock()
    
    delta = 0
    
    while True:
        # 仅在事件循环内处理事件
        for ev in pygame.event.get():
            game.handle_event(ev)
            
        # 核心逻辑移到主循环顶层,每帧都执行
        delta += clock.tick(60)
        
        if delta >= game.tick:
            game.update()
            delta = 0
        
        game.draw(surface)
        pygame.display.flip()

2. 修复Snake类的grow方法

Python列表没有appendLeft方法,需要改用insert(0, ...)实现头部添加节点:

def grow(self):
    self.nodes.insert(0, [
        self.nodes[0][0] + self.dir[0],
        self.nodes[0][1] + self.dir[1]])

3. 完善退出逻辑

handle_event方法中,收到QUIT事件时仅调用pygame.quit()无法确保程序完全退出,需补充sys.exit():

if event.type == QUIT:
    pygame.quit()
    sys.exit()

内容的提问来源于stack exchange,提问作者K. Russell Smith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 19:45:13