1

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

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

@MohammadS. I didn't provide an alternative, because I didn't see the reason for the clicking variable.
0

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

2 Comments

Marked correct because the explanation was thorough and provided alternative solution. |=^]
From your comment (and lack of an up-vote) it's unclear whether you found my answer to actually be correct or not. BTW, you could also break out of the for but in all_sprites: loop as soon as a mouse.click(but) call returns True.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.