1

Edit: added a longer example code.

I'm having trouble with coding buttons in pygame. I'm a newbie to the pygame module, so be gentle.

Basically, the goal is to make a point-and-click telltale kind of game. The player gets presented with two choices each gameloop, ie "go left" or "go right". Accordingly, there are two buttons in each gameloop, all located at the same coordinates.

Here is the function:

import pygame
import os
import time

pygame.init()

display_width= 1280
display_height = 720

gameDisplay = pygame.display.set_mode((display_width, display_height))
clock = pygame.time.Clock()

def button(msg,x, y, w, h, ic, ac, action=None): #message, x y location, width, height, inactive and active colour
    if action ==None:
        pygame.display.update()
        clock.tick(15)
    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(gameDisplay, ac,(x,y,w,h))
        for event in pygame.event.get():
            if event.type == pygame.MOUSEBUTTONDOWN:
                pygame.display.update()
                clock.tick(15)
                if action == "left1":
                    game_loop("loop1-1.png",0,0,"left2","left2","","")
    else:
        pygame.draw.rect(gameDisplay, ic,(x,y,w,h))

    smallText = pygame.font.SysFont('timesnewroman',20)
    textSurf, textRect = text_objects(msg, smallText, silver)
    textRect.center = ( (x+(w/2)), (y+(h/2)) )
    gameDisplay.blit(textSurf, textRect)

def game_loop(pic,width,heigth,act1,act2,left,right):
    intro = True
    while intro:
        for event in pygame.event.get():
            print(event)
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        gameDisplay.fill(white)
        gameDisplay.blit(get_image(pic), (0, 0)) #MAIN MENU PIC

        button(left,440,450,width,heigth, dark_gray, gray, action=act1)#start nupp
        button(right,740,450,width,heigth, dark_gray, gray, action=act2)#exit nupp

        pygame.display.update()
        clock.tick(15)

The problem occurs when I click the button carelessly, meaning if I don't purposefully click on the left mouse button as fast as I can. Once game_loop1 is called and if I click for a bit longer, the program will read the first click again in this game_loop1 and run the next game_loop and then the next etc. This means the player can accidentally skip through gameloops.

Is there a way to delay the program after the first click(however long it is)? Or maybe a way to include keyup in the function, so it won't count the click in the next gameloop?

Thanks!

