Pygame, best way to implement buttons?

2019-06-11 04:56发布

问题:

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.

It is essentially a tower defense style game where enemies spawn in rows and I need to be able to place objects to block/remove them.

The issue We're coming across is that

  1. we can't get our buttons to draw on screen and

  2. how to then spawn a troop to the screen after the click. I assume it would be done in a way if the button was clicked then it will wait for a mouse input on the grid to spawn the troop?

I've added the code for button below. We know we're missing something but are unclear of what that is.

button1 = button_image
button1.rect = pygame.Rect(100, 100, 50, 50)
screen.blit(button1)
if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_pos = event.pos

        if button1.collidepoint(mouse_pos):
            print('button was pressed at {0}'.format(mouse_pos))

回答1:

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()



回答2:

for getting buttons on screen, try drawing them as rectangles, (pygame.draw.rect()) then you can do:
if mouse_pos.colliderect(variable for button name) scree.blit(troops[x, y])



标签: python pygame