Related
I'm a beginner programmer who is starting with python and I'm starting out by making a game in pygame.
The game basically spawns circles at random positions and when clicked, it gives you points.
Recently I've hit a roadblock when I want to spawn multiple instances of the same object (in this case circles) at the same time.
I've tried stuff like sleep() and some other code related to counters, but it always results in the next circle spawned overriding the previous one (i.e the program spawns circle 1, but when circle 2 comes in, circle 1 disappears).
Does anyone know a solution to this? I would really appreciate your help!
import pygame
import random
import time
pygame.init()
window = pygame.display.set_mode((800,600))
class circle():
def __init__(self, color, x, y, radius, width,):
self.color = color
self.x = x
self.y = y
self.radius = radius
self.width = width
def draw(self, win, outline=None):
pygame.draw.circle(win, self.color, (self.x, self.y, self.radius, self.width), 0)
run=True
while run:
window.fill((0, 0, 0))
pygame.draw.circle(window, (255, 255, 255), (random.randint(0, 800),random.randint(0, 600)), 20, 20)
time.sleep(1)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
pygame.quit()
quit()
It does not work that way. time.sleep, pygame.time.wait() or pygame.time.delay is not the right way to control time and gameplay within an application loop. The game does not respond while you wait. The application loop runs continuously. You have to measure the time in the loop and spawn the objects according to the elapsed time.
pygame.Surface.fill clears the entire screen. Add the newly created objects to a list. Redraw all of the objects and the entire scene in each frame.
See also Time, timer event and clock
You have 2 options. Use pygame.time.get_ticks() to measure the time. Define a time interval after which a new object should appear. Create an object when the point in time is reached and calculate the point in time for the next object:
object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
next_object_time = 0
while run:
# [...]
current_time = pygame.time.get_ticks()
if current_time > next_object_time:
next_object_time += time_interval
object_list.append(Object())
Minimal example:
repl.it/#Rabbid76/PyGame-TimerSpawnObjects
import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))
class Object:
def __init__(self):
self.radius = 50
self.x = random.randrange(self.radius, window.get_width()-self.radius)
self.y = random.randrange(self.radius, window.get_height()-self.radius)
self.color = pygame.Color(0)
self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
next_object_time = 0
run = True
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
current_time = pygame.time.get_ticks()
if current_time > next_object_time:
next_object_time += time_interval
object_list.append(Object())
window.fill(0)
for object in object_list[:]:
pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
object.radius -= 0.2
if object.radius < 1:
object_list.remove(object)
pygame.display.flip()
pygame.quit()
exit()
The other option is to use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)
Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event.
Receive the event in the event loop:
while run:
for event in pygame.event.get():
if event.type == timer_event:
object_list.append(Object())
The timer event can be stopped by passing 0 to the time argument of pygame.time.set_timer.
Minimal example:
repl.it/#Rabbid76/PyGame-TimerEventSpawn
import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))
class Object:
def __init__(self):
self.radius = 50
self.x = random.randrange(self.radius, window.get_width()-self.radius)
self.y = random.randrange(self.radius, window.get_height()-self.radius)
self.color = pygame.Color(0)
self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)
run = True
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == timer_event:
object_list.append(Object())
window.fill(0)
for object in object_list[:]:
pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
object.radius -= 0.2
if object.radius < 1:
object_list.remove(object)
pygame.display.flip()
pygame.quit()
exit()
I've been searching for some good tutorial about making simple sprite animation from few images in Python using Pygame. I still haven't found what I'm looking for.
My question is simple: how to make an animated sprite from few images (for an example: making few images of explosion with dimensions 20x20px to be as one but animated)
Any good ideas?
There are two types of animation: frame-dependent and time-dependent. Both work in similar fashion.
Before the main loop
Load all images into a list.
Create three variable:
index, that keeps track on the current index of the image list.
current_time or current_frame that keeps track on the current time or current frame since last the index switched.
animation_time or animation_frames that define how many seconds or frames should pass before switching image.
During the main loop
Increment current_time by the amount of seconds that has passed since we last incremented it, or increment current_frame by 1.
Check if current_time >= animation_time or current_frame >= animation_frame. If true continue with 3-5.
Reset the current_time = 0 or current_frame = 0.
Increment the index, unless if it'll be equal or greater than the amount of images. In that case, reset index = 0.
Change the sprite's image accordingly.
A full working example
import os
import pygame
pygame.init()
SIZE = WIDTH, HEIGHT = 720, 480
BACKGROUND_COLOR = pygame.Color('black')
FPS = 60
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
def load_images(path):
"""
Loads all images in directory. The directory must only contain images.
Args:
path: The relative or absolute path to the directory to load images from.
Returns:
List of images.
"""
images = []
for file_name in os.listdir(path):
image = pygame.image.load(path + os.sep + file_name).convert()
images.append(image)
return images
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, position, images):
"""
Animated sprite object.
Args:
position: x, y coordinate on the screen to place the AnimatedSprite.
images: Images to use in the animation.
"""
super(AnimatedSprite, self).__init__()
size = (32, 32) # This should match the size of the images.
self.rect = pygame.Rect(position, size)
self.images = images
self.images_right = images
self.images_left = [pygame.transform.flip(image, True, False) for image in images] # Flipping every image.
self.index = 0
self.image = images[self.index] # 'image' is the current image of the animation.
self.velocity = pygame.math.Vector2(0, 0)
self.animation_time = 0.1
self.current_time = 0
self.animation_frames = 6
self.current_frame = 0
def update_time_dependent(self, dt):
"""
Updates the image of Sprite approximately every 0.1 second.
Args:
dt: Time elapsed between each frame.
"""
if self.velocity.x > 0: # Use the right images if sprite is moving right.
self.images = self.images_right
elif self.velocity.x < 0:
self.images = self.images_left
self.current_time += dt
if self.current_time >= self.animation_time:
self.current_time = 0
self.index = (self.index + 1) % len(self.images)
self.image = self.images[self.index]
self.rect.move_ip(*self.velocity)
def update_frame_dependent(self):
"""
Updates the image of Sprite every 6 frame (approximately every 0.1 second if frame rate is 60).
"""
if self.velocity.x > 0: # Use the right images if sprite is moving right.
self.images = self.images_right
elif self.velocity.x < 0:
self.images = self.images_left
self.current_frame += 1
if self.current_frame >= self.animation_frames:
self.current_frame = 0
self.index = (self.index + 1) % len(self.images)
self.image = self.images[self.index]
self.rect.move_ip(*self.velocity)
def update(self, dt):
"""This is the method that's being called when 'all_sprites.update(dt)' is called."""
# Switch between the two update methods by commenting/uncommenting.
self.update_time_dependent(dt)
# self.update_frame_dependent()
def main():
images = load_images(path='temp') # Make sure to provide the relative or full path to the images directory.
player = AnimatedSprite(position=(100, 100), images=images)
all_sprites = pygame.sprite.Group(player) # Creates a sprite group and adds 'player' to it.
running = True
while running:
dt = clock.tick(FPS) / 1000 # Amount of seconds between each loop.
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
player.velocity.x = 4
elif event.key == pygame.K_LEFT:
player.velocity.x = -4
elif event.key == pygame.K_DOWN:
player.velocity.y = 4
elif event.key == pygame.K_UP:
player.velocity.y = -4
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT or event.key == pygame.K_LEFT:
player.velocity.x = 0
elif event.key == pygame.K_DOWN or event.key == pygame.K_UP:
player.velocity.y = 0
all_sprites.update(dt) # Calls the 'update' method on all sprites in the list (currently just the player).
screen.fill(BACKGROUND_COLOR)
all_sprites.draw(screen)
pygame.display.update()
if __name__ == '__main__':
main()
When to chose which
Time-dependent animation allows you to play the animation at the same speed, no matter how slow/fast the frame-rate is or slow/fast your computer is. This allows your program to freely change the framerate without affecting the animation and it'll also be consistent even if the computer cannot keep up with the framerate. If the program lags the animation will catch up to the state it should've been as if no lag had happened.
Although, it might happen that the animation cycle don't synch up with the framerate, making the animation cycle seem irregular. For example, say that we have the frames updating every 0.05 second and the animation switch image every 0.075 second, then the cycle would be:
Frame 1; 0.00 seconds; image 1
Frame 2; 0.05 seconds; image 1
Frame 3; 0.10 seconds; image 2
Frame 4; 0.15 seconds; image 1
Frame 5; 0.20 seconds; image 1
Frame 6; 0.25 seconds; image 2
And so on...
Frame-dependent can look smoother if your computer can handle the framerate consistently. If lag happens it'll pause in its current state and restart when the lag stops, which makes the lag more noticeable. This alternative is slightly easier to implement since you just need to increment current_frame with 1 on each call, instead of dealing with the delta time (dt) and passing it to every object.
Sprites
Result
You could try modifying your sprite so that it swaps out its image for a different one inside update. That way, when the sprite is rendered, it'll look animated.
Edit:
Here's a quick example I drew up:
import pygame
import sys
def load_image(name):
image = pygame.image.load(name)
return image
class TestSprite(pygame.sprite.Sprite):
def __init__(self):
super(TestSprite, self).__init__()
self.images = []
self.images.append(load_image('image1.png'))
self.images.append(load_image('image2.png'))
# assuming both images are 64x64 pixels
self.index = 0
self.image = self.images[self.index]
self.rect = pygame.Rect(5, 5, 64, 64)
def update(self):
'''This method iterates through the elements inside self.images and
displays the next one each tick. For a slower animation, you may want to
consider using a timer of some sort so it updates slower.'''
self.index += 1
if self.index >= len(self.images):
self.index = 0
self.image = self.images[self.index]
def main():
pygame.init()
screen = pygame.display.set_mode((250, 250))
my_sprite = TestSprite()
my_group = pygame.sprite.Group(my_sprite)
while True:
event = pygame.event.poll()
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
# Calling the 'my_group.update' function calls the 'update' function of all
# its member sprites. Calling the 'my_group.draw' function uses the 'image'
# and 'rect' attributes of its member sprites to draw the sprite.
my_group.update()
my_group.draw(screen)
pygame.display.flip()
if __name__ == '__main__':
main()
It assumes that you have two images called image1.png and image2.png inside the same folder the code is in.
You should have all your sprite animations on one big "canvas", so for 3 20x20 explosion sprite frames you will have 60x20 image. Now you can get right frames by loading an area of the image.
Inside your sprite class, most likely in update method you should have something like this (hardcoded for simplicity, I prefer to have separate class to be responsible for picking the right animation frame). self.f = 0 on __init__.
def update(self):
images = [[0, 0], [20, 0], [40, 0]]
self.f += 1 if self.f < len(images) else 0
self.image = your_function_to_get_image_by_coordinates(images[i])
For an animated Sprite a list of images (pygame.Surface objects) must be generated. A different picture of the list is displayed in each frame, just like in the pictures of a movie. This gives the appearance of an animated object.
One way to get a list of images is to load an animated GIF (Graphics Interchange Format). Unfortunately, PyGame doesn't offer a function to load the frames of an animated GIF. However, there are several Stack Overflow answers that address this issue:
How can I load an animated GIF and get all of the individual frames in PyGame?
How do I make a sprite as a gif in pygame?
Pygame and Numpy Animations
One way is to use the popular Pillow library (pip install Pillow). The following function loads the frames of an animated GIF and generates a list of pygame.Surface objects:
from PIL import Image, ImageSequence
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
for frame in ImageSequence.Iterator(pilImage):
frame = frame.convert('RGBA')
pygameImage = pygame.image.fromstring(
frame.tobytes(), frame.size, frame.mode).convert_alpha()
frames.append(pygameImage)
return frames
Create a pygame.sprite.Sprite class that maintains a list of images. Implement an update method that selects a different image in each frame.
Pass the list of images to the class constructor. Add an index attribute that indicates the index of the current image in the list. Increase the index in the Update method. Reset the index if it is greater than or equal to the length of the image list (or use the modulo (%) operator). Get the current image from the list by subscription:
class AnimatedSpriteObject(pygame.sprite.Sprite):
def __init__(self, x, bottom, images):
pygame.sprite.Sprite.__init__(self)
self.images = images
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom = (x, bottom))
self.image_index = 0
def update(self):
self.image_index += 1
if self.image_index >= len(self.images):
self.image_index = 0
self.image = self.images[self.image_index]
See also Load animated GIF and Sprite
Example GIF (from Animated Gifs, Animated Image):
Minimal example: repl.it/#Rabbid76/PyGame-SpriteAnimation
import pygame
from PIL import Image, ImageSequence
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
for frame in ImageSequence.Iterator(pilImage):
frame = frame.convert('RGBA')
pygameImage = pygame.image.fromstring(
frame.tobytes(), frame.size, frame.mode).convert_alpha()
frames.append(pygameImage)
return frames
class AnimatedSpriteObject(pygame.sprite.Sprite):
def __init__(self, x, bottom, images):
pygame.sprite.Sprite.__init__(self)
self.images = images
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom = (x, bottom))
self.image_index = 0
def update(self):
self.image_index += 1
self.image = self.images[self.image_index % len(self.images)]
self.rect.x -= 5
if self.rect.right < 0:
self.rect.left = pygame.display.get_surface().get_width()
pygame.init()
window = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
ground = window.get_height() * 3 // 4
gifFrameList = loadGIF('stone_age.gif')
animated_sprite = AnimatedSpriteObject(window.get_width() // 2, ground, gifFrameList)
all_sprites = pygame.sprite.Group(animated_sprite)
run = True
while run:
clock.tick(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
all_sprites.update()
window.fill((127, 192, 255), (0, 0, window.get_width(), ground))
window.fill((255, 127, 64), (0, ground, window.get_width(), window.get_height() - ground))
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
exit()
I have an image in pygame. I want to apply antialiasing to it. How can I do this? Ideally, I would be able to do this just with pygame and built-in modules but I'm open to other options if necessary.
More specifically, I've got an image of a square split into 4. Each of the quadrants has a different colour. I want to blur it so it looks more like a gradient, so instead of the colours switching instantly where the quadrants meet, it would fade slowly. I believe anti-aliasing is the best way to accomplish this? Here's an image of what I mean:left: what I've got, right: what I need to have
It's not clear what specific image smoothing algorithm you want to use. SciPy provides some relevant functions, so I've constructed an example using ndimage.gaussian_filter().
Clicking a mouse button will apply the filter, or you can scroll the wheel.
import pygame
from scipy.ndimage import gaussian_filter
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html
def smooooooth(screen):
"""Apply a gaussian filter to each colour plane"""
# Get reference pixels for each colour plane and then apply filter
r = pygame.surfarray.pixels_red(screen)
gaussian_filter(r, sigma=72, mode="nearest", output=r)
g = pygame.surfarray.pixels_green(screen)
gaussian_filter(g, sigma=72, mode="nearest", output=g)
b = pygame.surfarray.pixels_blue(screen)
gaussian_filter(b, sigma=72, mode="nearest", output=b)
height = 300
width = 300
pygame.init()
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
pygame.display.set_caption("Smooth Image")
clock = pygame.time.Clock()
# draw initial pattern
screen.fill((194, 198, 199)) # fill with gray
# fill top left quadrant with white
screen.fill(pygame.Color("white"), pygame.Rect(0, 0, width // 2, height // 2))
# fill bottom left quadrant with black
screen.fill(pygame.Color("black"), pygame.Rect(width // 2, height // 2, width, height))
count = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONUP:
smooooooth(screen)
count += 1
pygame.display.set_caption(f"Smooth Image {count}")
pygame.display.update()
# limit framerate
clock.tick(30)
pygame.quit()
If you play with the sigma argument to the gaussian_filter function, you might be able to get closer to what you're after. E.g. with sigma 72, two passes looks like this:
I've modified the example code to properly support screen resizing, which will redraw the base pattern and using the + and - keys to increment the sigma value so you can do it interactively. Hold Shift to adjust by ten instead of one.
import pygame
from scipy.ndimage import gaussian_filter
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html
def smooooooth(screen, sisgma):
"""Apply a gaussian filter to each colour plane"""
# Get reference pixels for each colour plane and then apply filter
r = pygame.surfarray.pixels_red(screen)
gaussian_filter(r, sigma=sigma, mode="nearest", output=r)
g = pygame.surfarray.pixels_green(screen)
gaussian_filter(g, sigma=sigma, mode="nearest", output=g)
b = pygame.surfarray.pixels_blue(screen)
gaussian_filter(b, sigma=sigma, mode="nearest", output=b)
def draw_grey_squares(screen):
"""Draw the base pattern"""
screen.fill((194, 198, 199)) # fill with grey
# fill top left quadrant with white
screen.fill(pygame.Color("white"), (0, 0, width // 2, height // 2))
# fill bottom left quadrant with black
screen.fill(pygame.Color("black"), (width // 2, height // 2, width, height))
height = 300
width = 300
pygame.init()
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
draw_grey_squares(screen)
pygame.display.set_caption("Smooth Image")
clock = pygame.time.Clock()
sigma = 72
count = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.VIDEORESIZE:
# redraw on window resize
width, height = event.w, event.h
draw_grey_squares(screen)
count = 0
elif event.type == pygame.KEYUP:
# support + and - to modify sigma
if event.key in (pygame.K_PLUS, pygame.K_KP_PLUS):
if event.mod & pygame.KMOD_SHIFT:
sigma += 10
else:
sigma += 1
elif event.key in (pygame.K_MINUS, pygame.K_KP_MINUS):
if event.mod & pygame.KMOD_SHIFT:
sigma -= 10
else:
sigma -= 1
sigma = max(sigma, 1) # sigma below one doesn't make sense
elif event.type == pygame.MOUSEBUTTONUP:
smooooooth(screen, sigma)
count += 1
pygame.display.set_caption(f"Smooth Image {count} σ {sigma}")
pygame.display.update()
clock.tick(30) # limit framerate
pygame.quit()
Here's an example with sigma 200:
This question already has an answer here:
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
(1 answer)
Closed 1 year ago.
I read an article about how a mouse cursor can detect a rect, and it includes the line ".get_rect()" but somehow it doesnt work
heres the articles code ->
import pygame
pygame.init()
width=350;
height=400
screen = pygame.display.set_mode( (width, height ) )
pygame.display.set_caption('clicked on image')
redSquare = pygame.image.load("images/red-square.png").convert()
x = 20; # x coordnate of image
y = 30; # y coordinate of image
screen.blit(redSquare , ( x,y)) # paint to screen
pygame.display.flip() # paint screen one time
running = True
while (running):
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
# Set the x, y postions of the mouse click
x, y = event.pos
if redSquare.get_rect().collidepoint(x, y):
print('clicked on image')
#loop over, quite pygame
pygame.quit()
heres my code ->
import pygame
import os
import sys
pygame.init()
width,height = (1100,800)
WIN = pygame.display.set_mode((width,height))
global bcard
bg_filename = os.path.join('C:\\Users\\USER\\Desktop\\Python\\picture match','background.jpg')
bg = pygame.image.load(bg_filename)
bg = pygame.transform.scale(bg, (width, height)).convert()
card_width=130
card_height=160
blue_card=pygame.image.load(os.path.join('C:\\Users\\USER\\Desktop\\Python\\picture match','blue_card.png'))
red_card=pygame.image.load(os.path.join('C:\\Users\\USER\\Desktop\\Python\\picture match','red_card.png'))
bcard=pygame.transform.scale(blue_card,(card_width,card_height)).convert()
rcard=pygame.transform.scale(red_card,(card_width,card_height)).convert()
text=pygame.image.load(os.path.join('C:\\Users\\USER\\Desktop\\Python\\picture match','text.png'))
global clicking
clicking = False
def pictures():
global card1
card1=WIN.blit(bcard,(30,200))
card2=WIN.blit(rcard,(200,200))
card3=WIN.blit(bcard,(370,200))
card4=WIN.blit(rcard,(550,200))
card5=WIN.blit(bcard,(730,200))
card6=WIN.blit(rcard,(900,200))
card7=WIN.blit(rcard,(30,400))
card8=WIN.blit(bcard,(200,400))
card9=WIN.blit(rcard,(370,400))
card10=WIN.blit(bcard,(550,400))
card11=WIN.blit(rcard,(730,400))
card12=WIN.blit(bcard,(900,400))
card13=WIN.blit(bcard,(30,600))
card14=WIN.blit(rcard,(200,600))
card15=WIN.blit(bcard,(370,600))
card16=WIN.blit(rcard,(550,600))
card17=WIN.blit(bcard,(730,600))
card18=WIN.blit(rcard,(900,600))
card1_rect=pygame.Rect(30,200,130,160)
card1_rect=pygame.Rect(200,200,130,160)
card1_rect=pygame.Rect(370,200,130,160)
card1_rect=pygame.Rect(550,200,130,160)
card1_rect=pygame.Rect(730,200,130,160)
card1_rect=pygame.Rect(900,200,130,160)
WIN.blit(text,(25,0))
def draw():
WIN.blit(bg,(0,0))
pictures()
def main():
global clicking
global card1
global bcard
run = True
mx , my = pygame.mouse.get_pos()
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if bcard.get_rect().collidepoint(mx, my):
print('clicked on image')
draw()
pygame.display.flip()
main()
its suppose to be a picture match game btw, heres the error code "AttributeError: 'pygame.Surface' object has no attribute 'get_rect'"
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. A Surface is blit at a position on the screen. The position of the rectangle can be specified by a keyword argument. For example, the top left of the rectangle can be specified with the keyword argument topleft:
if bcard.get_rect().collidepoint(mx, my):
if bcard.get_rect(topleft = (30, 200)).collidepoint(mx, my):
print('clicked on image')
Just started learning Python/Pygame watching videos and reading to learn . I would like to see a example code to cycle 3 images on a rect button from a mouse press and return to first image. Really I want the pictures to be three options and return different results. So be able to cycle image be able to select option and option when triggered execute choice.
Example
import pygame
pygame.init()
screen = pygame.display.set_mode((300,200))
# three images
images = [
pygame.Surface((100,100)),
pygame.Surface((100,100)),
pygame.Surface((100,100)),
]
images[0].fill((255,0,0))
images[1].fill((0,255,0))
images[2].fill((0,0,255))
# image size and position
images_rect = images[0].get_rect()
# starting index
index = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# check mouse position and pressed button
if event.button == 1 and images_rect.collidepoint(event.pos):
# cycle index
index = (index+1) % 3
screen.blit(images[index], images_rect)
pygame.display.flip()
pygame.quit()
Example using class - to create many buttons
import pygame
class Button(object):
def __init__(self, position, size):
self._images = [
pygame.Surface(size),
pygame.Surface(size),
pygame.Surface(size),
]
self._images[0].fill((255,0,0))
self._images[1].fill((0,255,0))
self._images[2].fill((0,0,255))
self._rect = pygame.Rect(position, size)
self._index = 0
def draw(self, screen):
screen.blit(self._images[self._index], self._rect)
def event_handler(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and self._rect.collidepoint(event.pos):
self._index = (self._index+1) % 3
pygame.init()
screen = pygame.display.set_mode((320,110))
button1 = Button((5, 5), (100, 100))
button2 = Button((110, 5), (100, 100))
button3 = Button((215, 5), (100, 100))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
button1.event_handler(event)
button2.event_handler(event)
button3.event_handler(event)
button1.draw(screen)
button2.draw(screen)
button3.draw(screen)
pygame.display.flip()
pygame.quit()
If I understood the question correctly, you need a single button that changes the look every time you click on it, and changes its relative function accordingly.
You should be able to solve your problem by creating a class that takes two list and a counter
1) list of images
2) list of functions
3) the counter tells you which image/function is selected.
The functions needs to be built in the class, but you can provide the image that you want in the class argument (actually, you could pass them as arguments, but I don't think is worth it).
Here is the code, I commented some lines with their intended meaning (in line comments)
import pygame
import sys
pygame.init()
width = 600
height = 400
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Magic Buttons")
background = pygame.Surface(screen.get_size())
clock = pygame.time.Clock()
class Buttons:
def __init__(self, posX, posY, image1, image2, image3):
self.image_list = [image1, image2, image3] # static list of images
self.function_list = [self.button_function_1,self.button_function_2,self.button_function_3 ]
self.rect_position = (posX, posY) # this is a tuple to identify the upper left corner of the rectangle of the image
self.button_type = 0 # initial value of the button, both for the function and the image
self.image = pygame.image.load(self.image_list[0]) #default image, index number 0 of image_list
self.rect = pygame.Rect(posX, posY, self.image.get_width(), self.image.get_height()) # create a rectangle object same size of the images
def check(self, pos):
if self.rect.collidepoint(pos) ==True:
self.change_button()
else:
pass
def change_button(self):
self.button_type = (self.button_type +1)%3
self.image = pygame.image.load(self.image_list[self.button_type ]) # load the image relative to button_type
self.function_list[self.button_type -1]() # execute the function relative to the new value of button_type
self.draw_button()
def draw_button(self):
screen.blit(self.image, self.rect_position) # blit the new button image
def button_function_1(self):
print ("function 1 in action")
def button_function_2(self):
print ("function 2 in action")
def button_function_3(self):
print ("function 3 in action")
multibutton = Buttons(100,100,"button1.png","button2.png","button3.png") # create an istance of the button in the x=100, y = 100, with the three image button
while True:
background.fill((0,0,0))
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos() # fetch the position of the mouse
multibutton.check(pos) # check if the mouse is on the button
multibutton.draw_button()
pygame.display.flip()