Search code examples
pythonpygame

How do you dynamically create buttons using Pygame?


I'm making a simple project for Pygame, which is basically intended to run as a text-based strategy/fighting game. I coded it just using Python, but now I'm trying to implement a GUI using Pygame (and expand on it eventually).

In this game, the player has a number of available moves depending on their current position (stored as an array). What I'm hoping to do is create and display a button for each of these moves that the user can choose from.

I know how to make a button, but I'm struggling to figure out how to add a button per move and constantly updates, based on the player's changing position.


Solution

  • You basically want a Button class for that. That way you can create button objects, put them in a list and operate on them easily. Here is an example with a barely-even-a-button Button class just to get the point across. I have created a list with 4 buttons, but you can just as easily .append those buttons to dynamically create them according to the needs of your program.

    import pygame
    from sys import exit as _exit 
    
    ##just pygame stuff here. You can ignore them and go
    ##to the interesting things below
    
    class PG_Widnow_UI:
        def __init__(self, width, height):
            pygame.init()
            self.widht = width
            self.height = height
            self.window = pygame.display.set_mode((width, height))
    
        def update(self):
            pygame.display.flip()
    
        def clear(self, r, g, b):
            self.window.fill((r, g, b))
    
        def close(self):
            pygame.quit()
            _exit()
    
            #handles events 
    def handleEvents(events):
        exitGame = False
        for event in events:
            if event.type == pygame.QUIT:
                pg_window.close()
    
    #Takes rectangle's size, position and a point. Returns true if that
    #point is inside the rectangle and false if it isnt.
    def pointInRectanlge(px, py, rw, rh, rx, ry):
        if px > rx and px < rx  + rw:
            if py > ry and py < ry + rh:
                return True
        return False
    
    
    ##=====================================================================##
    
    #This class will act as a bllueprint for all the buttons in the game
    class Button:
        def __init__(self, text:str, position:tuple
                     , size:tuple=(200, 50), outline:bool=True)->None:
            self.position = position
            self.size = size
            self.button = pygame.Surface(size).convert()
            self.button.fill((0, 0, 0))
            self.outline = outline
    
            #Text is about 70% the height of the button
            font = pygame.font.Font(pygame.font.get_default_font(), int((70/100)*self.size[1]))
    
            #First argument always requires a str, so f-string is used.
            self.textSurf = font.render(f"{text}", True, (255, 255, 255))
    
        def clicked(self, events)->None:
            mousePos = pygame.mouse.get_pos()
            if pointInRectanlge(mousePos[0], mousePos[1], self.size[0], self.size[1], self.position[0], self.position[1]):
                for event in events:
                    if event.type == pygame.MOUSEBUTTONDOWN:
                        return True
            return False
    
        #Renders the button and text. Text position is calculated depending on position of button.
        #Also draws outline if self.outline is true
        def render(self, display:pygame.display)->None:
            #calculation to centre the text in button
            textx = self.position[0] + (self.button.get_rect().width/2) - (self.textSurf.get_rect().width/2)
            texty = self.position[1] + (self.button.get_rect().height/2) - (self.textSurf.get_rect().height/2)
    
            #display button first then text
            display.blit(self.button, (self.position[0], self.position[1]))
            display.blit(self.textSurf, (textx, texty))
            
            #draw outline
            if self.outline:
                thickness = 5
                posx = self.position[0] - thickness
                posy = self.position[1] - thickness
                sizex = self.size[0] + thickness * 2
                sizey = self.size[1] + thickness * 2
    
                pygame.draw.rect(display, (255, 0, 0), (posx, posy, sizex, sizey), thickness)
    
    
    windowWidth = 1000
    windowHeight = 500
    pg_window = PG_Widnow_UI(windowWidth, windowHeight)
    
    ##LOOP TO ADD BUTTONS TO THE LIST 
    
    
    number_of_buttons = 4
    buttons = [Button(f"Button {i}", ((220 * (i%4) + 10), ((i % 4) * 70) + 10)) 
    for i in range(number_of_buttons)]
    
    while True:
        pg_window.clear(255, 255, 255)
    
        events = pygame.event.get()
        handleEvents(events)
    
        ##DRAWING THE BUTTONS 
        for button in buttons:
            button.render(pg_window.window)
            if button.clicked(events):
                print(f"button at position: {button.position} was  clicked") 
        
        pg_window.update()