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

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)

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

Related

How to make image disappear or how to hide image from the screen? [duplicate]

This question already has answers here:
how to make image/images disappear in pygame?
(1 answer)
How to delete one object from a Surface instance?
(1 answer)
How to clean up sprites without covering other things?
(1 answer)
Closed 4 months ago.
I'm making my first pygame game, Plane Delivery game. I'm stuck at the menu. I created menu with my custom background image. Also, I created START button which is used to start the game. When player clicks the START button, I want to hide main menu background, and show the game's background image, world map. Thank you for help!
There's code:
import pygame
pygame.init()
window = pygame.display.set_mode((1920, 1080))
pygame.display.set_caption('Plane Delivery')
POZADINA = (254, 0, 60) # BOJA POZADINE
window.fill(POZADINA)
clock = pygame.time.Clock()
menu_img = pygame.image.load('Plane_Delivery2.jpg')
menu_img = pygame.transform.scale(menu_img, (1920, 1080))
bg_img = pygame.image.load('background.jpg')
bg_img = pygame.transform.scale(bg_img,(1920, 1080))
plane_sprite = pygame.image.load('avion3.png')
x2 = 960
y2 = 540
# -- Main Menu --
#button class
class Button():
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
start_img = pygame.image.load('start2.png').convert_alpha()
exit_img = pygame.image.load('exit2.png').convert_alpha()
start_button = Button(30, 400, start_img, 0.8)
exit_button = Button(200, 360, exit_img, 0.4)
run = True
while run:
clock.tick(40)
window.blit(menu_img, (0, 0))
if start_button.draw(window):
window.fill((255, 255, 0))
window.blit(bg_img, (0, 0))
pygame.display.flip()
window.blit(plane_sprite, (x2, y2))
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
while 1:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
y2 += 20
if event.key == pygame.K_s:
y2 -= 20
if event.key == pygame.K_a:
x2 -= 20
if event.key == pygame.K_d:
x2 += 20
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if exit_button.draw(window):
pygame.quit()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.update()
Thank you for help! :D

Creating a system which counts how many times a button has been clicked [duplicate]

This question already has answers here:
Pygame mouse clicking detection
(4 answers)
Closed 4 months ago.
Using another person's code, I wanted to improve on it and make my own version of a game that counts how many times a button has been clicked with a timer of only 20 seconds. I would like to make it somewhat similar to 'cookie clicker', with no upgrade system whatsoever. I've been looking for help for quite a while now but I hit a dead end, here is my code so far:
import pygame
import button
pygame.init()
#create game window
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Clicker Game")
#game variables
game_paused = True
menu_state = "main"
#define fonts
font = pygame.font.SysFont("arialblack", 40)
#define colours
TEXT_COL = (0, 0, 0)
#load button images
resume_img = pygame.image.load("Resume.jpg").convert_alpha()
quit_img = pygame.image.load("Quit.jpg").convert_alpha()
back_img = pygame.image.load('Back.jpg').convert_alpha()
#create button instances
resume_button = button.Button(304, 200, resume_img, 1)
quit_button = button.Button(304, 300, quit_img, 1)
back_button = button.Button(332, 450, back_img, 1)
def draw_text(text, font, text_col, x, y):
img = font.render(text, True, text_col)
screen.blit(img, (x, y))
#game loop
run = True
while run:
screen.fill((255, 255, 255))
#check if game is paused
if game_paused == True:
#check menu state
if menu_state == "main":
#draw pause screen buttons
if resume_button.draw(screen):
game_paused = False
if quit_button.draw(screen):
run = False
#check if the options menu is open
if menu_state == "options":
#draw the different options buttons
if back_button.draw(screen):
menu_state = "main"
else:
draw_text("Press SPACE to pause", font, TEXT_COL, 160, 250)
#event handler
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
game_paused = True
if event.type == pygame.QUIT:
run = False
pygame.display.update()
pygame.quit()
Button:
import pygame
#button class
class Button():
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
Here is a modified class that suits your needs
class Button():
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
self.times_clicked = 0#Counting times clicked
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
self.times_clicked += 1#Click detected, increment
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
This adds a variable within your class called times_clicked. Every time it is clicked, it goes up by one. Get it by calling (obj).times_clicked (example: resume_button.times_clicked)

How to Fill a rectangle with color every time the user clicks it? pygame

