Pygame how to detect clicks from a list of images - python

For my levels screen I use images for each level button, there are 15 on a page. I was able to blit them all to the screen in a nice layout using some iteration and math, I have been trying to figure out a way to do this with the click detection aswell. But I do not want to type "var = get_rect" 15 times a page.
All of the images are in a list for iteration,
self.levels_p1 = [self.l1, self.l2, self.l3, self.l4, self.l5,
self.l6, self.l7, self.l8, self.l9, self.l10,
self.l11, self.l12, self.l13, self.l14, self.l15,]
and are added to the screen in a 5 x 3 like this,
for i, lvl in enumerate(self.levels_p1, start=1):
x = ((WIDTH - 100) * (i % 5) // 6) + 17
y = HEIGHT // 10 + ((i // 5) * 125)
if i % 5 == 0:
x = ((WIDTH - 100) * 5 // 6) + 17
y = HEIGHT // 10 + (((i // 5) - 1) * 125)
self.screen.blit(lvl, (x, y))
if self.level_unlocked < i:
self.screen.blit(self.level_shade, (x, y))
And I have been detecting clicks like this,
mousepos = pg.mouse.get_pos()
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
if event.type == pg.MOUSEBUTTONDOWN:
#to menu
if self.back_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.start()
#to levels2 page
if self.arrow_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.levels_page2()
#to level 1
if self.l1_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.load_level(1)
But that will only work for all the levels if I manually define a rect for all of the buttons, which is what I am trying to avoid.
I was wondering if there is a good way to detect the clicks on each and every one of these buttons? Similarly to how I displayed them in the 2nd code bit.
Much appreciated.

pygame.Surface.blit returns a rectangle with the area of the affected pixels:
self.rect = self.screen.blit(self.level_shade, (x, y))
Alternatively you can get the bounding rectangle of a pygame.Surface by get_rect(). The location of this rectangle is (0, 0). Hence, you have to set the position by a keyword argument:
self.rect = self.level_shade.get_rect(topleft = (x, y))

if self.arrow_rect.collidepoint(*mousepos)
I don't know what you're packing on the line above. If you want to detect clicks, I think you should get mouse position first. I'll do something like this:
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
if event.type == pg.MOUSEBUTTONDOWN:
#to menu
mousepos = pg.mouse.get_pos() #Here lies the magic and no need to unpack
if self.back_rect.collidepoint(mousepos):
on_levels = False
self.swipe.play()
self.start()
#to levels2 page
if self.arrow_rect.collidepoint(mousepos):
on_levels = False
self.swipe.play()
self.levels_page2()
#to level 1
if self.l1_rect.collidepoint(mousepos):
on_levels = False
self.swipe.play()
self.load_level(1)
If that solves your problem please let me know and of it doesn't still let me know!
Edit: Try if/elif block instead of just if.

mousepos = pg.mouse.get_pos()
lvls = []
for i, lvl in enumerate(self.levels_p1):
#x, y topleft for 5x3 grid with easement and centered
x = ((WIDTH - 100) * (i % 5) // 5) + 110
y = HEIGHT // 10 + ((i // 5) * 125)
#add to screen
temp = self.screen.blit(lvl, (x, y))
#if not unlocked
if self.level_unlocked < i:
#darken
self.screen.blit(self.level_shade, (x, y))
#if unlocked
else:
#enlarged version if hovering over and unlocked
if temp.collidepoint(*mousepos):
self.screen.blit(self.levels_1l[i], (x-6, y-6))
#rect list for click detection
lvls.append(temp)
#back button interactive
if self.back_rect.collidepoint(*mousepos):
self.screen.blit(self.t_back2, self.back_rect2) # bigger
else:
self.screen.blit(self.t_back, self.back_rect) # normal
#arrow button interactive
if self.arrow_rect.collidepoint(*mousepos):
self.screen.blit(self.arrowr2, self.arrow_rect2) # bigger
else:
self.screen.blit(self.arrowr, self.arrow_rect) # normal
pg.display.flip()
#all button presses
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
if event.type == pg.MOUSEBUTTONDOWN:
#to menu
if self.back_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.start()
#to levels2 page
if self.arrow_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.levels_page2()
#to level
for i, val in enumerate(lvls):
#if clicked pos = level pos
if lvls[i].collidepoint(*mousepos):
on_levels = False
self.swipe.play()
#level to load
self.load_level(i+1)

Related

Making a clickable grid using Pygame

I'm making a minesweeper game using python. I have the game working in a text only form and I'm adding a GUI now.
I have three difficulty options with different dimensions. Is there any way to generate a grid without having to make it manually. This is how I generated a square matrix in text form:
for count in range(side):
count2 = 0
temp1 = []
temp2 = []
for count2 in range(side):
temp1.append(0)
temp2.append("x")
grid.append(temp1)
field.append(temp2)
count+=1
Is there any way to automatically generate a grid like this in pygame? I am also using custom sprites and every cell needs to be clickable. Will I just have to make the grids manually and use them according to the difficulty?
I came across a similar issue, here is the solution I found worked well and is fairly simple to implement.
gridstate = {}
class Button:
def __init__(self, x, y, w, h, *get_co, action=False):
self.x = x
self.y = y
self.w = w
self.h = h
self.get_co = get_co
self.colour = (255, 255, 255)
self.clicked = False
self.box = py.draw.rect(screen, self.colour, (x, y, w, h))
self.action = action
def draw(self):
pos = py.mouse.get_pos()
if self.box.collidepoint(pos):
if py.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
if self.action == False:
self.action = True
self.colour = (255, 0, 0)
elif self.action == True:
self.action = False
self.colour = (255, 255, 255)
if py.mouse.get_pressed()[0] == 0:
self.clicked = False
self.box = py.draw.rect(screen, self.colour,
(self.x, self.y, self.w, self.h))
gridstate.update({self.get_co: self.action})
return self.action
I created the class for the button as above, this accepts the argument "get_co" to which I put the 'co ordinate' relative to the rest of the grid so I am able to identify which button was pressed. I pass in the data in like so - This creates a 10x10 grid
resx = 10
resy = 10
buttonsize = 25
grid = []
for x in range(resx):
for y in range(resy):
grid.append(Button((x*buttonsize), (y*buttonsize),
buttonsize, buttonsize, x, y))
Within the loop of game I simply run
activecells = []
for key, value in gridstate.items():
if value == True:
activecells.append(key)
To find the clicked buttons and add them to a list to reference later.
for row in grid:
row.draw()
To draw the grid to the screen.
This seemed to do the trick for me. Apologies ahead of time for any actual programmers who will probably be horrified by my variable naming!
There is no simple solution to draw a grid. You have to loop over each cell as you did in your example. You can find all functions to draw in the pygame wiki.
Example for grid creation:
size = (100, 100)
cell_width = 10
cell_height = 10
grid_surface = pygame.Surface(size) # here you draw
grid = [] # simple list to save your data
for x in range(side):
grid.append([]) # new column for the list
for y in range(side):
# save data to grid
grid[x][y] = "x"
# draw something
# e.g.: this draws a red rectangle in each cell
pygame.draw.rect(grid_surface, (200, 0, 0), (x * cell_width, y * cell_height, cell_width, cell_height)
Potential event loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = (event.pos[0] // cell_width, event.pos[1] // cell_height)
# do something
print(grid[x][y])
grid[x][y] = "a"
pygame.draw.rect(grid_surface, (0, 200, 0), (x * cell_width, y * cell_height, cell_width, cell_height)
I will give you a very simple start. You should create a class that represents a cell in the grid. Create a matrix of cells to represent your board:
class Cell:
def __init__(self):
self.clicked = False
grid_size = 10
board = [[Cell() for _ in range(grid_size)] for _ in range(grid_size)]
Calculate the index of a cell when the mouse button is clicked and change the attributes in the cell:
for event in pygame.event.get():
# [...]
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
row = event.pos[1] // 20
col = event.pos[0] // 20
board[row][col].clicked = True
Draw the board in 2 nested loops:
for iy, rowOfCells in enumerate(board):
for ix, cell in enumerate(rowOfCells):
color = (64, 64, 64) if cell.clicked else (164, 164, 164)
pygame.draw.rect(window, color, (ix*20, iy*20, 20, 20))
Minimal example:
import pygame
class Cell:
def __init__(self):
self.clicked = False
grid_size = 10
board = [[Cell() for _ in range(grid_size)] for _ in range(grid_size)]
pygame.init()
window = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
row = event.pos[1] // 20
col = event.pos[0] // 20
board[row][col].clicked = True
window.fill(0)
for iy, rowOfCells in enumerate(board):
for ix, cell in enumerate(rowOfCells):
color = (64, 64, 64) if cell.clicked else (164, 164, 164)
pygame.draw.rect(window, color, (ix*20+1, iy*20+1, 18, 18))
pygame.display.flip()
pygame.quit()
exit()

Drawing square colour stored in a list of tuples?

I am trying to draw a checkerboard. However when I call the 'drawBoard' function it returns a black screen. I think the problem may be this line here:
colour = con.BOARD_COLOURS[((r+c) % 2)]
I was attempting to index the different colour values using either 0 or 1 in order to alternate the colour of the squares as they are drawn. Could any one explain where I am going wrong. Below is the full code:
#Defining constants
# RGB colours
BOARD_COLOURS = [(245, 222, 179),(34, 139, 34)]
# Dimensions
ROWS = 8
COLS = 8
DIMS = 600
SQUARE = 600 // 8
class Board:
def __init__(self):
pass
def drawBoard(self, screen):
for r in range(con.ROWS):
for c in range(con.ROWS):
colour = con.BOARD_COLOURS[((r+c) % 2)]
pg.draw.rect(screen, colour, pg.Rect(c*con.SQUARE, r*con.SQUARE, con.SQUARE, con.SQUARE))
def main():
t = 60
clock = pg.time.Clock()
interface = pg.display.set_mode((con.DIMS, con.DIMS))
pg.init()
run = True
while run:
clock.tick(t)
for ev in pg.event.get():
if ev.type == pg.QUIT:
run = False
b = Board()
b.drawBoard(interface)
pg.quit()
main()
You need to update the display. Update the display by calling either pygame.display.update() or pygame.display.flip() after drawing the scene:
def main():
t = 60
clock = pg.time.Clock()
interface = pg.display.set_mode((con.DIMS, con.DIMS))
pg.init()
b = Board()
run = True
while run:
clock.tick(t)
for ev in pg.event.get():
if ev.type == pg.QUIT:
run = False
interface.fill(0)
b.drawBoard(interface)
pygame.display.flip()
pg.quit()
main()
The typical PyGame application loop has to:
limit the frames per second to limit CPU usage with pygame.time.Clock.tick
handle the events by calling either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by calling either pygame.display.update() or pygame.display.flip()
To drag the objects you have to implement the MOUSEBUTTONDOWN, MOUSEBUTTONUP and MOUSEMOTION event:
def main():
t = 60
clock = pg.time.Clock()
interface = pg.display.set_mode((DIMS, DIMS))
pg.init()
pg.display.set_caption("Checkers Game")
dragitem = None
run = True
b = Board()
while run:
clock.tick(t)
for ev in pg.event.get():
if ev.type == pg.QUIT:
run = False
if ev.type == pg.MOUSEBUTTONDOWN:
col = ev.pos[0] * COLS // DIMS
row = ev.pos[1] * ROWS // DIMS
if b.board[row][col] > 1:
dragitem = (row, col, b.board[row][col])
b.board[row][col] = (row + col) % 2 == 0
dragpos = ev.pos
if ev.type == pg.MOUSEBUTTONUP:
new_col = ev.pos[0] * COLS // DIMS
new_row = ev.pos[1] * ROWS // DIMS
if b.board[new_row][new_col] < 2:
b.board[new_row][new_col] = dragitem[2]
else:
b.board[dragitem[0]][dragitem[1]] = dragitem[2]
dragpos = None
dragitem = None
if ev.type == pg.MOUSEMOTION and dragitem:
dragpos = ev.pos
interface.fill(0)
b.drawBoard(interface)
b.drawPiece(interface)
if dragitem:
pg.draw.circle(interface, PIECE_COLOUR[dragitem[2]-2], dragpos, RADIUS)
pg.display.flip()
pg.quit()

I get an incorrect position from .get_rect()?

I'm currently working on a school project where I'm making a "hexcells" similar game in pygame and now I'm trying to blit an a new image if the user has clicked a current image. It will blit an image in the top left area, if clicked in the top left area, but not if I click any of the existing images. I told the program to print the coordinates from the images with help of the .get_rect() function, but it remains the same whereever I click and the coordinates aren't even where a image is. Can someone help me understand how this works and help me blit the new images on top of the existing images? Code below is not the entire document, however there is so much garbage/trash/unused code so I'd thought I spare you the time of looking at irrelevant code. Also sorry if the formatting is wrong or the information isn't enough, I tried my best.
import pygame, sys
from pygame.locals import *
#Magic numbers
fps = 30
winW = 640
winH = 480
boxSize = 40
gapSize = 75
boardW = 3
boardH = 3
xMargin = int((winW - (boardW * (boxSize + gapSize))) / 2)
yMargin = int((winW - (boardW * (boxSize + gapSize))) / 2)
#Lil bit o' color R G B
NAVYBLUE = ( 60, 60, 100)
correctCords = [[175,275,375],[375,275,175]]
bgColor = NAVYBLUE
unC = pygame.image.load("unC.png")
cor = pygame.image.load("correct.png")
inc = pygame.image.load("wrong.png")
correct = "Correct"
inCorrect = "Incorrect"
def main():
global FPSCLOCK, DISPLAYSURF
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((winW, winH))
mousex = 0 #stores x-coordinate of mouse event
mousey = 0 #stores y-coordinate of mouse event
pygame.display.set_caption("Branches")
DISPLAYSURF.fill(bgColor)
gridGame(inCorrect, correct,gapSize,xMargin,yMargin,boxSize)
while True:
mouseClicked = False
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION:
mousex,mousey = event.pos
elif event.type == MOUSEBUTTONUP:
mousex, mousey = event.pos
pos = pygame.mouse.get_pos()
mouseClicked = True
unCa = unC.get_rect()
corA = cor.get_rect()
print unCa
print corA
print pos
if unCa.collidepoint(pos):
DISPLAYSURF.blit(cor,(mousey,mousex))
"""lada = unC.get_rect()
lada =
if mousex and mousey == lada:
for x in correctCords:
for y in x:
for z in x:
if mousey and mousex == z and y:
DISPLAYSURF.blit(cor,(mousey,mousex))
print lada"""
pygame.display.update()
FPSCLOCK.tick(fps)
def gridGame(inCorrect, correct,gapSize,xMargin,yMargin,boxSize):
grid = []
cordX = []
cordY = []
correctRecs = []
#cordinates = []
#cordinates.append([])
#cordinates.append([])
#cordinates.append([])
#this is basically getBoard() all over again
#This part will arrange the actual backend grid
for row in range(3):
grid.append([])
#cordinates[0].append(gapSize+(row+1)*100)
#cordinates[1].append(gapSize+(row+1)*100)
#cordinates[2].append(gapSize+(row+1)*100)
for column in range(3):
grid[row].append(inCorrect)
for row in range(3):
cordX.append([])
for column in range(3):
cordX[row].append(gapSize+(row+1)*100)
for row in range(3):
cordY.append([])
for column in range(3):
cordY[row].append(gapSize+(column+1)*100)
#print cordX[0][0], cordY[0][0]
grid[0][2] = correct
grid[1][1] = correct
grid[2][0] = correct
#Y-AXEL SKRIVS FoRST ([Y][X])
#print cordinates[2][1]
DISPLAYSURF.blit(cor,(100,100))
#Let's draw it as well
for row in range(3):
for column in range(3):
DISPLAYSURF.blit(unC,(gapSize+(row+1)*100,gapSize+(column+1)*100))
main()
Also real sorry about the horrible variable naming and occasional swedish comments.
unCa = unC.get_rect() gives you only image size - so use it only once at start (before while True) - and later use the same unCa all the time to keep image position and change it.
btw: better use more readable names - like unC_rect
ie.
# move 10 pixel to the right
unC_rect.x += 10
# set new position
unC_rect.x = 10
unC_rect.right = 100
unC_rect.topleft = (10, 200)
unC_rect.center = (10, 200)
# center on screen
unC_rect.center = DISPLAYSURF.get_rect().center
etc.
And then use this rect to blit image
blit(unC, unC_rect)
and check collision with other rect
if unC_rect.colliderect(other_rect):
or with point - like mouse position
elif event.type == MOUSEMOTION:
if unC_rect.collidepoint(pygame.mouse.get_pos()):
hover = True
# shorter
elif event.type == MOUSEMOTION:
hover = unC_rect.collidepoint(pygame.mouse.get_pos()):

Referencing previous frames values Python Pygame

I have a really simple piece of code. What I would like to do as the program loops at 60 fps is I want to reference the previous mouse click state. i.e. I have 'one' as the variable for mouse click state, 0 for not clicked and 1 for clicked. What I want to happen is If the mouse is currently clicked i.e. one = 1 & the previous value of one was 0 i.e. unclicked then save the value of mx and my which are the mouse co-ordinates. Please see the code below:
PDimageFull = pygame.image.load('F:\Project files\coils\PDsinwaveResize.jpg')
PDresX = 300
PDresY = 720
gameDisplay = pygame.display.set_mode((Display_Width,Display_Height))
pygame.display.set_caption('PD diagnostic tool')
clock = pygame.time.Clock()
def PDimage(x, y):
gameDisplay.blit(PDimageFull, (x, y))
# Defining our main programing loop
def mainProgram_loop():
dx1 = (Display_Width-PDresX)
dy1 = (Display_Height-PDresY)
gameExit = False
# Event handling
while not gameExit:
mx, my = pygame.mouse.get_pos()
one, two, three = pygame.mouse.get_pressed()
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
# This expression controls the movement of the overall PD graph
# The section in the IF statement defines the boundary by which you can move the object
if one == 1 and mx > dx1 and mx < dx1 + PDresX and my > dy1 and my < dy1+PDresY:
dx1 = dx1 + (mx - PDresX)
dy1 = dy1 + (my - PDresY)
gameDisplay.fill(white)
PDimage(dx1, dy1)
pygame.display.update()
clock.tick(60)
mainProgram_loop()
Just use another variable:
prev_one = one
one, two, three = pygame.mouse.get_pressed()
if prev_one == 0 and one == 1:
print 'mouse was clicked this frame'
Be aware that you will have to initialise one with a default value at the start of your script.

Python clicking game, updating score

import pygame
from pygame.locals import *
import random
import time
pygame.init()
randomNumber = random.randint(1,600)
randomNumber2 = random.randint(1,600)
x = 0
text = ""
squareCount = 0
beenHere = 0
# colours = (red, green, blue)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
WHITE = (255, 255, 255)
LBLUE = (0, 123, 255)
colour = RED
# Make a window appear
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Square Chase | Score: 0")
screen.fill(LBLUE)
pygame.display.flip()
pygame.time.set_timer(USEREVENT + 1, 1500)
done = False
clock = pygame.time.Clock()
while done == False:
for event in pygame.event.get():
print(event)
if event.type == pygame.QUIT:
done = True
if event.type == USEREVENT + 1:
screen.fill(LBLUE)
randomNumber = random.randint(1,625)
randomNumber2 = random.randint(1,420)
mySquare = pygame.draw.rect(screen,colour,(randomNumber,randomNumber2,50,50),5)
squareCount = squareCount + 1
if squareCount == 50:
done == true
pygame.display.flip()
if event.type == pygame.MOUSEBUTTONDOWN:
y, z = pygame.mouse.get_pos()
is_inside = mySquare.collidepoint(y, z)
if is_inside and colour == GREEN:
x = x+1
text = str(x)
pygame.display.set_caption("Square Chase | Score " + text)
colour = RED
elif is_inside:
x = x+1
text = str(x)
pygame.display.set_caption("Square Chase | Score " + text)
colour = GREEN
clock.tick(20)
pygame.quit()
I am aiming to create a game in which the user has to click on a square for their score to increase. They get 50 chances to do this. If they click in the square within the 1.5 seconds the colour of the square changes.
The above code works apart from each time a new square is drawn the user could click on it say 5 times and their score will go up by 5. Any suggestions as to how to get the score to increase by just one? Thanks in advance.
Wish I could just use a comment but I have lack of reputation.
I didn't look through all your code, but your description makes it sound like a simple flag would be able to help you.
Once you recognize it has been clicked, increment the score AND set a boolean.
So in essence you will want something along the lines of
if clicked == false
score = score+1
clicked = true
You will want to clear the flag back to false once another block has appeared.
Wouldn't it be better, if a new square would show up after a successful keypress? That way the game would be more dynamic.
It's easy to change this. Instead of using user events that are called every 1.5 sec, sum the value returned by clock.tick() and when their sum will be greater than 1500 ms, create a new rectangle. This is a better approach, because you can call the method that creates a new square and reset the timer right after a successful keypress.
Some code to get you started:
def new_rect(screen,colour):
screen.fill(LBLUE)
randomNumber = random.randint(1,625)
randomNumber2 = random.randint(1,420)
mySquare = pygame.draw.rect(screen,colour,(randomNumber,randomNumber2,50,50),5)
return mySquare
done = False
clock = pygame.time.Clock()
timer_var = 0
while done == False:
if(timer_var > 1500):
timer_var = 0
mySquare = new_rect(screen,color)
squareCount = squareCount + 1
if squareCount == 50:
done == true
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
y, z = pygame.mouse.get_pos()
is_inside = mySquare.collidepoint(y, z)
if is_inside:
x = x+1
text = str(x)
pygame.display.set_caption("Square Chase | Score " + text)
timer_var = 0
new_rect(screen,colour)
squareCount = squareCount + 1
if squareCount == 50:
done == true
if colour == GREEN:
colour = RED
else:
colour = GREEN
timer_var += clock.tick(20)
pygame.quit()

Categories

Resources