Please note that I used python 2.7 for this tutorial. If you are using python3.x, this code may not work. I have adapted this tutorial to Python3 in this blog post.
Ok, folks it’s been awhile since I wrote my first part of Games Menu tutorial with pygame, but serendipity didn’t kiss me since then. I am trying to make up for this with the second part of my tutorial. We will cover the recognition of mouse movement. Again, like last time, the entire code is at the end of this blog entry.
The last time, we wrote the options we wanted to the screen and centred them. We used a list of list to do this. Today, we will start off by changing this list of list for a list of objects (MenuItems). This makes the entire thing easier to handle and clearer.
So just add this piece of code in between your imports and the GameMenu
class.
class MenuItem(pygame.font.Font): def __init__(self, text, font=None, font_size=30, font_color=(255, 255, 255), (pos_x, pos_y)=(0, 0)): pygame.font.Font.__init__(self, font, font_size) self.text = text self.font_size = font_size self.font_color = font_color self.label = self.render(self.text, 1, self.font_color) self.width = self.label.get_rect().width self.height = self.label.get_rect().height self.pos_x = pos_x self.pos_y = pos_y self.position = pos_x, pos_y def set_position(self, x, y): self.position = (x, y) self.pos_x = x self.pos_y = y
and replace this section:
for index, item in enumerate(items): label = self.font.render(item, 1, font_color) width = label.get_rect().width height = label.get_rect().height posx = (self.scr_width / 2) - (width / 2) # t_h: total height of text block t_h = len(items) * height posy = (self.scr_height / 2) - (t_h / 2) + (index * height) self.items.append([item, label, (width, height), (posx, posy)])
for the following
for index, item in enumerate(items): menu_item = MenuItem(item) # t_h: total height of text block t_h = len(items) * menu_item.height pos_x = (self.scr_width / 2) - (menu_item.width / 2) # This line includes a bug fix by Ariel (Thanks!) # Please check the comments section for an explanation pos_y = (self.scr_height / 2) - (t_h / 2) + ((index * 2) + index * menu_item.height) menu_item.set_position(pos_x, pos_y) self.items.append(menu_item)
and finally in the while mainloop:
loop
for name, label, (width, height), (posx, posy) in self.items: self.screen.blit(label, (posx, posy))
for
for item in self.items: self.screen.blit(item.label, item.position)
Please have a look at what has been exchanged and you will find that it is essentially still all the same. All I did, was move all the info for the Font
class into a separate class called MenuItem
.
If you have questions about this change, please leave a comment with the sections, that need clarification.
The next point will be the recognition of whether the mouse pointer hovers over one of our menu items or not. To do this all we need to is figure out if the position of the mouse is within the boundaries. To this end, we will introduce a new method to our MenuItem
class, called is_mouse_selection()
.
def is_mouse_selection(self, (posx, posy)): if (posx >= self.pos_x and posx <= self.pos_x + self.width) and \ (posy >= self.pos_y and posy <= self.pos_y + self.height): return True return False
This may look like much, but essentially we are just asking if mouse coordinates x and y are between the left and right border and the top and bottom border of MenuItem
, respectively.
Furthermore, we obviously want something to happen, so that we know that something has been selected. This can be anything you want. I chose to change the color of the font. This is quite easy. We just need to write a short setter method and append it to the MenuItem
class once more.
def set_font_color(self, rgb_tuple): self.font_color = rgb_tuple self.label = self.render(self.text, 1, self.font_color)
I hope you can see by now, why I chose to go away from a list of values to an actual object. It makes it all this little bit easier 😉 Now we only need to put this all together and see the great effect. I chose red as a color and for the fun of it on selection, I let the font be italic, too. This section is put into the while mainloop
loop. I am sure you can figure out in which section (Otherwise, see below)
for item in self.items: if item.is_mouse_selection(pygame.mouse.get_pos()): item.set_font_color((255, 0, 0)) item.set_italic(True) self.screen.blit(item.label, item.position)
If you have tried this out, you will have sure noticed that once you hovered over an item it will stay red and italic. That’s not really what we want, right? We obviously have to return to neutral, too.
else: item.set_font_color((255, 255, 255)) item.set_italic(False)
And there you go! We now recognise the menu items by mouse 🙂 Simple, isn’t it.
——————————————–
The entire code:
#!/usr/bin/python import pygame pygame.init() class MenuItem(pygame.font.Font): def __init__(self, text, font=None, font_size=30, font_color=(255, 255, 255), (pos_x, pos_y)=(0, 0)): pygame.font.Font.__init__(self, font, font_size) self.text = text self.font_size = font_size self.font_color = font_color self.label = self.render(self.text, 1, self.font_color) self.width = self.label.get_rect().width self.height = self.label.get_rect().height self.dimensions = (self.width, self.height) self.pos_x = pos_x self.pos_y = pos_y self.position = pos_x, pos_y def set_position(self, x, y): self.position = (x, y) self.pos_x = x self.pos_y = y def set_font_color(self, rgb_tuple): self.font_color = rgb_tuple self.label = self.render(self.text, 1, self.font_color) def is_mouse_selection(self, (posx, posy)): if (posx >= self.pos_x and posx <= self.pos_x + self.width) and \ (posy >= self.pos_y and posy <= self.pos_y + self.height): return True return False class GameMenu(): def __init__(self, screen, items, bg_color=(0,0,0), font=None, font_size=30, font_color=(255, 255, 255)): self.screen = screen self.scr_width = self.screen.get_rect().width self.scr_height = self.screen.get_rect().height self.bg_color = bg_color self.clock = pygame.time.Clock() self.items = [] for index, item in enumerate(items): menu_item = MenuItem(item)#, '/home/nebelhom/.fonts/SHOWG.TTF') # t_h: total height of text block t_h = len(items) * menu_item.height pos_x = (self.scr_width / 2) - (menu_item.width / 2) # This line includes a bug fix by Ariel (Thanks!) # Please check the comments section for an explanation pos_y = (self.scr_height / 2) - (t_h / 2) + ((index * 2) + index * menu_item.height) menu_item.set_position(pos_x, pos_y) self.items.append(menu_item) def run(self): mainloop = True while mainloop: # Limit frame speed to 50 FPS self.clock.tick(50) for event in pygame.event.get(): if event.type == pygame.QUIT: mainloop = False # Redraw the background self.screen.fill(self.bg_color) for item in self.items: if item.is_mouse_selection(pygame.mouse.get_pos()): item.set_font_color((255, 0, 0)) item.set_italic(True) else: item.set_font_color((255, 255, 255)) item.set_italic(False) self.screen.blit(item.label, item.position) pygame.display.flip() if __name__ == "__main__": # Creating the screen screen = pygame.display.set_mode((640, 480), 0, 32) menu_items = ('Start', 'Quit') pygame.display.set_caption('Game Menu') gm = GameMenu(screen, menu_items) gm.run()
Pingback: Create a simple game menu with pygame pt. 3 – Recognising the keyboard | (Auto-)Didactic Programming
I’m having issue with the posx and posy values. In line 10 I had to change (pos_x, pos_y)=(0, 0) to pos_x=0 , pos_y=0 to make it work.
Now after adding in the code for mouseover highlighting there is issue with Line 32 having invalid syntax. This is the second time now that Python doesn’t like the (x,y) format in the initial “def” line of a method.
I could get around the first instance, but I’m not sure how to troubleshoot the mouseover highlighting process because the game returns an answer in the (x,y) format when we run pygame.mouse.get_pos(). I’m running Python 3.3.
Any ideas?
Hi SWJ,
your problem is version based. I used python2.7 while you use python3.3. Bear in mind that version 3.x is not backward compatible with 2.x (which is also the reason why many people are slow to adopt 3.x unfortunately). They tried to iron out inconsistencies with the new version.
To solve your problem, I would suggest you read PEP 3113. Please see http://www.python.org/dev/peps/pep-3113/.
Hope that helps. I will make a note in the articles that version 2.7 was used. Sorry about the confusion.
I know this is a late comment, but I just thought I’d point out a simple bug and its solution, that you may or may not have addressed in the next part.
The bug:
When you position the mouse just at the end of the option ‘Start’ and above the option ‘Quit’, I mean at the very bottom pixel of ‘Start’, you end up having BOTH options highlighted.
The fix:
This line in GameMenu’s __init__:
pos_y = (self.scr_height / 2) – (t_h / 2) + (index * menu_item.height)
changed into:
pos_y = (self.scr_height / 2) – (t_h / 2) + ((index*2) + index * menu_item.height)
index*2 will equal zero on the first menu item so the position should still be the same as before,
but on the next enumeration it will equal to two and add a couple of pixel’s space between ‘Start’
and ‘Quit’.
@Ariel
It is true that I have not looked at this blog for quite some time, but every comment is welcome.
I will try your suggestion out myself first and see how it works for me. Once I am content, I will annotate the text with your contribution. Thanks for your comment. It’s always good to know that people are reading the stuff I put up 😉 Keep on enjoying to program 🙂
Happy to help, this is a very enlightening tutorial and anything I can do to thank you for writing it will make me happy.
I should mention that this line still needs fixing on the other parts. 😛
If only because I am a pedantic person.
Hi,
Using Python 3.5.0 and struggling to get this to run – keep getting the below error:
if item.is_mouse_selection(pygame.mouse.get_pos()):
TypeError: is_mouse_selection() missing 1 required positional argument: ‘posy’
I have tried with both:
pos_y = (self.scr_height / 2) – (t_h / 2) + (index * menu_item.height)
pos_x = (self.scr_width / 2) – (menu_item.width / 2)
pos_y = (self.scr_height / 2) – (t_h / 2) + ((index * 2) + index * menu_item.height)
Is this due to the version of Python I am using? What am I doing wrong please?
Hi Danny,
I’m not really familiar with python 3. I never made the transition.
Having said that a quick Google search on your error message gave me this link:
http://stackoverflow.com/questions/20815887/missing-1-required-positional-argumentself
According to this you did not create an instance of the class containing the method that you want to use. Maybe check if you did this.
Just in case, you create an instance like so:
instance = class()
I hope that helps…