DyingInCS
DyingInCS

Reputation: 51

wondering about pixel perfect mousepointer collision in the case of a button

I've got a pretty simple code up right now that just moves between two menu screens once the button for each is pressed. I know that you can mask images in pygame to get pixel perfect collision but not sure how I'd go about doing it for the buttons in this code (it's just pretty annoying that you can click slightly off and have it still transfer you to the other menu). A follow-up question I had was on how I could do fade transitions between the screens - I've seen some tutorials but they've all seemed overcomplicated.

import pygame, os, time, random, sys

width, height = 1600, 900

pygame.init()

mainMenu = True
resMenu = False

screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
fps = 120


bg = pygame.image.load("assets/mainMenu.jpg").convert()
bgRes = pygame.image.load("assets/resMenu.jpg").convert()

res_button_image = pygame.transform.scale2x(
    pygame.image.load("assets/changeRes.png")
).convert_alpha()

back_button_image = pygame.transform.scale2x(
    pygame.image.load("assets/backToMenu.png")
).convert_alpha()


class resolutionButton:
    def __init__(self, x, y, image, scale):
        width = image.get_width()
        height = image.get_height()
        self.image = pygame.transform.scale(
            image, (int(width * scale), int(height * scale))
        )
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
        self.clicked = False

    def draw(self, surface):
        action = False
        # get mouse position
        pos = pygame.mouse.get_pos()

        # check mouseover and clicked conditions
        if self.rect.collidepoint(pos):
            if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
                self.clicked = True
                action = True

        if pygame.mouse.get_pressed()[0] == 0:
            self.clicked = False

        # draw button on screen
        surface.blit(self.image, (self.rect.x, self.rect.y))

        return action


class backtoMenuButton:
    def __init__(self, x, y, image, scale):
        width = image.get_width()
        height = image.get_height()
        self.image = pygame.transform.scale(
            image, (int(width * scale), int(height * scale))
        )
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
        self.clicked = False

    def draw(self, surface):
        action = False
        # get mouse position
        pos = pygame.mouse.get_pos()

        # check mouseover and clicked conditions
        if self.rect.collidepoint(pos):
            if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
                self.clicked = True
                action = True

        if pygame.mouse.get_pressed()[0] == 0:
            self.clicked = False

        # draw button on screen
        surface.blit(self.image, (self.rect.x, self.rect.y))

        return action


while True:
    while mainMenu:

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        start_button = resolutionButton(100, 400, res_button_image, 1)

        screen.blit(bg, (0, 0))
        if start_button.draw(screen):
            resMenu = True
            mainMenu = False

        pygame.display.update()

        clock.tick(fps)

    while resMenu:

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        back_button = backtoMenuButton(100, 700, back_button_image, 1)

        screen.blit(bgRes, (0, 0))
        if back_button.draw(screen):
            resMenu = False
            mainMenu = True
        pygame.display.update()

        clock.tick(fps)

Upvotes: 3

Views: 186

Answers (1)

Rabbid76
Rabbid76

Reputation: 210948

To test whether the mouse is on an icon, you need to create a mask from the image (pygame.Surface) with pygame.mask.from_surface:

image_mask = pygame.mask.from_surface(image)

Define the bounding rectangle of the icon. e.g.:

image_rect = image.get_rect(center = (x, y)

Test whether the mouse is on the image, calculate the coordinates of the pixel on which the mouse is located (mask_x, mask_y) and use pygame.mask.Mask.get_at to test whether the mask is set at this point:

mouse_pos = pygame.mouse.get_pos()
if image_rect.collidepoint(mouse_pos):
    mask_x = mouse_pos[0] - image_rect.left
    mask_y = mouse_pos[1] - image_rect.top
    if image_mask.get_at((mask_x, mask_y)):
        print("hit")

Minimal example:

import pygame

pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

image = pygame.image.load('Banana.png')
image_rect = image.get_rect(center = window.get_rect().center)
image_mask = pygame.mask.from_surface(image)

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

    color = (0, 0, 0)
    mouse_pos = pygame.mouse.get_pos()
    if image_rect.collidepoint(mouse_pos):
        mask_x = mouse_pos[0] - image_rect.left
        mask_y = mouse_pos[1] - image_rect.top
        if image_mask.get_at((mask_x, mask_y)):
            color = (255, 0, 0)

    window.fill(color)
    window.blit(image, image_rect)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
exit()

Upvotes: 1

Related Questions