Button on main screen flickering in pygame

440 Views Asked by At

I'm creating a game. So, on my main menu, I have two buttons for play and exit. But, whichever button i call later, i.e, if i call my func for Start button and then call the Exit button, or vice versa, the second called button starts flickering. Here's my code:

import math
import pygame
import random
import time
from pygame import mixer

# Initialising PyGame
pygame.init()

# Creating Screen
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Space Invaders')

# Global Variables
playerIMG = pygame.image.load('player.png')
alienIMG = []
bulletIMG = pygame.image.load('bullet.png')
bullet_state = 'ready'
clock = pygame.time.Clock()
start_count = 0
exit_count = 0
# Background
bgIMG = pygame.image.load('C:bgIMG.png')
bgIMG = pygame.transform.scale(bgIMG, (800, 600))


# Font
def text_on_screen(text, colour, x, y, size):
    font = pygame.font.Font('Stargaze Regular.otf', size)
    screen_text = font.render(text, True, colour)
    screen.blit(screen_text, [x, y])


def player(x, y):
    screen.blit(playerIMG, (x, y))


def alien(x, y, i):
    screen.blit(alienIMG[i], (x, y))


def fire_bullet(x, y):
    global bullet_state
    bulletSound = mixer.Sound('laser.wav')
    bulletSound.play()
    bullet_state = 'fire'
    screen.blit(bulletIMG, (x + 15, y - 30))


def isCollision(x1, x2, y2, y1):
    dist = math.sqrt(((x1 - x2) ** 2) + ((y1 - y2) ** 2))
    if dist < 23:
        collisionSound = mixer.Sound('explosion.wav')
        collisionSound.set_volume(120)
        collisionSound.play()
        return True
    else:
        return False


class Button:
    def __init__(self, color, x, y, width, height, text=''):
        self.color = color
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.text = text

    def draw(self, win, font_size, outline=None):
        # Call this method to draw the button on the screen
        if outline:
            pygame.draw.rect(win, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)

        pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.height), 0)

        if self.text != '':
            font = pygame.font.Font('Stargaze Regular.otf', font_size)
            text = font.render(self.text, 1, (0, 0, 0))
            win.blit(text, (
                self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))

    def isOver(self, pos):
        # Pos is the mouse position or a tuple of (x,y) coordinates
        if self.x < pos[0] < self.x + self.width:
            if self.y < pos[1] < self.y + self.height:
                return True

        return False


# Game Over
def game_over():
    for i in range(3000):
        text_on_screen('GAME OVER!', (255, 255, 0), 190, 220, 60)
        text_on_screen('PRESS R TO RESTART', (255, 255, 0), 270, 310, 20)
        text_on_screen('PRESS Q TO QUIT', (255, 255, 0), 300, 335, 20)
        pygame.display.update()
        for e in pygame.event.get():
            if e.type == pygame.KEYDOWN:
                if e.key == pygame.K_r:
                    gameloop()
                    break
                elif e.key == pygame.K_q:
                    pygame.quit()
                    quit()


def Start_button():
    global start_count
    mouse = pygame.mouse.get_pos()
    start_button = Button((255, 0, 255), 10, 370, 80, 40, 'PLAY')
    startwidth = start_button.width
    startheight = start_button.height
    start_text = start_button.text
    if start_button.isOver(mouse):
        start_button.color = (255, 255, 255)
        start_button.width += 10
        start_button.height += 10
        loop = 0
        i = 1
        if start_count == 0:
            while loop < 4 and i < 5:
                start_button.text = start_text[:i]
                start_button.draw(screen, 23)
                time.sleep(0.08)
                loop += 1
                i += 1
                start_count += 1
                pygame.display.update()
        else:
            start_button.draw(screen, 23)
            pygame.display.update()
    else:
        start_button.height = startheight
        start_button.width = startwidth
        start_button.draw(screen, 20)
    pygame.display.update()
    if not start_button.isOver(mouse):
        start_count = 0
    for event in pygame.event.get():
        if event.type == pygame.MOUSEBUTTONDOWN and start_button.isOver(mouse):
            gameloop()