I know that there are several similar questions online, but none of them really helped me. I simply want to draw a grid and give the user the option to click into those grid cells. Every time the user clicks, the color/fill of the cell should change from black to white.
What I'm doing at the moment is the following:
BLACK = (0, 0, 0)
WHITE = (200, 200, 200)
def drawGrid(h, w, blocksize):
for x in range(w):
for y in range(h):
rect = pygame.Rect(x*blocksize, y*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, WHITE, rect, 1)
def handle_events():
col = WHITE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
col = WHITE
# determine mouse position
mpos_x, mpos_y = event.pos
# determine cell number
coord = mpos_x // blocksize, mpos_y // blocksize
rect = pygame.Rect(coord[0]*blocksize, coord[1]*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, col, rect, 1)
pygame.display.update()
def main():
global SCREEN, CLOCK, blocksize
w = int( sys.argv[1] )
h = int( sys.argv[2] )
blocksize = int( sys.argv[3] )
pygame.init()
SCREEN = pygame.display.set_mode((h, w))
CLOCK = pygame.time.Clock()
SCREEN.fill(BLACK)
drawGrid(h,w,blocksize)
handle_events()
if __name__ == "__main__":
main()
The program is printing the grid. However, when I click somewhere nothing happens. I know this is not the best code, so I would appreciate for any suggestion.
I changed the code a little and it worked properly, pygame.draw.rect(SCREEN, col, rect, 1) you draw same thing and you can't see the change. You should use pygame.draw.rect(SCREEN, col, rect):
import pygame
import sys
BLACK = (0, 0, 0)
WHITE = (200, 200, 200)
# WINDOW_HEIGHT = 400
# WINDOW_WIDTH = 400
def drawGrid(h, w, blocksize):
#blockSize = 20 #Set the size of the grid block
for x in range(w):
for y in range(h):
rect = pygame.Rect(x*blocksize, y*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, WHITE, rect, 1)
def handle_events():
#coords_list = []
col = WHITE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
col = WHITE
# determine mouse position
mpos_x, mpos_y = event.pos
# determine cell number
coord = mpos_x // 32, mpos_y // 32
rect = pygame.Rect(coord[0]*32, coord[1]*32,
32, 32)
pygame.draw.rect(SCREEN, col, rect)
#coords_list.append(coord)
pygame.display.update()
#return coords_list
def main():
global SCREEN, CLOCK, blocksize
pygame.init()
SCREEN = pygame.display.set_mode((480, 640))
CLOCK = pygame.time.Clock()
SCREEN.fill(BLACK)
drawGrid(480,640,32)
handle_events()
if __name__ == "__main__":
main()
As a suggestion, I think you should use sprite for every cell. For example like this:
class Cell(pygame.sprite.Sprite):
def __init__(self, sprite_group, x, y, cell_dimension, color=BLACK):
self.groups = sprite_group
pygame.sprite.Sprite.__init__(self, self.groups)
self.image = pygame.Surface((cell_dimension, cell_dimension))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x * cell_dimension
self.rect.y = y * cell_dimension
self.clicked = False
def update(self):
if self.clicked:
self.image.fill(WHITE)

How do I fix movement in my pygame script?

I am trying to make a replica for pong in pygame for my first project but when I try to move my paddles they stretch instead. I believe the reason is it creates a new rect every time I try to move it but I can't seem to figure out why. Please review the code and help rectify my mistake.
Here is my code:
import pygame
W, H = 600, 500
screen = pygame.display.set_mode((W, H))
FPS = 30
class Paddle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
super(Paddle, self).__init__()
self.x = x
self.y = y
self.width = width
self.height = height
self.surf = pygame.Surface((width, height))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect()
self.rect.center = (x, y)
def move(self, distance):
self.rect.move_ip(0, distance)
paddleA = Paddle(15, 250, 10, 50)
paddleB = Paddle(585, 250, 10, 50)
allSprites = pygame.sprite.Group()
allSprites.add(paddleA)
allSprites.add(paddleB)
run = True
clock = pygame.time.Clock()
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
pressedKeys = pygame.key.get_pressed()
if pressedKeys[pygame.K_UP]:
paddleB.move(-5)
elif pressedKeys[pygame.K_DOWN]:
paddleB.move(5)
elif pressedKeys[pygame.K_w]:
paddleA.move(-5)
elif pressedKeys[pygame.K_s]:
paddleA.move(5)
for sprite in allSprites:
screen.blit(sprite.surf, sprite.rect)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
quit()
Before drawing the new rect you should fill the screen with the background color, to remove the old rect. Otherwise the old one is still drawn there and you are just drawing new over the old one. Its' like painting a new picture on an old one.
screen.fill(color, rect) should do the trick.

Changing a class so that when the mouse hovers over it, it changes colour - Pygame

