Clickable images in pygame? - pygame

#Imported Pygame
import pygame
#The Colors
BLACK = ( 0, 0, 0)
GREEN = ( 0, 255, 0)
WHITE = ( 255, 255, 255)
RED = ( 255, 0, 0)
ORANGE = ( 255, 115, 0)
YELLOW = ( 242, 255, 0)
BROWN = ( 115, 87, 39)
PURPLE = ( 298, 0, 247)
GRAY = ( 168, 168, 168)
PINK = ( 255, 0, 234)
pygame.init()
#The Screen
screen = pygame.display.set_mode([1000,500])
#Name of the window
pygame.display.set_caption("My first game")
clock = pygame.time.Clock()
#The sounds
# Positions of graphics
background_position = [0,0]
singleplayer_position = [350,200]
#The graphics
background_image = pygame.image.load("Castle.png").convert()
singleplayer_image = pygame.image.load("SinglePlayer.png").convert()
singleplayer_image.set_colorkey(WHITE)
#Main Loop __________________________
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Copy of background or main menu
screen.blit(background_image, background_position)
#Copy of other images
screen.blit(singleplayer_image, singleplayer_position)
pygame.display.flip()
if pygame.mouse.get_pressed()[0] and singleplayer_image.collidepoint(mouse_pos):
print("Hi")
clock.tick(60)
#To quit game
pygame.quit()
This is basicaly my code, but I keep getting the error that pygame.surface object has no attribute collide point. Im trying to have a clickable image,, but it isn't working to well. If you could show a way that a image can be clickable thank you.

Your traceback is explaining the issue perfectly: pygame surfaces do not have an attribute collide_point. Collidepoint belongs to the Rect class, but you are calling it on a Surface object.
To test if if the mouse position collides with the image, you need to have a Rect that describes the images position. So, if you redefine your singleplayer_position...
singleplayer_position = Rect(350, 200, 100, 100) # Width/height of 100 pixels.
You can now use this variable for Rect methods, such as collidepoint.
singleplayer_position.collidepoint(mouse_pos)
Note: To have your Rect accurately represent the picture you load..
singleplayer_position = singleplayer_image.get_rect()
This defaults to the top left, but it has the correct width/height now. Lets move it to where you wanted it.
singleplayer_position = singleplayer_position.move(350, 200)
Edit, to show how to get mouse position:
Add at the top,
from pygame.locals import * # Brings in all the pygame keywords we need.
Now, add this to your event for loop.
if event.type == MOUSEBUTTONDOWN:
mouse_pos = event.pos # Now it will have the coordinates of click point.
if singleplayer_position.collidepoint(mouse_pos):
print('hi')
Now, whenever the mousebutton is clicked down, you can check the images Rect (singleplayer_position) to see if it collides with where the mouse was clickd.

Related

Two arcs while using Pygame tranform.flip

This is my first real code project. I am trying to create a Crash gambling project for a math project at School.
My current problem is that when I flip my arc it draws 2 separate arcs. I don't know why it does this but if anyone can help it would be much appreciated.
Here is my code:
import pygame
from math import sin, cos, radians
grid = pygame.image.load('grid3.jpg')
wn = pygame.display.set_mode((600, 600))
wn2 = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()
r = 600
a = 0
b = 0
def x_y(r, i, a, b):
return (int(r * cos(radians(i)) + a), int(r * sin(radians(i)) + b))
for i in range(0, 90, 1):
clock.tick(30)
pygame.draw.line(wn, (255, 255, 255), x_y(r, i, a, b), x_y(r, i+1, a, b), 10)
wn2.blit(wn, (0,0))
wn.blit(grid,(0,0))
wn2.blit(pygame.transform.rotate(wn2, -90), (0, 0))
wn = pygame.transform.flip(wn2, True, False)
pygame.display.update()
When you draw onto a display Surface, it won't remove the other existing elements. Take this code as an example:
import pygame
from pygame.locals import *
screen = pygame.display.set_mode((320, 240))
pygame.draw.rect(screen, (0, 255, 0), Rect(50, 20, 100, 100))
pygame.draw.rect(screen, (255, 0, 0), Rect(120, 100, 100, 100))
pygame.display.flip() # same as pygame.display.update() (with no arguments)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
When executing this, you will see 2 squares appear, the second one being drawn not preventing the first one from still appearing, even though you updated the screen.
As a display surface is also a transparent surface, blitting a display Surface onto another will not erase its content.
To do that, you can call the Surface.fill function.
A few optional (but some I recommend) improvements to your code:
Implement a game loop to your game/simulation to namely get the user events and preventing your game from constantly crashing after a while
Use pygame.display.flip() instead of pygame.display.update() (the latter is useful when you want to only update part of the screen)
You are creating 2 screen surfaces, but that's not possible. wn and wn2 are referring to the same surface. Use pygame.Surface for creating other surfaces.
Try to leave the main display surface as it is, and work with other surfaces rather than changing the entire screen by transforming it, as this can be confusing when debugging.
I hope that helped!

