'Why does my button not work outside of the main loop

I have been following this tutorial about animated buttons in pygame. It worked perfectly until I created a button outside of the main loop in another function.

Here is my code:

import pygame
from pygame.locals import *
import sys
import random

# Constants

SCREEN = pygame.display.set_mode((1280, 720), 0, 32)

# Colours
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (100, 100, 100)
LIGHT_BLUE = (66, 233, 245)

# Button Class
class Button:
    def __init__(self, text, width, height, pos, elevation):
        # Core attributes
        self.pressed = False
        self.elevation = elevation
        self.dynamicElevation = elevation
        self.originalYPos = pos[1]

        # Top Rectangle
        self.topRectangle = pygame.Rect(pos, (width, height))
        self.topColor = '#457B9D'

        # Bottom Rectangle
        self.bottomRectangle = pygame.Rect(pos, (width, elevation))
        self.bottomColor = '#1D3557'

        # Text
        self.textSurface = gui_font.render(text, True, '#FFFFFF')
        self.textRectangle = self.textSurface.get_rect(center = self.topRectangle.center)

    def draw(self):
        # Elevation Logic
        self.topRectangle.y = self.originalYPos - self.dynamicElevation
        self.textRectangle.center = self.topRectangle.center

        self.bottomRectangle.midtop = self.topRectangle.midtop
        self.bottomRectangle.height = self.topRectangle.height + self.dynamicElevation

        bottom =pygame.draw.rect(SCREEN, self.bottomColor, self.bottomRectangle, border_radius = 12)
        top = pygame.draw.rect(SCREEN, self.topColor, self.topRectangle, border_radius = 12)
    
        pygame.draw.rect(SCREEN, '#000000', top, 1, border_radius = 12)
        pygame.draw.rect(SCREEN, '#000000', bottom, 1, border_radius = 12)
    
        SCREEN.blit(self.textSurface, self.textRectangle)
        self.check_click()

    def check_click(self):
        mousePosition = pygame.mouse.get_pos()
        if self.topRectangle.collidepoint(mousePosition):
            self.topColor = '#F1FAEE'
            if pygame.mouse.get_pressed()[0]:
                self.dynamicElevation = 0
                self.pressed = True
            else:
                self.dynamicElevation = self.elevation
                if self.pressed == True:
                    print("Click")
                    self.pressed = False
        else:
            self.topColor = '#457B9D'

class GameState():
    def __init__(self):
        self.state = "welcome"

    def welcomeScreen(self):
        SCREEN.fill(WHITE)
        for event in pygame.event.get():
            if event.type == QUIT:
                exit()
            
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    exit()
                if event.key == K_F1:
                    self.state = "mainGame"

        pygame.display.update()


    def mainGame(self):
        SCREEN.fill(GREY)
        buttonBack = Button("Back to Main Screen", 250, 30, (1000, 650), 8)

        for event in pygame.event.get():
            if event.type == QUIT:
                exit()
            
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    exit()
                if event.key == K_F2:
                    self.state = "welcome"

        buttonBack.draw()
        pygame.display.update()

    def stateManager(self):
        if self.state == "welcome":
            self.welcomeScreen()
        if self.state == "mainGame":
            self.mainGame()




pygame.init()
clock = pygame.time.Clock()
gameState = GameState()

pygame.display.set_caption("Button Test")

gui_font = pygame.font.Font(None, 30)

while True:

    gameState.stateManager()
    clock.tick(60)

I have tried to play around with putting the button in a different screen or at different stages of the loop. Is there a logic error I cannot see or lies my mistake somewhere else?



Solution 1:[1]

You are actually creating the button inside the main loop since you create it each time mainGame is called. mainGame is called by stateManager if the state is "mainGame", and that's called at each frame in your while True loop. So as you are recreating your button at each frame I think your problems might come from there.

I suggest you create your button in the parent's class constructor instead:

class GameState():
    def __init__(self):
        self.state = "welcome"
        # Create the button here to make the object persistent.
        self.buttonBack = Button("Back to Main Screen", 250, 30, (1000, 650), 8)
    # ...

    def mainGame(self):
        SCREEN.fill(GREY)

        for event in pygame.event.get():
            if event.type == QUIT:
                exit()
            
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    exit()
                if event.key == K_F2:
                    self.state = "welcome"

        self.buttonBack.draw() # <- use your button with self.buttonBack
        pygame.display.update()
     # ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peterrabbit