Search code examples
pythonpygamepygame-surfacepygame-clockpygame-tick

Second surface doesn't appear on screen until I freeze the game


I'm trying out Pygame and started with a classic, creating the Snake game, the issue is that I try to blit the 'apple' on the board but the apple doesn't appear up until I crash in the walls this is when I set fps = 0 and I freeze the game. I've noticed that when I increase the fps to something like 100 or more the apple starts appearing and disappearing.

import random
import pygame


class Game:
    def __init__(self):
        pygame.init()
        self.board = pygame.display.set_mode((400, 400))
        self.apple = Apple(self.board)
        self.snake = Snake(self.board, self)
        self.direction = 'right'
        self.going = True
        self.fps = 5

    def play(self):
        clock = pygame.time.Clock()
        while self.going:
            clock.tick(self.fps)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.going = False
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_LEFT:
                        self.direction = 'left'
                    if event.key == pygame.K_RIGHT:
                        self.direction = 'right'
                    if event.key == pygame.K_UP:
                        self.direction = 'up'
                    if event.key == pygame.K_DOWN:
                        self.direction = 'down'
            self.apple.spawn()
            self.snake.move(self.direction)

    def game_over(self):
        self.fps = 0

    def get_fps(self):
        return self.fps


class Snake:
    def __init__(self, board, game):
        self.board = board
        self.image = pygame.Surface((10, 10))
        self.game = game

        self.length = 5
        self.x = [10] * self.length
        self.y = [10] * self.length

    def move(self, direction):
        for i in range(self.length-1, 0, -1):
            self.x[i] = self.x[i-1]
            self.y[i] = self.y[i-1]

        if direction == 'right':
            self.x[0] += 10
        if direction == 'left':
            self.x[0] -= 10
        if direction == 'up':
            self.y[0] -= 10
        if direction == 'down':
            self.y[0] += 10

        if self.x[0] == self.board.get_width() or self.x[0] == -10 or\
                self.y[0] == self.board.get_height() or self.y[0] == -10:
            self.game.game_over()

        if self.game.get_fps() != 0:
            self.draw()

    def draw(self):
        self.board.fill((0, 255, 0))
        for i in range(self.length):
            self.board.blit(self.image, (self.x[i], self.y[i]))
        pygame.display.flip()


class Apple:
    def __init__(self, board):
        self.board = board
        self.image = pygame.Surface((10, 10))
        self.image.fill(pygame.color.Color('red'))
        self.x = random.randint(0, self.board.get_width())
        self.y = random.randint(0, self.board.get_height())

    def spawn(self):
        self.board.blit(self.image, (self.x, self.y))
        pygame.display.flip()


if __name__ == '__main__':
    game = Game()
    game.play()

Solution

  • Clear the display at the begin of the application loop and update the dispaly at the end of the application loop:

    class Game:
        # [...]
    
        def play(self):
            clock = pygame.time.Clock()
            while self.going:
                clock.tick(self.fps)
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        self.going = False
                    if event.type == pygame.KEYDOWN:
                        if event.key == pygame.K_LEFT:
                            self.direction = 'left'
                        if event.key == pygame.K_RIGHT:
                            self.direction = 'right'
                        if event.key == pygame.K_UP:
                            self.direction = 'up'
                        if event.key == pygame.K_DOWN:
                            self.direction = 'down'
    
                self.board.fill((0, 255, 0))             # <---
    
                self.apple.spawn()
                self.snake.move(self.direction)
    
                pygame.display.flip()                    # <---
    
    class Snake:
        # [...]
    
        def draw(self):
            # self.board.fill((0, 255, 0))                          <-- DELETE
            for i in range(self.length): 
                self.board.blit(self.image, (self.x[i], self.y[i]))
            # pygame.display.flip()                                 <-- DELETE
    
    class Apple:
        # [...]
    
        def spawn(self):
            self.board.blit(self.image, (self.x, self.y))
            #pygame.display.flip()                          <-- DELETE
    

    fill clears the entire Surface. Everything that was previously drawn is lost.
    One update of the display at the end of the application loop is sufficient. Multiple calls to pygame.display.update() or pygame.display.flip() cause flickering.