1

I've made this button class that performs different actions depending on which button is clicked. At the moment when the buttons are clicked the actions are run multiple times. Is there anyway of making it so that the action only runs once.

def action_button(x,y,w,h,ic,ac,text, text_colour,action=None): 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)) if click[0] == 1 and action != None: action() 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)]) 
4
  • it is not class but function. Here is example-class.py with class Button. Commented Apr 9, 2019 at 12:47
  • 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 unpressed to pressed and this will be only one moment so it will execute function only once. But using event need a lot of changes. Commented Apr 9, 2019 at 12:51
  • I see this button in many questions. It is probably from some tutorial . Commented Apr 9, 2019 at 12:59
  • I found this function in Sentdex's tutorial Commented Apr 9, 2019 at 14:08

2 Answers 2

1

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)

Sign up to request clarification or add additional context in comments.

Comments

0

I solved it this way

I used this to make a button in pygame

def __init__(self): self.pressed = 0 def myfunc(self): if self.rect.collidepoint(pygame.mouse.get_pos()): if pygame.mouse.get_pressed()[0] and self.pressed == 0: print("I clicked once") self.pressed = 1 # you cannot click anymore elif pygame.mouse.get_pressed() == (0,0,0): self.pressed = 0 # when you release the mouse button, you can again 

Now you click and it will show the text only once per click

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.