How do I draw an object on the screen inside of a sprite class?

So I'm trying to draw a rectangle with another rectangle on top, using Sprites. I made a class for the player and did some basic setup, but when I tried to blit the second rectangle on top, it didn't work. I did some testing and found out that I can't even draw lines or rectangles from inside this player class.
Here's my basic testing code:
import pygame as pg
pg.init()
width, height = 800, 800
screen = pg.display.set_mode((width, height))
run = True
class Player(pg.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pg.image.load("test_image.png")
self.rect = self.image.get_rect()
def update(self):
pg.draw.rect(screen, [0, 255, 0], (200, 200, 100, 50))
print("test")
def draw_window():
screen.fill((255, 255, 255))
playerGroup.draw(screen)
pg.display.update()
playerGroup = pg.sprite.GroupSingle()
playerGroup.add(Player())
while run:
for event in pg.event.get():
if event.type == pg.QUIT:
run = False
playerGroup.update()
draw_window()
This is what i get: image
The blue thing is the player image that gets drawn normally in the top left corner. The rect that i'm trying to draw inside the update() method however is nowhere to be seen, even though i can clearly see the method gets called with the print("test"). This isn't just true for pg.draw() but also for surface.blit()
Why is this and how do i fix it?
screen.fill((255, 255, 255)) fills the entire display with a white color. Anything previously drawn will be lost. You must call playerGroup.update() after clearing the display and before updating the display. e.g.:
def draw_window():
screen.fill((255, 255, 255))
playerGroup.update()
playerGroup.draw(screen)
pg.display.update()

Pygame, my circle turns to a rect after I stored it in a variable, how do I prevent that from happening

I need to store a Circle in a variable but after I've done that it has turned into a rect
circle_1 = pygame.draw.circle(screen, (0, 0, 0), (300, 300), 30)
Print(circle_1)
the print returns
<rect(270, 270, 60, 60)>
but I can't work with that.
My circle is predefined but it won't show it on the canvas, here is an example of the problem
> import pygame, sys
>
>
> pygame.init() screen = pygame.display.set_mode((600, 600))
> predefined_circle = pygame.draw.circle(screen,(0, 0, 0),(300, 300), 30)
>
> def update():
> screen.fill((200, 0, 0))
> while 1:
> for event in pygame.event.get():
> if event.type == pygame.QUIT: sys.exit()
> # It shows my circle if I dirctly tip pygame.draw.circle(screen,(0, 0, 0),(300, 300), 30) into it
> predefined_circle
> pygame.display.update()
>
> update()
So that you can better relate to what I'm trying to achieve here is the code of what I'm doing but it is not necessary to read as I've already tried to explain it as best as I can above.
Please note the comments should explain everything that the block of code below it is doing.
# Creating the canvas which can paint any wanted Object from another class
class Canvas:
# Initialising the screen and setting all needed variables
def __init__(self, painting):
pygame.init()
self.screen_size = (600, 600)
self.background = (25, 255, 255)
self.screen = pygame.display.set_mode(self.screen_size)
self.paint = painting
# Let the user set the name of the canvas
def set_screen_name(self):
return self.screen
# Draw the everything you want to
def update(self):
# Paint the canvas
self.screen.fill(self.background)
# Make the game be quittable
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
# Draw the defined Circle and then update the Canvas
# it only draws a circle if directly tip pygame.draw.circle(surface, color, position, radius)
self.paint
pygame.display.update()
# Draw any circle you like
class Cir:
# Get all the required Information's to Draw a circle
def __init__(self, canvas, what_color, position, radius, line=0):
self.can = canvas
self.color = what_color
self.pos = position
self.r = radius
self.line = line
self.cir = None
# Create the circle with the acquired Information's
def create(self):
self.cir = pygame.draw.circle(self.can, self.color, self.pos, self.r, self.line)
return self.cir
# So far there is no Surface for the Cir class
# And there is no Object that cloud be painted for the Canvas class
# I initialise a canvas instance without anything that needs to be painted
get_surface = Canvas(None)
# Now I can access set_screen_name from the Canvas class and give the surface a name
# Which the Cir class can now use as a surface
screen = get_surface.set_screen_name()
c1 = pygame.draw.circle(screen, (0,0,0), (300, 300), 30)
print(c1)
# I'm initialising a circle
init_circle = Cir(screen, (0, 255, 0), (300, 300), 30)
# Create the initialised circle
circle_1 = init_circle.create()
# Give the Canvas class the created circle
paint = Canvas(circle_1)
# Draw the circle
paint.update()
My circle turns to a rect.
Actually, no, it doesn't. As per the documentation for those drawing functions, the intent of the calls is to draw something immediately, not to give you an object you can draw later:
Draw several simple shapes to a Surface.
From analysis of your question, it sounds like you believe that you are storing the act of drawing the circle so that it can be done later. That is not the case. Instead, what you are doing is actually drawing the circle and saving the result of that drawing action - evaluating the result later on will not actually draw, or redraw, the circle.
So, if the draw function is not returning something for later drawing, what is it returning? That can also be found in the above-mentioned documentation:
The functions return a rectangle representing the bounding area of changed pixels.
In other words, it's telling you the smallest rectangle that was changed by the drawing action - this will be a square with sides the same length as the diameter and centered around the same point.
Obviously, the authors of PyGame thought this information may be handy for some purpose, just not the purpose of redrawing the circle :-)
One way to do what you're trying to achieve would be to simply have a function to draw the "predefined" circle and call that instead of trying to evaluate the rectangle returned from a previous call.