def Exit_Button():
    global exit_count
    mouse = pygame.mouse.get_pos()
    exit_button = Button((255, 0, 255), 10, 425, 80, 40, 'EXIT')
    exitwidth = exit_button.width
    exitheight = exit_button.height
    exit_text = exit_button.text
    if exit_button.isOver(mouse):
        exit_button.color = (255, 255, 255)
        exit_button.width += 10
        exit_button.height += 10
        loop = 0
        i = 1
        if exit_count == 0:
            while i < 5:
                exit_button.text = exit_text[:i]
                exit_button.draw(screen, 23)
                time.sleep(0.08)
                loop += 1
                i += 1
                exit_count += 1
                pygame.display.update()
        else:
            exit_button.draw(screen, 23)
            pygame.display.update()
    else:
        exit_button.height = exitheight
        exit_button.width = exitwidth
        exit_button.draw(screen, 20)
    pygame.display.update()
    if not exit_button.isOver(mouse):
        exit_count = 0
    for event in pygame.event.get():
        if event.type == pygame.MOUSEBUTTONDOWN and exit_button.isOver(mouse):
            pygame.quit()
            quit()


# Welcome
def welcome():
    run = True
    global clock

    while run:
        screen.fill((0, 0, 0))
        Start_button()
        Exit_Button()
        pygame.display.update()
        clock.tick(60)


# Pause
def pause_game():
    pause = True
    while pause:
        mixer.music.pause()
        screen.fill((0, 0, 0))
        text_on_screen('PAUSE', (255, 255, 255), 0, 0, 40)
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    mixer.music.unpause()
                    pause = False


# Game Loop
def gameloop():
    mixer.music.load('background.wav')
    mixer.music.set_volume(50)
    mixer.music.play(-1)
    # Player
    global bullet_state
    playerX = 370
    playerY = 500
    player_changeX = 0
    fps = 30

    # Alien Invader

    alienX = []
    alienY = []
    alien_changeX = []
    number_of_aliens = 5
    for i in range(number_of_aliens):
        alienIMG.append(pygame.image.load('alien.png'))
        alienX.append(random.randint(0, 735))
        alienY.append(random.randint(0, 100))
        alien_changeX.append(7)

    # Bullet

    bulletX = 0
    bulletY = 500
    bullet_changeY = 20

    score = 0
    running = True
    while running:

        screen.fill((0, 0, 0))
        screen.blit(bgIMG, (0, 0))
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                running = False
            if e.type == pygame.KEYDOWN:
                if e.key == pygame.K_F4 and pygame.key.get_mods() & pygame.KMOD_ALT:
                    running = False
                if e.key == pygame.K_LEFT or e.key == pygame.K_a:
                    player_changeX = -7
                if e.key == pygame.K_RIGHT or e.key == pygame.K_d:
                    player_changeX = 7
                if e.key == pygame.K_SPACE or e.key == pygame.K_RETURN:
                    bulletX = playerX
                    fire_bullet(bulletX, bulletY)
                if e.key == pygame.K_ESCAPE:
                    pause_game()
            if e.type == pygame.KEYUP:
                if e.key == pygame.K_LEFT or e.key == pygame.K_RIGHT or e.key == pygame.K_a or e.key == pygame.K_d:
                    player_changeX = 0
        playerX += player_changeX
        if playerX < 0:
            playerX = 0
        elif playerX >= 736:
            playerX = 736
        if bulletY <= 0:
            bulletY = 500
            bullet_state = 'ready'
        if bullet_state == 'fire':
            fire_bullet(bulletX, bulletY)
            bulletY -= bullet_changeY

        for i in range(number_of_aliens):
            if abs(alienY[i] - playerY) < 50 and abs(alienX[i] - playerX) < 67:
                for h in range(number_of_aliens):
                    alienY[h] = 2000
                game_over()
                break
            alienX[i] += alien_changeX[i]
            if alienX[i] < 0:
                alienX[i] = 0
                alien_changeX[i] = 7
                alienY[i] += 40
            elif alienX[i] > 736:
                alienX[i] = 736
                alien_changeX[i] = -7
                alienY[i] += 40
            collisionbullet = isCollision(alienX[i], bulletX, alienY[i], bulletY)
            if collisionbullet:
                bulletY = 480
                bullet_state = 'ready'
                alienX[i] = random.randint(0, 735)
                alienY[i] = random.randint(0, 120)
                score += 10

            alien(alienX[i], alienY[i], i)

        player(playerX, playerY)
        text_on_screen('Score:' + str(score), (0, 255, 0), 0, 0, 20)
        clock.tick(fps)
        pygame.display.update()

    pygame.quit()
    quit()