import pygame as pg
import sys
import time
# All pygame stuff under here
pg.init()
# Font definitions
backFont = pg.font.SysFont("monospace", 40)
titleFont = pg.font.SysFont("garamond", 100)
cipherFont = pg.font.SysFont("garamond", 50)
buttonFont = pg.font.SysFont("garamond", 25)
bigFont = pg.font.SysFont("garamond", 100)
Font = pg.font.SysFont(None, 32)
inputFont = pg.font.SysFont('consola', 35)
errorFont = pg.font.SysFont('tahoma', 20)
diagramFont = pg.font.SysFont('courier new', 25)
# Colour definitions
BackGray = pg.Color('gray60')
screenGray = pg.Color('gray80')
buttonGray1 = pg.Color('gray40')
buttonGray2 = pg.Color('gray50')
buttonGray3 = pg.Color('gray30')
textColour = pg.Color('navy')
# Screen size set
screen = pg.display.set_mode((800, 600))
# Clock initiated to allow regular typing speeds
clock = pg.time.Clock()
# Change button class so that when the mouse pos is sent through, changes colour ======================
class Button(pg.sprite.Sprite):
def __init__(self, text, x, y, width, height, enabled):
super().__init__()
self.colour = buttonGray2
self.image = pg.Surface((width, height))
self.image.fill(buttonGray2)
self.rect = self.image.get_rect()
txt = buttonFont.render(text, True, textColour)
txtRect = txt.get_rect(center=self.rect.center)
self.image.blit(txt, txtRect)
self.rect.topleft = x, y
self.enabled = enabled
def isPressed(self, event):
if self.enabled == True:
if event.type == pg.MOUSEBUTTONDOWN:
# MOUSE... events have an event.pos attribute (the mouse position)
# which you can pass to the collidepoint method of the rect.
if self.rect.collidepoint(event.pos):
return True
return False
def HoveredOver(self,event):
print("Ocurring")
if event.type == pg.MOUSEMOTION:
if self.rect.collidepoint(event.pos):
print("Hoveredover")
self.colour = buttonGray1
else:
self.colour = buttonGray2
def FrontPage():
# Back screen
screen.fill(screenGray)
# Button definition for continuing
Continue = Button('Continue', 105, 455, 120, 50, True)
buttonsGroup = pg.sprite.Group(Continue)
# Pygane while loop for events
while True:
for event in pg.event.get():
mouseX, mouseY = pg.mouse.get_pos()
if event.type == pg.QUIT:
pg.quit()
sys.exit()
elif Continue.isPressed(event):
print("Continue")
Continue.HoveredOver(event)
buttonsGroup.draw(screen)
pg.display.flip()
clock.tick(60)
FrontPage()
I have this class to define and create buttons in Pygame. At the moment, when the mouse hovers over the button, it does not change colour. I attempted to add another method in the class, so that it changes colour, if the mouse is over the button. However, when I run the HoveredOver method, it does not change the colour of the button.
How can I make this method work to change the colour of the button?
Thanks in advance!
It's not enough to change the colour attribute of the sprite, you also have to change the image because pygame blits the image onto the screen when you call buttonsGroup.draw(screen).
I'd create the images in the __init__ method or pass them as arguments and then swap them by assigning the current image to self.image if the mouse hovers over the button.
class Button(pg.sprite.Sprite):
def __init__(self, text, x, y, width, height, enabled):
super().__init__()
self.colour = buttonGray2
self.image = pg.Surface((width, height))
self.image.fill(buttonGray2)
self.image_normal = self.image # Store a reference to the original image.
# Create a separate hover image.
self.image_hover = pg.Surface((width, height))
self.image_hover.fill(buttonGray1)
self.rect = self.image.get_rect()
txt = buttonFont.render(text, True, textColour)
txtRect = txt.get_rect(center=self.rect.center)
self.image.blit(txt, txtRect)
self.image_hover.blit(txt, txtRect) # Blit the text onto the hover image.
self.rect.topleft = x, y
self.enabled = enabled
def isPressed(self, event):
if self.enabled == True:
if event.type == pg.MOUSEBUTTONDOWN:
# MOUSE... events have an event.pos attribute (the mouse position)
# which you can pass to the collidepoint method of the rect.
if self.rect.collidepoint(event.pos):
return True
return False
def HoveredOver(self, event):
if event.type == pg.MOUSEMOTION:
if self.rect.collidepoint(event.pos):
print("Hoveredover")
# Swap the image.
self.image = self.image_hover
else:
# Swap the image.
self.image = self.image_normal

Categories