Pygame blit part of an image(background image)

I have a pygame menu where i have drawn some buttons, which represent the level difficulty of my game. For user convenience, i have made a sprite which indicates which level-button is selected(think of it as a light green frame around the button). Now, if i have a solid color as my background, i can just fill the frame with the bg color. But i wanna have a custom image. However i am not sure how to do the deleting stuff with this image. I dont want to have to do a surface.blit(bgImage, surface.get_rect())
in every while-loop. Is there any way to tell pygame to blit just part of the image? So the end-result is still fine-looking. Here is my code when i have a color as the background
(please note that my question does not apply only to this scenario, its more of a general way as to blitting part of an image, without having to rely on cropping the image using 3rd party software like paint(net), photoshop etc.):
#class for the highlight sprite that appears when a level button is clicked
class HighLightImage(Sprite):
def __init__(self, spriteX, spriteY, width = 180, height = 60):
Sprite.__init__(self)
self.rect = pygame.Rect(spriteX, spriteY, width, height)
self.image = pygame.image.load("highlight.png")
#function to draw the highlight sprite, after deleting its older position.
def draw(self, newSpriteX, newSpriteY):
#due to technical issues the following method is using 4 dirty sprite deletions.
surface.fill(bgCol, (self.rect.x, self.rect.y, self.rect.width, 10))
surface.fill(bgCol, (self.rect.x, self.rect.y + self.rect.height-10, self.rect.width, 10))
surface.fill(bgCol, (self.rect.x, self.rect.y, 10, self.rect.height))
surface.fill(bgCol, ( self.rect.x + self.rect.width-10, self.rect.y, 10, self.rect.height))
self.rect.x = newSpriteX
self.rect.y = newSpriteY
surface.blit(self.image, self.rect)
And here is the main while-loop
def mainIntro():
#snake image
snakeImg = pygame.image.load("snakeB.png")
snakeImg = pygame.transform.scale(snakeImg, (150,200))
#highlight obj
hlObj = HighLightImage(0, 0)
#starting level = 1
levels = 1
#initial fill
surface.fill(bgCol)
intro = True
#start button
startButton = StartButton(WIDTH/2-330, HEIGHT - 150)
startButton.draw("Start")
#Exit button
exitButton = ExitButton(WIDTH/2+110, HEIGHT - 150)
exitButton.draw("Exit")
#level buttons
easyLvl = EasyLevelButton( 65, HEIGHT/2 )
easyLvl.draw("Easy")
midLvl = MediumLevelButton( 320, HEIGHT/2 )
midLvl.draw("Medium")
hardLvl = HardLevelButton( 570, HEIGHT/2 )
hardLvl.draw("Hard")
instructions()
surface.blit(snakeImg, (WIDTH/2-75, HEIGHT - 250))
while intro:
for ev in pygame.event.get():
# X exit event
if ev.type == QUIT:
pygame.quit()
sys.exit()
if ev.type == MOUSEMOTION:
startButton.hover()
exitButton.hover()
easyLvl.hover()
midLvl.hover()
hardLvl.hover()
if ev.type == MOUSEBUTTONDOWN:
if easyLvl.clicked():
levels = 1
if midLvl.clicked():
levels = 2
if hardLvl.clicked():
levels = 4
#button exit event
elif exitButton.clicked():
pygame.quit()
sys.exit()
elif startButton.clicked():
intro = False
#highlight frame, according to level-button chosen
if levels == 1:
hlObj.draw(easyLvl.x-10, easyLvl.y-10)
elif levels == 2:
hlObj.draw(midLvl.x-10, midLvl.y-10)
elif levels == 4:
hlObj.draw(hardLvl.x-10, hardLvl.y-10)
update()
return levels
Finally here is an image of the end result :
P.s In the above code snippets i have not included the button classes, and the global variables like colors, width, height etc., since i dont think they are relevant with what i want to accomplice. Feel free to correct my code, and/or suggest improvements.
As #cmd said above, the area param would be a good option, but for more information, try the pygame docs or have a look at this question or try pygame.transform.chop()
Try the code below:
pygame.init()
size = width, height = 1200, 800
screen = pygame.display.set_mode(size)
image = pygame.image.load("example.png")
rect = image.get_rect()
cropx,cropy = 100,10 #Change value to crop different rect areas
cropRect = (cropx, cropy, rect.w,rect.h)
while(True):
for event in pygame.event.get():
if(event.type == pygame.QUIT):
pygame.quit()
sys.exit()
screen.blit(image,cropRect,cropRect)
pygame.display.update()

