Pygame, best way to implement buttons?

后端 未结 1 1695

I am working on a pygame project with a group where we need to be able to click a button to select a class of objects, and then have that object be placed down on to a grid.

1条回答
  •  孤街浪徒
    2021-01-07 08:52

    To make this work, let's think about what we need.

    We need something to represent a button: a button has a text, and an action that is invoked when we click it.

    Since something happens when a button is clicked, we need to somehow represent the fact that the game has different game states: let's describe them as 'the game is running and buttons can be pressed' and 'the player has to select a position for something'.

    Here's how it could look like. Note the comments (I assume basic pygame knowledge of how the Surface, Rect and Sprite classes etc. work).

    import pygame
    import random
    
    # this class is a container for 
    # our game state and all the sprites
    class Game:
        def __init__(self, font):
            self.font = font
            # currently we have 2 states: RUNNING and SELECT_POSITION
            self.state = 'RUNNING'
    
            # a sprite group for all sprites (+ UI)
            self.sprites = pygame.sprite.Group()
    
            # a sprite group for all game objects (- UI)
            self.actors = pygame.sprite.Group()
            self.callback = None
    
        def update(self, events, dt):
            for event in events:
                if event.type == pygame.MOUSEBUTTONDOWN:
    
                    # if we're in SELECT_POSITION, a mouse click will
                    # change the game state back to RUNNING
                    # we pass the mouse position to the callback
                    # so the action actually happens
                    if self.state == 'SELECT_POSITION' and self.callback:
                        self.callback(event.pos)
                        self.state = 'RUNNING'
    
            # just update the sprites
            self.sprites.update(events, dt)
    
        def draw(self, screen):
            # usually, the background is black, but to give the player 
            # a visual clue that they have to do something, let's change
            # it to grey when we're in the SELECT_POSITION mode
            screen.fill(pygame.Color('black' if self.state == 'RUNNING' else 'grey'))
    
            # just draw all the sprites
            self.sprites.draw(screen)
    
            # just some info text for the player
            if self.state == 'SELECT_POSITION':
                screen.blit(self.font.render('Select a position', True, pygame.Color('black')), (150, 400))
    
        # a button can call this function when the action that should be invoked
        # needs a position that the player has to choose
        def select_position(self, callback):
            self.state = 'SELECT_POSITION'
            self.callback = callback
    
    # just a little square guy that walks around the screen
    # nothing special happens here
    class WalkingRect(pygame.sprite.Sprite):
        def __init__(self, pos, color, game):
            super().__init__(game.sprites, game.actors)
            self.image = pygame.Surface((32, 32))
            self.image.fill(pygame.Color(color))
            self.rect = self.image.get_rect(center=pos)
            self.pos = pygame.Vector2(pos)
            self.direction = pygame.Vector2(random.choice([-1,0,1]), random.choice([-1,1])).normalize()
    
        def update(self, events, dt):
            self.pos += self.direction * dt/10
            self.rect.center = self.pos
            if random.randint(0, 100) < 10:
                self.direction = pygame.Vector2(random.choice([-1,0,1]), random.choice([-1,1])).normalize()
    
    # the actuall Button class
    # it takes an action that is invoked when the player clicks it
    class Button(pygame.sprite.Sprite):
        def __init__(self, pos, color, text, game, action):
            super().__init__(game.sprites)
            self.color = color
            self.action = action
            self.game = game
            self.text = text
            self.image = pygame.Surface((150, 40))
            self.rect = self.image.get_rect(topleft=pos)
            self.fill_surf(self.color)
    
        def fill_surf(self, color):
            self.image.fill(pygame.Color(color))
            self.image.blit(self.game.font.render(self.text, True, pygame.Color('White')), (10, 10))
    
        def update(self, events, dt):
    
            # the player can only use the button when the game is in the RUNNING state
            if self.game.state != 'RUNNING':
                self.fill_surf('darkgrey')
                return
    
            self.fill_surf(self.color)
            for event in events:
                if event.type == pygame.MOUSEBUTTONDOWN:
                    if self.rect.collidepoint(event.pos):
                        # if the player clicked the button, the action is invoked
                        self.action(self.game)
    
    def main():
        pygame.init()
        screen = pygame.display.set_mode((500, 500))
        screen_rect = screen.get_rect()
        font = pygame.font.SysFont(None, 26)
        clock = pygame.time.Clock()
    
        game = Game(font)
    
        # the action for the green button
        # when invoked, as the game for the player
        # to select a position, and spawn a green
        # guy at that position
        def green_action(game_obj):
            def create_green(pos):
                WalkingRect(pos, 'green', game_obj)
            game_obj.select_position(create_green)
    
        # the same but spawn a red guy instead
        def red_action(game_obj):
            def create_red(pos):
                WalkingRect(pos, 'darkred', game_obj)
            game_obj.select_position(create_red)
    
        Button((10, 10), 'green', 'CREATE GREEN', game, green_action)
        Button((10, 50), 'darkred', 'CREATE RED', game, red_action)
    
        # a button to kill all guys
        # just to show how generic our buttons are
        Button((10, 90), 'red', 'KILL', game, lambda game_obj: [x.kill() for x in game_obj.actors])
    
        # classic boring main loop
        # just updates and draws the game
        dt = 0
        while True:
            events = pygame.event.get()
            for e in events:
                if e.type == pygame.QUIT:
                    return
    
            game.update(events, dt)
            game.draw(screen)
    
            pygame.display.flip()
            dt = clock.tick(60)
    
    if __name__ == '__main__':
        main()
    

    0 讨论(0)
提交回复
热议问题