Python Forum
Thread Rating:
  • 1 Vote(s) - 2 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Classes Pygame Help
#1
Hello,
I found a piece of code on the internet which creates an interactive menu. I modified it to use classes to make it more modular. It works, but the way it works isn't very Pythonic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import pygame as pg
 
class Control():
    def __init__(self, size=(480,320),caption=""):
        pg.init()
        self.size = size
        self.caption = caption
        self.screen = pg.display.set_mode(self.size)
        pg.display.set_caption(self.caption)
        self.clicked = False
        self.main_menu = Menu(self,["NEW GAME", "LOAD GAME", "OPTIONS"])
        self.main()
 
    def main(self):
        while True:
            pg.event.pump()
            self.screen.fill((0,0,0))
            self.clicked = False
            for event in pg.event.get():
                if event.type == pg.MOUSEBUTTONUP:
                    self.clicked = True
                elif event.type == pg.QUIT:
                    pg.quit
                    sys.exit()
            self.main_menu.update()
            pg.display.update()
             
 
class Menu(Control):
    def __init__(self,window,labels,font=None):
        self.font = pg.font.Font(font,40)
        self.options = []
        self.window = window
        self.screen = window.screen
        self.labels = labels
        self.startypos = self.window.size[1]//(len(labels)+1)
        self.middle = self.window.size[0]//2
        count = self.startypos
        for label in labels:
            self.options.append(Option(self,label,(self.middle,count)))
            count += self.startypos
 
    def update(self):
        for option in self.options:
            if option.rect.collidepoint(pg.mouse.get_pos()):
                option.hovered = True
                if self.window.clicked:
                    option.clicked()
            else:
                option.hovered = False
            option.draw()
 
class Option(Menu):
    def __init__(self,menu,text,pos):
        self.hovered = False
        self.menu = menu
        self.font = self.menu.font
        self.screen = self.menu.screen
        self.text = text
        self.pos = pos
        self.set_rect()
        self.draw()
 
    def draw(self):
        self.set_rend()
        self.screen.blit(self.rend,self.rect)
 
    def set_rend(self):
        self.rend = self.font.render(self.text,True,self.get_colour())
 
    def get_colour(self):
        if self.hovered:
            return (255,255,255)
        else:
            return (100,100,100)
 
    def set_rect(self):
        self.set_rend()
        self.rect = self.rend.get_rect()
        self.rect.center = self.pos
 
    def clicked(self):
        print(self.text)
 
 
app = Control()
I have searched for a way to write this where I don't have to pass the instances through as parameters, but I haven't found a way.

Thanks!
Reply
#2
Here another way.
example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import pygame
pygame.init()
 
class Scene:
    def blit(self, surface): pass
    def event(self, event): pass
 
class Game:
    fps = 0
    clock = None
    scenes = {}
    surface = None
    running = False
    next_scene = None
    current_scene = None
 
    @classmethod
    def setup(cls, caption, size):
        cls.clock = pygame.time.Clock()
        pygame.display.set_caption(caption)
        cls.surface = pygame.display.set_mode(size)
 
    @classmethod
    def loop(cls, intro_scene, fps=30):
        cls.fps = fps
        cls.running = True
        cls.next_scene = intro_scene
 
        while cls.running:
            if cls.next_scene:
                cls.current_scene = cls.scenes[cls.next_scene]
                cls.next_scene = None
 
            for event in pygame.event.get():
                cls.current_scene.event(event)
 
            cls.current_scene.blit(cls.surface)
            pygame.display.flip()
            cls.clock.tick(cls.fps)
 
class MenuItem:
    default_color = (0, 0 ,200)
    default_hcolor = (0, 200, 0)
    default_font = pygame.font.Font(None, 36)
 
    def __init__(self, name, callback, pydata=None):
        self.name = name
        self.hover = False
        self.pydata = pydata
        self.callback = callback
 
    def image(self, color=None, hcolor=None, font=None):
        self.hcolor = [hcolor, MenuItem.default_hcolor][hcolor is None]
        self.color = [color, MenuItem.default_color][color is None]
        self.font = [font, MenuItem.default_font][font is None]
 
        self.image = self.font.render(self.name, 1, self.color)
        self.hover_image = self.font.render(self.name, 1, self.hcolor)
        self.rect = self.image.get_rect()
        return self
 
class Menu:
    def __init__(self, rect, spacing=4):
        self.rect = rect
        self.items = []
        self.spacing = spacing
        self.location = 0
 
    def add(self, item):
        self.items.append(item)
 
    def blit(self, surface):
        for item in self.items:
            if not item.hover:
                surface.blit(item.image, item.rect)
            else:
                surface.blit(item.hover_image, item.rect)
 
    def event(self, event):
        if event.type == pygame.MOUSEMOTION:
            for item in self.items:
                if item.rect.collidepoint(event.pos):
                    item.hover = True
                else:
                    item.hover = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            for item in self.items:
                if item.rect.collidepoint(event.pos):
                    item.callback()                   
 
    # untested
    def align_left(self):
        x = self.rect.x + 1
        down = 1
 
        for item in self.items:
            item.rect.x = x
            item.rect.y = down
            down += item.font.get_linesize()
 
    def align_center(self):
        down = self.rect.height / 2 + self.rect.y
        for item in self.items:
            down -= (item.font.get_linesize() / 2 + self.spacing / 2)
 
        for item in self.items:
            item.rect.x = self.rect.x + self.rect.width / 2 - item.rect.width / 2
            item.rect.y = down
            down += item.font.get_linesize() + self.spacing
 
class Intro(Scene):
    def __init__(self):
        rect = Game.surface.get_rect()
        self.menu = Menu(rect.inflate(-100, -100))
        self.menu.add(MenuItem('New Game', self.menu_newgame).image())
        self.menu.add(MenuItem('Options', self.menu_options).image())
        self.menu.add(MenuItem('Quit', self.menu_quit).image(color=(150,0,0), hcolor=(250,0,0)))
        self.menu.align_center()
 
    def blit(self, surface):
        surface.fill((0,0,0))
        self.menu.blit(surface)
 
    def event(self, event):
        if event.type == pygame.QUIT:
            Game.running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                Game.running = False
        else:
            self.menu.event(event)
 
    def menu_newgame(self):
        print('Menu New Game')
 
    def menu_options(self):
        print('Menu Options')
 
    def menu_quit(self):
        Game.running = False
 
def main():
    Game.setup('Menu', (800, 600))
    Game.scenes['Intro'] = Intro()
    Game.loop('Intro')
    pygame.quit()
 
if __name__ == '__main__':
    main()
99 percent of computer problems exists between chair and keyboard.
Reply
#3
Your way is pretty similar to my way
https://python-forum.io/Thread-PyGame-Cr...te-machine

(Jul-09-2018, 09:21 AM)sam57719 Wrote: a way to write this where I don't have to pass the instances through as parameters
The window and menu options should not be passed back and forth between states. Each state should have its own selection of menu options. For example the main menu would have things like "Play", "Options", and "exit". But the Options menu would have things like "audio", "video", etc. So i am not sure why you are passing the same menu between them?
Recommended Tutorials:
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020