【问题标题】:PyGame - while loop causing game to lagPyGame - while循环导致游戏滞后
【发布时间】:2020-07-03 18:30:31
【问题描述】:

我正在设计一个简单的情绪识别游戏,它在屏幕上显示面孔一秒钟,然后消失并让参与者单击按钮来决定显示哪种情绪。然而,我努力让它只显示一秒钟,我最终使用了一个 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


    【解决方案1】:

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

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

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

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

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

    编辑: 您的更改大多是正确的。您的代码的某些部分我不明白 - 这就是它应该有 cmets 的原因。 未来-你也不会理解它的一部分。

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

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

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

    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
    

    【讨论】:

    • 非常感谢,金斯利!这是我在 StackOverflow 上的第一篇文章,很高兴收到如此有用的答案。我会立即采纳您的反馈 - 这绝对是一种更好的做事方式!澄清一下,由于这些原因(阻塞事件循环并导致游戏延迟),我们是否应该避免在主游戏循环中使用 while 循环,或者有时可以这样做,这取决于你想要做什么?
    • @Theamazingone1 - 循环没问题,但它们需要轮询事件队列。阅读pygame.org/docs/ref/event.html 在一个紧凑的循环中,您可能只需在每次迭代时调用pygame.event.pump() 就可以逃脱。但是,如果您可以将程序布局为具有单个主事件循环,并在其中分支绘制/更新逻辑,那么您将永远不会有问题。
    • 非常感谢@Kingsley 的所有帮助。我合并了“状态机”概念,但不幸的是它似乎不起作用。图像无限期地保留在屏幕上 - 我不确定问题是它没有改变状态,还是当它改变状态时,什么都没有发生。我在包含我更新的代码的原始帖子中添加了一个编辑。你能看一下,看看你能不能找出我哪里出错了?
    • 我明白了,因为我在主游戏循环中设置了初始状态,所以每次循环结束后它都会自动返回到那个状态?这很有意义!非常感谢你的帮助,你给了我很多思考和改进的地方。我一定会确保在未来添加更多的 cmets!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    相关资源
    最近更新 更多