Tuts
  • 79
  • 1
  • 10
  • Welcome to SO! Could you try to turn your code into a [minimal, runnable example](https://stackoverflow.com/help/mcve)? That would make it easier for us to figure out how the code works, where the problem lies and to give you tips on how to fix them. – skrx Dec 11 '17 at 11:48
  • 2
    It looks like your main problem is `pygame.mouse.get_pressed()`. It checks if a mouse button is held down, but you only want to know if the button was clicked once. That means you should rather use an event loop `for event in pygame.event.get()` and check `if event.type == pygame.MOUSEBUTTONDOWN`. I'll try to prepare a minimal example. Edit: I've recently posted some examples [here](https://stackoverflow.com/a/47664205/6220679). – skrx Dec 11 '17 at 11:51
  • BTW, where did you find that code? It's almost the same as in the [linked question](https://stackoverflow.com/q/47639826/6220679). I should probably mark this question as a duplicate. – skrx Dec 11 '17 at 12:43
  • 1
    Possible duplicate of [Pygame button single click](https://stackoverflow.com/questions/47639826/pygame-button-single-click) – skrx Dec 11 '17 at 13:12
  • I hope you will read the linked answer, because the suggestions apply to your question as well. (I've just added another solution with dictionaries.) My main suggestion is that you shouldn't write a function that does everything and rather store the relevant items (rect, text surface, etc.) in an appropriate data structure (list, dict, Button class), handle the events in the event loop and only draw the buttons with a separate function. – skrx Dec 11 '17 at 14:04
  • Thanks for the info! Will try your suggestions in the other thread once I get the chance. Seems like a probable solution. Also, the button function is possibly very similar due to it being from the same source. It's a function from a good pygame tutorial on Youtube. – Tuts Dec 13 '17 at 22:15
  • Could you give me a link to the video? – skrx Dec 14 '17 at 04:41
  • https://www.youtube.com/watch?v=K5F-aGDIYaM&list=PL6gx4Cwl9DGAjkwJocj7vlc_mFU-4wXJq Here's the playlist. The suggestion on the other page worked partly, meaning that it doesn't click through all the loops now, but the button function now works at random times. I have to click multiple times to get to the next loop and it always proceeds whenever, at a random click. So I don't really know how to continue now. – Tuts Dec 20 '17 at 19:58
  • If you add your latest code to your question, I'll take a look at it. A [minimal, complete and verifiable example](https://stackoverflow.com/help/mcve) would be helpful. – skrx Dec 20 '17 at 20:07
  • Added one, has your suggestion from the other topic added in. Let me know if it makes sense. – Tuts Dec 20 '17 at 21:09

1 Answers1

0

I think your original code is a bit too convoluted to fix it and I'll rather show you some better ways to do what you want. You need a finite-state machine to transition between different states/scenes. You can find a simple example with functions as scenes here.

If the logic in your scenes is mostly the same, you could also try to just swap out the data for the scene, for example the background image. Each state/scene needs to know to which new states it can switch, so I put the data into a dictionary of dictionaries. The nested dicts contain the background image of the scene and the connected left and right scenes. When the user presses a button/rect I check if it was the left or right button and then switch to the corresponding scene (subdict) in the states dictionary.

import pygame


pygame.init()

display_width= 1280
display_height = 720

gameDisplay = pygame.display.set_mode((display_width, display_height))
clock = pygame.time.Clock()

# Use uppercase names for constants that should never be changed.
DARK_GRAY = pygame.Color('gray13')
BACKGROUND1 = pygame.Surface((display_width, display_height))
BACKGROUND1.fill((30, 150, 90))
BACKGROUND2 = pygame.Surface((display_width, display_height))
BACKGROUND2.fill((140, 50, 0))
BACKGROUND3 = pygame.Surface((display_width, display_height))
BACKGROUND3.fill((0, 80, 170))

states = {
    'scene1': {'background': BACKGROUND1, 'left_scene': 'scene2', 'right_scene': 'scene3'},
    'scene2': {'background': BACKGROUND2, 'left_scene': 'scene1', 'right_scene': 'scene3'},
    'scene3': {'background': BACKGROUND3, 'left_scene': 'scene1', 'right_scene': 'scene2'},
    }

def game_loop():
    # The buttons are just pygame.Rects.
    left_button = pygame.Rect(440, 450, 60, 40)
    right_button = pygame.Rect(740, 450, 60, 40)
    # The current_scene is a dictionary with the relevant data.
    current_scene = states['scene1']

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            elif event.type == pygame.MOUSEBUTTONDOWN:
                # If the left button is clicked we switch to the 'left_scene'
                # in the `current_scene` dictionary.
                if left_button.collidepoint(event.pos):
                    current_scene = states[current_scene['left_scene']]
                    print(current_scene)
                # If the right button is clicked we switch to the 'right_scene'.
                elif right_button.collidepoint(event.pos):
                    current_scene = states[current_scene['right_scene']]
                    print(current_scene)

        # Blit the current background.
        gameDisplay.blit(current_scene['background'], (0, 0))
        # Always draw the button rects.
        pygame.draw.rect(gameDisplay, DARK_GRAY, left_button)
        pygame.draw.rect(gameDisplay, DARK_GRAY, right_button)
        pygame.display.update()
        clock.tick(30)  # 30 FPS feels more responsive.


game_loop()
pygame.quit()
skrx
  • 19,980
  • 5
  • 34
  • 48
  • Feel free to ask if you need further explanations. – skrx Dec 21 '17 at 14:55
  • Thanks for the post! Your way is absolutely better, but due to time constraint I did it the long and dumb way just to get it done. I found the answer to my problem and it seems to agree with your post - I just needed to up the framerate! I changed it to 60 fps, however, just in case. – Tuts Dec 21 '17 at 17:58
  • I think the main problem in your `button` function was that you used `pygame.mouse.get_pressed()`, because it just checks if the mouse button is currently pressed. If you use the `pygame.MOUSEBUTTONDOWN` events in the event loop instead, you can check if the user clicked once. For each mouse click, only one `MOUSEBUTTONDOWN` event is added to the event queue. – skrx Dec 21 '17 at 18:02
  • I agree. And 15 fps isn't nearly enough. I appreciate your quick answers! – Tuts Dec 21 '17 at 19:00