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.
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
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