首页 > 解决方案 > PyGame - while循环导致游戏滞后

问题描述

我正在设计一个简单的情绪识别游戏,它在屏幕上显示面孔一秒钟,然后消失并让参与者单击一个按钮来决定显示哪种情绪。然而,我努力让它只显示一秒钟,我最终使用了一个 while 循环来显示它,但自从我发现我的游戏滞后并且运行非常缓慢。这是相关代码,来自我定义的用于显示图像的函数。我设置了它,所以它创建了一个透明版本的图像,然后将其改为 blits,以提供图像在 1 秒后消失的错觉。

def askQuestion(imageNumber):
    mouse = pygame.mouse.get_pos()
    last = pygame.time.get_ticks()
    while pygame.time.get_ticks() - last <= 1000:
        screen.blit(images[imageNumber], (((display_width/2) - (images[imageNumber].get_size()[0]/2)), (((display_height/2) - (images[imageNumber].get_size()[1]/2)))))
    else:
        images[imageNumber].fill((255,255,255,0))

我强烈怀疑它滞后的原因是,通过 while 循环,Python 不断地将图像传送到屏幕上,而不是只执行一次,从而消耗了大量资源。任何人都可以建议一种更简单的方法来仅显示此图像一秒钟而不使用 while 循环吗?(另外,我知道我的图像坐标代码真的很乱——它在我的待办事项清单上!)

更新:我试图合并@Kingsley 建议的“状态机”,但它似乎仍然无法正常工作,因为图像无限期地保留在屏幕上。这是我到目前为止所拥有的:

running = True
while running:
    screen.fill((255, 255, 255))
    answerDetection()
    pygame.draw.circle(screen, (0,0,0), (int(display_width/2), int(display_height/2)), 10, 2)
    displayButtons("sad", 0, 700, 200, 100)
    displayButtons("happy", 240, 700, 200, 100)
    displayButtons("neutral", 480, 700, 200, 100)
    displayButtons("angry", 720, 700, 200, 100)
    displayButtons("afraid", 960, 700, 200, 100)
    imageNumber = len(answers) + 1    
    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False

    gameState = States.VIEWING
    timeNow = pygame.time.get_ticks()
    startTime = timeNow
    if ( gameState == States.VIEWING):
        if( timeNow <= startTime + 1000):
            askQuestion(imageNumber)
        else:
            gameState == States.ANSWERING

    if (gameState == States.ANSWERING):
        images[imageNumber].fill((255,255,255,0))
        for rects in rectList:
            for event in pygame.event.get():
                if event.type == pygame.MOUSEBUTTONUP:
                    if pygame.Rect(rects).collidepoint(mouse):
                        gameState == States.VIEWING   

澄清一下, answerDetection() 是一个检测用户点击了哪个按钮的函数。在 States.ANSWERING 部分,我使用 "images[imageNumber].fill((255, 255, 255, 0)) 作为使图像透明的一种方式,因为在 VIEWING 状态下我要做的就是使图像消失(因为无论如何按钮都会不断显示)。我不确定问题是它没有改变状态,还是当它改变状态时没有发生任何事情。

任何帮助将非常感激!

编辑 2:进一步的实验揭示了问题:通过我有 timeNow = pygame.time.get_ticks() 紧跟 startTime = timeNow 的方式,循环不断更新 timeNow 变量,然后将 startTime 变量重新设置为紧随其后的相同数字 - 而据我了解,我们希望 startTime 是一个静态数字,以指示“查看”状态开始的时间。否则,timeNow 永远不会比 startTime 多 1000 毫秒,并且永远不会进入“应答”状态。我只需要找到一种方法来设置查看状态开始时的时间,并将其设置为 startTime 变量中的静态时间。

标签: pygame

解决方案


你完全正确。Pygame 正在尽可能快地重新传输该图像。在此期间,您还阻塞了事件循环,因此无法关闭窗口、调整窗口大小等,最终操作系统会认为它“无响应”。你需要另一种方法。

你的游戏有两个阶段,“提问”和“回答”。思考游戏的一个好方法是状态图。它从“显示问题”状态开始,然后在时间限制到期后,移动到“回答问题”状态。如果用户提供并回答,它会再次回到“显示问题”状态。你还会有一些额外的状态,比如“游戏结束”,也许还有“介绍显示”等。

像这样的状态只能用一个符号数字来表示。大多数编程语言都有枚举类型只是为了这种事情:

## Enumerated Type to keep a set of constants together
class States( enum.IntEnum ):
    GAME_OVER = 0
    VIEWING   = 1
    ANSWERING = 2

因此,要知道您处于什么状态,只需将其与这些进行比较。

if ( current_state == States.GAME_OVER ):
    drawGameOverScreen()
    waitForKeyPress()

所以在你的主循环中,代码可以使用状态来控制绘画和事件处理路径:

while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True

    # Update the window
    window.fill( WHITE )

    # State Machine
    time_now = pygame.time.get_ticks()
    if ( game_state == States.VIEWING ):
        # If the viewing time is not expired, paint the question image
        if ( time_now <= start_time + IMAGE_VIEW_TIME ):
            emotion_image = emotion_images[ image_index ]
            image_rect = emotion_image.get_rect()
            image_rect.center = ( WINDOW_WIDTH//2, WINDOW_HEIGHT//2 )
            window.blit( emotion_image, image_rect )
        else:
            # The timer has expired, shift to the next state
            game_state = States.ANSWERING
            start_time = time_now
            # ( screen will repaint next frame )

    elif ( game_state == States.ANSWERING ):
        # TODO: draw the multiple choice answers 
        #       determine if the user-input was correct, etc.
        if ( answer_key_pressed != None ):
            if ( //answer correct// ):
                # TODO
            else: 
                # TODO
            
            # Answer has been made, Switch to Question-Viewing State
            game_state = States.VIEWING
            start_time = time_now
            image_index = random.randrange( 0, len( emotion_images ) )
            # ( screen will repaint next frame )

使用状态转换来驱动你的游戏可能有点乏味。但它确实允许您绘制所有不同的游戏阶段,并在它们之间移动,而无需求助于(如您的示例)损害整个程序的独立绘图和控制功能。

编辑: 您的更改大多是正确的。您的代码的某些部分我不明白 - 这就是为什么它应该有评论。 未来——你也不会理解其中的一部分。

使用状态转换设置,通常您检查每个操作的状态。需要绘制屏幕 => 检查状态以查看要绘制的内容。得到一些用户输入 => 检查状态以查看它是否被忽略或纠正等。您检查给定场景中的状态,然后逐位处理。

您编辑的代码的主要问题是它在每个循环过程中将状态重新设置为 VIEWING 。所以你的状态总是在查看,即使它改变了,下一帧它又变回来了。

我重新安排了您的代码,检查了鼠标单击处理程序中的状态。哦,并添加了一些评论。请尝试至少写出这种级别的评论。有一天它会拯救你的培根。

gameState     = States.VIEWING  # The initial state
answerClicked = -1              # The answer-box the user clicked on

running = True
while running:

    # update the screen
    screen.fill((255, 255, 255))
    pygame.draw.circle(screen, (0,0,0), (int(display_width/2), int(display_height/2)), 10, 2)
    displayButtons("sad", 0, 700, 200, 100)
    displayButtons("happy", 240, 700, 200, 100)
    displayButtons("neutral", 480, 700, 200, 100)
    displayButtons("angry", 720, 700, 200, 100)
    displayButtons("afraid", 960, 700, 200, 100)
    imageNumber = len(answers) + 1    

    # reset the clock, and any previous answer
    timeNow       = pygame.time.get_ticks()
    answerClicked = -1

    #answerDetection()  what does this do?  Not Here anyway.
    
    # Handle events and user-input
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False
        elif event.type == pygame.MOUSEBUTTONUP:
            # Mouse was clicked, are we in answer-mode?
            if ( gameState == States.ANSWERING ):
                for i,rects in enumerate( rectList ):
                    if pygame.Rect(rects).collidepoint(mouse):
                        print( "Answer-box was cicked in rect %d" % ( i ) )
                        answerClicked = i
                
    # Handle Game State and Transitions
    if ( gameState == States.VIEWING):
        if( timeNow <= startTime + 1000):
            # Draw the questions to the screen
            askQuestion( imageNumber )
        else:
            # question time is up
            gameState == States.ANSWERING

    elif (gameState == States.ANSWERING):
        images[imageNumber].fill((255,255,255,0))  # why do this?
        # Did the user click an answer-rect?
        if ( answerClicked != -1 ):
            # USer has clicked an answer, was it correct?
            print( "Handling correct/incorrect answer! Add Code HERE!!!" )
            # Maybe we now Change back into question mode ?
            # not sure how failures/retries/etc. should be handled
            gameState == States.VIEWING   
            startTime = timeNow

推荐阅读