RidiculousName 255 Posted February 21, 2017 (edited) Is there an elegant way to create buttons in PyGame? I want to create the main menu for a game I'm making, but I can't figure out a way to check if the mouse pointer is inside the Rect. Is there some elegant way to do this? A code sample is below import pygame as pg import os from pygame.locals import * def dispMainMenu(SURF): """ displays main menu background and buttons """ #loads main menu background image MainMenu_BG = pg.image.load("images\mMenu\MainMenu_BG.png") #displays main menu background SURF.blit(MainMenu_BG, (0, 0)) #assigns main menu buttons mmContinueButt = pg.image.load(os.path.join("images\mMenu\continue.png")) mmStartButt = pg.image.load(os.path.join("images\mMenu\start.png")) mmLoadButt = pg.image.load(os.path.join("images\mMenu\load.png")) mmOptionsButt = pg.image.load(os.path.join("images\mMenu\options.png")) mmLeaveButt = pg.image.load(os.path.join("images\mMenu\leave.png")) #assigns mouse position to rat rat = pg.mouse.get_pos() #blits main menu buttons to screen #assigns them as rects SURF.blit(mmContinueButt,((190, 260), (281, 86))) SURF.blit(mmStartButt,((190, 380), (281, 86))) SURF.blit(mmLoadButt,((190, 500), (281, 86))) SURF.blit(mmOptionsButt,((190, 620), (281, 86))) SURF.blit(mmLeaveButt,((190, 740), (281, 86))) Edited February 21, 2017 by RidiculousName 0 Share this post Link to post Share on other sites
Alberth 9604 Posted February 22, 2017 (edited) Sorry, I don't have time for a precise example, below is some copy/paste from an application I wrote, as inspiration # Wrap all button data in a class. class Button(object): def __init__(self, x, y, w, h, text, colour=None): if colour is None: colour = NORMAL self.normal_colour = colour self.x = x self.y = y self.w = w self.h = h self.font = pygame.font.Font(None, 20) self.text = text self.shiny = False def draw(self, screen): if self.shiny: bg = SHINY else: bg = self.normal_colour surf = self.font.render(self.text, True, BLACK, bg) rect = (self.x, self.y, self.w, self.h) xo = self.x + (self.w - surf.get_width()) // 2 yo = self.y + (self.h - surf.get_height()) // 2 screen.fill(bg, rect) screen.blit(surf, (xo, yo)) def on_button(self, pos): return self.x <= pos[0] and self.x + self.w > pos[0] and \ self.y <= pos[1] and self.y + self.h > pos[1] # Make list of buttons in the program buttons.append(Button(x, y, w, sh, '7d', colour=NORMAL_LARGE)) # Mouse click handler def update_click(pos): """ Mouse got clicked Returns whether a change occurred. """ global disable_select selected_found = None selected_set = None for elm in buttons: if elm.on_button(pos): # do magic, like toggling elm.shiny to highlight the button etc Obviously, you can do the same trick but with buttons with images. Hope this helps! By the way, the \ is an escape character in strings, so your Windows paths to the images won't work if you pick an unlucky name. Use raw strings like r"path\to\image.png", use double backslashes like "path\\to\\image.png", or use slashes like "path/to/image.png". Edited February 22, 2017 by Alberth 2 Share this post Link to post Share on other sites
RidiculousName 255 Posted April 11, 2017 Thanks for this. I realized I couldn't do much with pygame until I understood classes. I understand them now (barely) but some things about your post still confuse me. What does self.shiny represent? It seems like you're creating new x and y values for blitting the image to the window. Why is that? code example: xo = self.x + (self.w - surf.get_width()) // 2 yo = self.y + (self.h - surf.get_height()) // 2 screen.blit(surf, (xo, yo)) Do you declare button with the object parameter because this was originally a child class? Lastly, is it possible to make the update_click function a method of the button instead? I want to call this class in multiple py files. 0 Share this post Link to post Share on other sites
Alberth 9604 Posted April 12, 2017 I realized I couldn't do much with pygame until I understood classes. You can work without classes too, by storing all data in tuples or dictionaries, but classes have a lot of nice features (and are much less cumbersome to write and modify), so they're useful to know in general. Nobody said you must use all their features, it's fine to use a small subset only at first, and slowly expand your knowledge. What does self.shiny represent? Good point, it's a change in background colour (bg==background). It's a flag that is set when the button is visited by the mouse pointer iirc, and it gives the button a whiter background to give visual feedback to the user that the mouse is over it. I tried it first without this feedback, but the interface felt a bit dead. In hindsight, it should probably not be a flag of the button itself, I might change that at some point. It seems like you're creating new x and y values for blitting the image to the window. Why is that? The image is the text of the button, which I want centered on the button. I don't know the size of the image until I create it with the text-draw method "font.render". That code computes the top-left corner of the text-image at the screen. I agree it's the same image and therefore the same size every single time, so you could compute it once, and store the result, but my application renders the buttons around once a minute (a bit more often when moving the mouse over it), so execution time is a non-issue in my application. Do you declare button with the object parameter because this was originally a child class? It's Python2, which requires deriving from object since around 2.3 or so, to get a sanely behaving class. In Python3, they fixed that, and you can just write "class Button:" Lastly, is it possible to make the update_click function a method of the button instead? I want to call this class in multiple py files. Not really, as it has a different function. The "Button" class represents a single button that you can select (by clicking). The "update_click" function gets called when you press the LMB. The "pos" gives the position of the mouse at the moment you clicked. The function then walks over all buttons "for elm in buttons:", and asks each button "is this position you?" with 'if elm.on_button(pos):'. If the button answers yes (True), the function can eg set the 'shiny' flag for the button that said 'yes', and clear 'shiny' at all other buttons, and then redraw the entire display (thus letting the user know the button was selected). Likely, you want something else than having shiny buttons, that requires programming some other code below the "if". The update_click function thus handles all buttons at the screen, rather than a single button like the Button class does. For this reason, the update_click doesn't belong in the Button class. Calling a function or using a class definition from several files can be done with an import statement. You import the file containing the function or class definition, and then you can access it. 2 Share this post Link to post Share on other sites
RidiculousName 255 Posted April 30, 2017 (edited) Okay, I'm back. I couldn't find a tutorial on blitting an image as a button so this is what I came up with. Unfortunately, it doesn't work. At least, not the way I planned. It keeps raising an error when I try to use my program. The error is: Traceback (most recent call last): File "N:/Aztlan/aztlan.py", line 8, in <module> from button import Button as b File "N:\Aztlan\button.py", line 39 self._index = 0 ^ SyntaxError: invalid syntax I just want to iterate through a list of Button class objects but it's giving me a hard time. my Button class (in button.py) import pygame as pg class Button: """ Creates a button """ _buttList = [] def __init__(self, x, y, w, h, imgAddr, name): """ x and y are ints and indicate where the top right corner of the button will be w is an int that represents how wide the button will be h is an int that represents the height of the button imgAddr is a string that holds the image address on it it is used to create a pygame image object name is a string indicating button function """ # inits self._image to pygame image object self._image = pg.image.load(imgAddr) #pygame x y coordinates self._x = x self._y = y #width and height of button self._w = w self._h = h self._buttList = [] self._buttList.append((self._x, self._y, self._w, self._h) self._index = 0 def draw(self, surf): """ draws a button the window :param surf: surf is the pygame surface window the button is drawn to """ # draws button to screen surf.blit(self._image, ((self._x, self._y), (self._w, self._h))) def overButton(self, pos): """ checks where the mouse is :param pos: pygame mouse object that indicates where the mouse is """ return self._x <= pos[0] and self._x + self._w > pos[0] and \ self._y <= pos[1] and self._y + self._h > pos[1] def __iter__(self): return self def __next__(self): try: result = self._buttList[self._index] except IndexError: raise StopIteration self._index += 1 return result def __getitem__(self, index): """ :param index: int that finds proper index """ return Button._buttList[index] my MainMenu class (in screens.py) """ Contains classes to handle the various screens of the game """ __author__ = "RidiculousName" __date__ = "4/22/17" #imports Button class from button import Button as b import pygame as pg class MainMenu: """ Class that handles the main menu screen """ def __init__(self, surf): """ surf is a pygame.surface object that the buttons, images, etc. are blitted to isClick is a pygame mouse object that determines whether the mouse has been clicked """ # where main menu images are located self._i = "images/mainMenu/" self._surf = surf self._surf.fill((0,0,0)) #fill surface with white #list of main menu buttons # x value, y value, width, height, image path, surface, button type self._mmList = [ b(150, 300, 200, 80, self._i + "mmResume.png", "Resume"), b(150, 400, 200, 80, self._i + "mmStart.png", "Start"), b(150, 500, 200, 80, self._i + "mmLoad.png", "Load"), b(150, 600, 200, 80, self._i + "mmCreate.png", "Create"), b(150, 700, 200, 80, self._i + "mmExit.png", "Exit"), b(600, 100, 400, 160, self._i + "mmTitle.png", "Title") ] # blits buttons to screen for ei in self._mmList: b.draw(ei,surf) def butts(self, isClick): if isClick[0] == True: rat = pg.mouse.get_pos() for ei in self._mmList: for item in ei: # if button-X + button Width > mouse-X > button-X # if button-Y + button Width > mouse-Y > button-Y print(ei[1]) print(type(ei[1])) print(self._mmList) if ei[0] + ei[2] > rat[0] > ei[0] \ and ei[1] + ei[3] > rat[1] > ei[1]: if ei[-1] == "Exit": return pg.QUIT my main (put it all together) file: (in aztlan.py) """ Combines classes to run the game """ __author__ = "RidiculousName" __Date__ = "4/22/17" import pygame as pg from button import Button as b from screens import * def main(): """ set up the game and run the main game loop """ pg.init() # prepare pygame module for use surfW = 1600 # window width surfH = 900 # window height # create window surf = pg.display.set_mode((surfW, surfH)) #set window caption pg.display.set_caption("Aztlan") while True: #opens first screen mm = MainMenu(surf) mm.butts(pg.mouse.get_pressed()) ev = pg.event.poll() # look for any event if ev.type == pg.QUIT: # window close button clicked? break pg.display.flip() pg.quit() if __name__ == "__main__": main() Edited April 30, 2017 by RidiculousName 0 Share this post Link to post Share on other sites
Alberth 9604 Posted May 1, 2017 File "N:\Aztlan\button.py", line 39 self._index = 0 ^ SyntaxError: invalid syntax With syntax errors near the start of the line it usually pays to check the previous line too: self._buttList.append((self._x, self._y, self._w, self._h) Seems a missing closing parenthesis to me :) 1 Share this post Link to post Share on other sites
RidiculousName 255 Posted May 2, 2017 (edited) self._buttList.append((self._x, self._y, self._w, self._h) Seems a missing closing parenthesis to me :) Urk, that was a really dumb mistake on my part. A friend and I got the indexing to work, but there are still problems. I want to work on them a little myself before asking again, but thank you. You've really helped me here. Edited May 2, 2017 by RidiculousName 0 Share this post Link to post Share on other sites
Alberth 9604 Posted May 3, 2017 Thanks for the update. Trying to fix problems on your own works much better for learning, so by all means, experiment away as much as you can! Unfortunately, it's a way of programming I can't really do anymore. I have coded so much, that with anything I write, I immediately see 3-5 things that I need to handle as well to make things work in the next step, and it's very hard to ignore them. Good luck with fixing, if you want feedback or have a question you know where to go :) 0 Share this post Link to post Share on other sites