Pygame Sprites doesn't detect collision

I've made a start menu and a menu screen. The start screen has a single Button Sprite. After clicking, it creates the correction animation then goes to menu screen as expected. However, this menu screen has two Button Sprites. It seems whichever Button Sprite I add first to the group is clickable, but the other isn't. I'm not sure why this odd behavior exists.

At this piece of the code is where I notice it messes up:

        elif event.type == MOUSEBUTTONDOWN:
            #HERE IS THE ISSUE
            print("size of all_sprites after clicking:  " + str(len(all_sprites)))
            print("     info on all_sprites:  " + str(all_sprites))
            for but in all_sprites:
                print("         sprite info:  " + str(but))
                if isinstance(but, (Button)) and mouse.click(but):
                    but.set_clicked()
                    print("             clicked:  " + str(but))

When iterating over the Sprites group and calling this method:

mouse.click(but)

It only works for the first Button added. When I try clicking the other Button, it never returns True. I'm not sure why.

Here is the relevant objects:

#seperate this into an object class
class Mouse(pygame.sprite.Sprite):
    """moves a hand on the screen, following the computer mouse"""
    def __init__(self):
        pygame.sprite.Sprite.__init__(self) #call Sprite initializer
        self.clicking = 0
        self.image, self.rect = util.load_image('cursor.png',-1)

    def update(self):
        "move the hand based on the computer mouse position"
        pos = pygame.mouse.get_pos()
        self.rect.midtop = pos
        if self.clicking:
            pygame.mixer.Sound("data/high_tone_sword.wav").play()
            self.rect.move_ip(5, 10)

    def click(self, target):
        "returns true if the hand collides with the target"
        if not self.clicking:
            self.clicking = 1
            hitbox = self.rect.inflate(-5, -5)
            return hitbox.colliderect(target.rect)

    def unclick(self):
        self.clicking = 0


#seperate this into an object/menu class
class Button(pygame.sprite.Sprite):
    """Button class used for all menus.  Handles events"""
    def __init__(self, original_image_source, clicked_image_source, location_coordinates):
        pygame.sprite.Sprite.__init__(self) #call Sprite intializer
        self.original_image_source = original_image_source
        self.clicked_image_source = clicked_image_source
        self.location_coordinates = location_coordinates
        self.clicked = 0
        self.image, self.rect = util.load_image(original_image_source, None)
        self.rect.midtop = location_coordinates

    def update(self):
        "update on click or unclick"
        if self.clicked:
            self.image, self.rect = util.load_image(self.clicked_image_source, None)
            self.rect.midtop = self.location_coordinates
        else:
            self.image, self.rect = util.load_image(self.original_image_source, None)
            self.rect.midtop = self.location_coordinates

    def set_clicked(self):
        self.clicked = 1

    def set_unclicked(self):
        self.clicked = 0

Here is the driver:

def main():

    #initialise screen
    pygame.init()
    screen = pygame.display.set_mode((1000, 600))
    pygame.display.set_caption('States of Matter')
    pygame.mouse.set_visible(0)

    #music!!!
    pygame.mixer.music.load("music/Glorious Morning 2.mp3")
    pygame.mixer.music.play(-1, 53.0)

    background = pygame.Surface(screen.get_size(), pygame.SRCALPHA, 32)
    background = background.convert()
    background.fill((250, 250, 250))

    elements_background = util.load_image("4-elements-background.jpg")


    if pygame.font:
        font = pygame.font.Font(None, 180)
        font.set_italic(True)
        text = font.render("States of Matter", 1, (20, 125, 120))
        textpos = text.get_rect(centerx=elements_background[0].get_width()/2, centery=elements_background[0].get_height()/4)
        elements_background[0].blit(text, textpos)


    background.blit(elements_background[0],(-450, -200))
    screen.blit(background, (0, 0))
    pygame.display.flip()

    flamethrower = Animation('animation/Flames/flamethrower_/flamethrower_', 29, (-50,0))
    bolt_tsela = Animation('animation/voltage_0/bolt_tesla/bolt_tesla_', 10, (600, 200))
    mouse = Mouse()
    start_button = Button("start_button_original.jpg", "start_button_clicked.jpg", (background.get_width()/2, 12.5*background.get_height()/17))
    #all_sprites = pygame.sprite.RenderPlain((flamethrower, bolt_tsela, mouse, start_button))   #arbitary order
    all_sprites = pygame.sprite.OrderedUpdates((flamethrower, bolt_tsela, start_button, mouse)) #order based on how they are added!
    clock = pygame.time.Clock()

    battle_button = Button("battle_menu_button_original.jpg", "battle_menu_button_clicked.jpg", (background.get_width()/2,4.5*background.get_height()/17))
    options_button = Button("options_menu_button_original.jpg", "options_menu_button_clicked.jpg", (background.get_width()/2, 10.5*background.get_height()/17))

    menu_control = MenuControl()
    menu_control.set_in_start_screen(True)

    #game driver
    while 1:
        clock.tick(60)

        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                return
            elif event.type == MOUSEBUTTONDOWN:
                #HERE IS THE ISSUE
                print("size of all_sprites after clicking:  " + str(len(all_sprites)))
                print("     info on all_sprites:  " + str(all_sprites))
                for but in all_sprites:
                    print("         sprite info:  " + str(but))
                    if isinstance(but, (Button)) and mouse.click(but):
                        but.set_clicked()
                        print("             clicked:  " + str(but))
            elif event.type == MOUSEBUTTONUP:
                mouse.unclick()
                if menu_control.bools["in_start_screen"] and start_button.clicked:
                    #enter main manu
                    menu_control.set_in_main_menu(True)
                    start_button.set_unclicked()
                    all_sprites.empty()
                    all_sprites.add(battle_button, options_button, mouse)
                    print("size of all_sprites after enting main menu:  " + str(len(all_sprites)))
                elif menu_control.bools["in_start_screen"]:
                    start_button.set_unclicked()
                if menu_control.bools["in_main_menu"] and battle_button.clicked:
                    battle_button.set_unclicked()
                if menu_control.bools["in_main_menu"] and options_button.clicked:
                    options_button.set_unclicked()

        all_sprites.update()

        #redraw everything
        screen.blit(background, (0, 0))
        all_sprites.draw(screen)
        pygame.display.flip()

if __name__ == '__main__': main()

Upvotes: 1

Views: 237

Answers (2)

Bartlomiej Lewandowski
Bartlomiej Lewandowski

Reputation: 11170

This is because you reset you clicked state on your mouse on a MOUSE_UP event.

The variable clicking is set while checking the first button, and the condition not self.clicking is never fulfilled for other buttons.

EDIT:

I would advice to split this method into the part that sets the clicking variable for the mouse, and another one that checks for collisions. They are two separate actions. Right now, if you will add no buttons, the mouse will never be in the clicking state. I don't think that was your intention.

Upvotes: 1

martineau
martineau

Reputation: 123443

For eachMOUSEBUTTONDOWNevent that occurs, themouse.click()method is called for everyButtoninstance inall_sprites. Theclick()method only setsself.clicking = 1when it hasn't already been set. This means that when it's called again for buttons later on inall_sprites after the first one, it'll see thatself.clickinghas already been set and not do anything.

Hope that's clear.

Perhaps you want something along these lines [untested] -- which would only set the mouse's self.clickingattribute once in theforloop (assuming two buttons don't overlap):

def click(self, target):
    "returns true if the hand collides with the target"
    hitbox = self.rect.inflate(-5, -5)
    if hitbox.colliderect(target.rect):
        self.clicking = 1
        return True

Upvotes: 0

Related Questions