Problem is pygame.mouse.get_pressed() which gives True all the time when you keep mouse's button pressed. Button treads it as many clicks and it executes function many times. Using event you can catch moment when button change state from not-pressed to pressed and this will be only one moment so it will execute function only once.
But using event need a lot of changes. If you move all function to for event loop then it will run action only once but it will also draw button only once - when you click region where should be button.
Easy method is to split this function into two functions
action_button_draw which only draw button and use it in old place action_button_click which only run action and put it in for event loop
if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # 1 = left button, 3 = right button action_button_click(x, y, w, h, action)
or at least
if event.type == pygame.MOUSEBUTTONDOWN: action_button_click(x, y, w, h, action)
Function:
def action_button_click(x, y, w, h, action=None): mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() if x+w > mouse[0] > x and y+h > mouse[1] > y: if click[0] == 1 and action != None: action() def action_button_draw(x, y, w, h, ic, ac, text, text_colour): mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() if x+w > mouse[0] > x and y+h > mouse[1] > y: pygame.draw.rect(screen, ac,(x,y,w,h)) else: pygame.draw.rect(screen, ic,(x,y,w,h)) font = pygame.font.SysFont("arial black",20) text = font.render(text,True,(text_colour)) screen.blit(text,[x+w/2-(text.get_rect().w/2),y+h/2-(text.get_rect().h/2)])
Minimal working example:
import pygame # --- constants --- WIDTH = 640 HEIGHT = 480 FPS = 5 # --- functions --- def action_button_click(x, y, w, h, action=None): mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() if x+w > mouse[0] > x and y+h > mouse[1] > y: if click[0] == 1 and action != None: action() def action_button_draw(x, y, w, h, ic, ac, text, text_colour): mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() if x+w > mouse[0] > x and y+h > mouse[1] > y: pygame.draw.rect(screen, ac,(x,y,w,h)) else: pygame.draw.rect(screen, ic,(x,y,w,h)) font = pygame.font.SysFont("arial black",20) text = font.render(text,True,(text_colour)) screen.blit(text,[x+w/2-(text.get_rect().w/2),y+h/2-(text.get_rect().h/2)]) def test_action(): print("clicked") # --- main --- # - init - pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) screen_rect = screen.get_rect() # - mainloop - clock = pygame.time.Clock() running = True while running: # - events - 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 # MOUSEBUTTONDOWN is created only once, # when button changes state from "not-pressed" to "pressed" # so it is better for this job than "pygame.mouse.get_pressed()" elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: action_button_click(100, 100, 100, 50, test_action) # --- draws ---- screen.fill([0,0,0]) # clear screen action_button_draw(100, 100, 100, 50, [255,0,0], [0,255,0], "Hello", [0,0,0]) pygame.display.flip() # - FPS - clock.tick(FPS) # - end - pygame.quit()
These two functions you can also put in class and use class like in example-class.py
Original function works good only if executed action removes button from this region (ie. it removes menu and start game) but it has problem when button stays in the same place after clicking or you put new button in the same place (ie. when you go from menu to options which has button in the same place)
pygame.mouse.get_pressed()which givesTrueall the time when you keep mouse's button pressed. Button treads it as many clicks and it executes function many times. Usingeventyou can catch moment when button change state from unpressed to pressed and this will be only one moment so it will execute function only once. But usingeventneed a lot of changes.