welcome()

Any ideas on how to fix this or where the problem is?

1

There are 1 best solutions below

4
On BEST ANSWER

Only perform one display update at the end of the application loop, not multiple updates during the frame. Multiple updates of the display cause flickering. One single pygame.display.update() or pygame.display.flip() at the end of the frame is sufficient.

Remove all the (6) calls to pygame.display.update() from Start_button Exit_Button and call pygame.display.update() once in welcome.

Code duplications like in Start_button Exit_Button are a code smell. Avoid them. Implement a button class which can represent and animate the button. Remove start_count and exit_count, but add a count attrubute to Button:

class Button:
    def __init__(self, color, x, y, width, height, text=''):
        self.color = color
        self.text = text
        self.rect = pygame.Rect(x, y, width, height)
        self.normal = pygame.Surface((width, height))
        self.normal.fill(self.color) 
        self.highlight = [pygame.Surface((width, height)), pygame.Surface((width + 10, height + 10))]
        self.highlight[0].fill((255, 255, 255))
        self.highlight[1].fill((255, 255, 255))
        small_font = pygame.font.Font('Stargaze Regular.otf', 20)
        large_font = pygame.font.Font('Stargaze Regular.otf', 23)
        text_small = small_font.render(self.text, 1, (0, 0, 0))
        text_large = large_font.render(self.text, 1, (0, 0, 0))
        self.normal.blit(text_small, text_small.get_rect(center = self.normal.get_rect().center))
        self.highlight[0].blit(text_small, text_small.get_rect(center = self.highlight[0].get_rect().center))
        self.highlight[1].blit(text_large, text_large.get_rect(center = self.highlight[1].get_rect().center))
        self.count = 0

    def draw(self, win, pos, outline = None):
        text_surf = self.normal
        if self.isOver(pos):
            text_surf = self.highlight[0]
            self.count += 1
            if self.count < 200:
                text_surf = self.highlight[self.count // 20 % len(self.highlight)]              
        else:
           self.count = 0
        text_rect = text_surf.get_rect(center = self.rect.center) 
        if outline:
            pygame.draw.rect(win, outline, text_rect.inflate(2, 2), 2)
        win.blit(text_surf, text_rect)

    def isOver(self, pos):
        return self.rect.collidepoint(pos)

With this class you can simplify the function welcome a nd you don't need the functions Start_button Exit_Button at all:

def welcome():
    exit_button = Button((255, 0, 255), 10, 425, 80, 40, 'EXIT')
    start_button = Button((255, 0, 255), 10, 370, 80, 40, 'PLAY')

    run = True
    while run:
        clock.tick(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            if event.type == pygame.MOUSEBUTTONDOWN:
                if exit_button.isOver(event.pos):
                    run = False
                if start_button.isOver(event.pos):
                    gameloop()
        mouse = pygame.mouse.get_pos()

        screen.fill((0, 0, 0))
        exit_button.draw(screen, mouse)
        start_button.draw(screen, mouse)
        pygame.display.update()

    pygame.quit()
    quit()