Creating walls from surface data in Pygame

I'm new to the python language and pygame, what I'm trying to do is create a function which will convert the data from a 2D black and white image into areas where the player can or cannot move. In other words: I want to take a maze that I've drawn in a graphics program and use pygame to interpolate black and white images so that a player class object may move wherever the surface contains bytes of (255, 255, 255) and cannot move where the surface contains bytes of (0, 0, 0) First I need to be able to get the data. What I have so far is.
def GetMaze(M):
Get = M.get_view('2')
Sep = Get.raw
In this function M represents a black and white bitmap of a maze ('Maze.bmp'). I know what I have so far will grab the bytes in an unstructured block of bytes. What I need this function to do is iterate the bytes into lists of floors where the bytes equal (255, 255, 255) and walls where they equal (0, 0, 0) but I can't figure out how to get it to that point.
Using code found here http://www.petercollingridge.co.uk/book/export/html/550:
import sys, pygame
from pygame.locals import *
screen = pygame.display.set_mode((200,200))
imgsurf = pygame.Surface((200,200))
def init():
global mazearray
mazeimg = pygame.image.load("maze.bmp")
mazearray = pygame.surfarray.array3d(mazeimg)
def update():
global mazearray
screen.fill((0,0,0))
imgsurf.fill((255,255,255))
for x in range(200):
for y in range(200):
if mazearray[x][y][0] == 0 and mazearray[x][y][1] == 0 and mazearray[x][y][2] == 0:
pygame.draw.rect(imgsurf, (0,0,0), (x,y,2,2))
screen.blit(imgsurf, (0,0))
pygame.display.flip()
if __name__=="__main__":
init()
update()
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
sys.exit()
keys=pygame.key.get_pressed()
if keys[K_q]:
sys.exit()
Obviously you will need to change the sizes for your maze. Also, I hand drew the maze in GIMP, so the lines were wonky, hence the maze it drew was wonky.