This question already has answers here:
Spawning multiple instances of the same object concurrently in python
(1 answer)
How to wait some time in pygame?
(3 answers)
Is there a way to have different tick rates for differents parts of my code in pygame?
(1 answer)
How to run multiple while loops at a time in Pygame
(1 answer)
Closed 6 months ago.
I have a list of images that I want pygame to draw in two rows, one by one, with a one second delay between them. The code I wrote is this:
import pygame
def main():
FPS = 60
clock = pygame.time.Clock()
clock.tick(FPS)
delay = 50
WIN = pygame.display.set_mode((500, 500))
WIN.fill((0,0,0))
pygame.display.update()
pygame.time.delay(delay)
run = True
while run:
# positions of the first two images to be drawn
x1, y1 = 20, 20
x2, y2 = 20, 300
i = 0
for _ in range(len(IMAGES)):
if i >= len(IMAGES):
break
image = IMAGES[i]
WIN.blit(image, (x1, y1))
pygame.display.update()
pygame.time.delay(delay)
i += 1
if i >= len(IMAGES):
break
image = IMAGES[i]
WIN.blit(image, (x2, y2))
pygame.display.update()
pygame.time.delay(delay)
i += 1
x1 += 10
x2 += 10
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
main()
The program crashes if I set the delay higher than 50 milliseconds. After some research here on StackOverflow, I found out that pygame.time.delay and pygame.time.wait can't be used in my case. However, the numerous workarounds suggested here are too complicated for me, as I'm only a beginner.
Is there a simple, straightforward way to make my program to work properly?
Thanks!
The best way to do this is through custom events, you add another image every time you receive an event. That way the rest of the event handling can take place and the operating system won't think your application is no longer responding.
Here's a minimal example, it doesn't limit the number of squares, although your can either skip the image creation in the event handler, or call set_timer(…) with a loops keyword argument to specify the number or events to be generated.
import pygame
import itertools
CUSTOM_TIMER_EVENT = pygame.USEREVENT + 1
my_colors = ["red", "orange", "yellow", "green", "blue", "purple"]
# create an iterator that will repeat these colours forever
color_cycler = itertools.cycle([pygame.color.Color(c) for c in my_colors])
pygame.init()
pygame.font.init()
clock = pygame.time.Clock()
width, height = 320, 240
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Timer Rows")
squares = []
done = False
x, y = 20, 20 # initial position
pygame.time.set_timer(CUSTOM_TIMER_EVENT, 100) # tenth of a second
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == CUSTOM_TIMER_EVENT:
image = pygame.Surface((8, 8))
image.fill(next(color_cycler))
squares.append((image, (x, y)))
x += 10
if x > (width - 20): # next row
x = 20
y += 20
# Graphics
screen.fill(pygame.Color("black"))
# Draw squares
for image, pos in squares:
screen.blit(image, pos)
# Update Screen
pygame.display.update()
clock.tick(30)
pygame.quit()
The timer is set to 100 ms instead of a second due to my own impatience.
But suppose you want to do something with your newly created squares. Instead of creating everything from scratch because it seems easier than reading documentation, I would encourage you to use sprites.
Defining our Sprite:
class Square(pygame.sprite.Sprite):
def __init__(self, size, pos, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([size[0], size[1]])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = pos[0] # sprite tracks its position
self.rect.y = pos[1]
Instead of a list, squares is a sprite group:
squares = pygame.sprite.Group()
The event handler changes to create a sprite and add it to the group instead of creating an image and adding it the list:
elif event.type == CUSTOM_TIMER_EVENT:
square = Square((8,8), (x,y), next(color_cycler))
squares.add(square)
But we want to do things with the sprite, so we can use its update function to check if the mouse is over it, and if so, destroy the sprite:
def update(self):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.kill()
This will look something like:
Here's the full code listing:
import pygame
import itertools
class Square(pygame.sprite.Sprite):
def __init__(self, size, pos, color):
pygame.sprite.Sprite.__init__(self)
self.size = size
self.image = pygame.Surface([size[0], size[1]])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = pos[0]
self.rect.y = pos[1]
def update(self):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.kill()
CUSTOM_TIMER_EVENT = pygame.USEREVENT + 1
my_colors = ["red", "orange", "yellow", "green", "blue", "purple"]
# create an iterator that will repeat these colours forever
color_cycler = itertools.cycle([pygame.color.Color(c) for c in my_colors])
pygame.init()
pygame.font.init()
clock = pygame.time.Clock()
width, height = 320, 240
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption("Timer Sprite Rows")
squares = pygame.sprite.Group()
done = False
x, y = 20, 20 # initial position
pygame.time.set_timer(CUSTOM_TIMER_EVENT, 100) # tenth of a second
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == CUSTOM_TIMER_EVENT:
square = Square((8,8), (x,y), next(color_cycler))
squares.add(square)
x += 10
if x > (width - 20): # next row
x = 20
y += 20
# Graphics
screen.fill(pygame.Color("black"))
# Update Sprites
squares.update()
# Draw Sprites
squares.draw(screen)
# Update Screen
pygame.display.update()
clock.tick(30)
pygame.quit()
Related
This question already has answers here:
Pygame mouse clicking detection
(4 answers)
How do I add a border to a sprite when the mouse hovers over it, and delete it after the mouse stops?
(1 answer)
Closed 5 months ago.
So im trying to remake a simple card game in pygame. Im trying to make it where whenever you hover over a card it will turn slightly transparent by changing the alpha of the card.
However I can't seem to get this to work and im genuinely confucious as to why my code doesnt work.
while running:
mX, mY = pygame.mouse.get_pos()
if 50 + 60 > mX > 50 and 500 + 84 > mY > 500:
hand[0].set_alpha(100)
else:
hand[0].set_alpha(255)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
#Hover()
if printHand:
#print(hand)
#print(cardSize)
#print(hand[0].get_width())
#print(hand[0].get_height())
#print(hand[0].get_rect(center=(80, 542)))
printHand = False
if displayHand:
DisplayHand(hand)
DisplayBoard(pile1)
DisplayBoard(pile2)
displayHand = False
print(50 + 60 > mX > 50 and 500 + 84 > mY > 500)
pygame.display.update()
Where hand is an array of surfaces from image.load
As of right now this code doesnt do anything but the print statement at the end returns true
Here is a minimal example that creates a Card sprite with two images, swapping the image if the mouse is over the card.
import pygame
WIDTH = 640
HEIGHT = 480
FPS = 30
class Card(pygame.sprite.Sprite):
"""A playing card like sprite"""
def __init__(self, color="white", pos=(0, 0)):
pygame.sprite.Sprite.__init__(self)
self.color = pygame.Color(color)
# if you're loading an image, make sure to use .convert()
self.orig_image = pygame.Surface((50, 100), pygame.SRCALPHA)
self.orig_image.fill(self.color)
# create a copy of the image
self.alt_image = self.orig_image.copy()
self.alt_image.set_alpha(127) # make the copy transparent
self.image = self.orig_image
self.rect = self.image.get_rect(center=pos)
def update(self):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.image = self.alt_image
else:
self.image = self.orig_image
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
cards = pygame.sprite.Group()
# create five cards distributed across the bottom of the screen
for x in range(5):
card = Card(pos=((x + 1) * (WIDTH // 6), HEIGHT - 90))
cards.add(card)
pygame.display.set_caption("♠♣♥♦ Cards")
paused = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYUP:
pass
# update game elements
cards.update()
# draw surface - fill background
window.fill("black")
## draw sprites
cards.draw(window)
# show surface
pygame.display.update()
# limit frames
clock.tick(FPS)
pygame.quit()
This will look like this, although the screenshot doesn't include the mouse cursor:
I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by 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 either pygame.display.update() or pygame.display.flip()
This question already has answers here:
Pygame clock and event loops
(1 answer)
Framerate affect the speed of the game
(1 answer)
Closed last year.
I recently started with pygame and im trying to create movement but when I move my rectangle the speed changes at various times. it becomes slower and faster at various times
import pygame
pygame.init() # inisializing pygame
WIN = pygame.display.set_mode((500, 500)) # the width and hieght of the window
pygame.display.set_caption('pygame tutorial') # give the window a title
x = 50
y = 50 # character x and y positions
width = 40
height = 60 # rectangle width and height
vel = 5 # velocity (speed)
run = True # boolean variable
while run: # forever loop
for event in pygame.event.get(): # specifing the event
if event.type == pygame.QUIT: # pygame.QUIT makes the red x work so you can close the window
run = False # run = false so the while loop stops
key = pygame.key.get_pressed()
if key [pygame.K_a]:
x -= vel
if key[pygame.K_d]:
x += vel
if key[pygame.K_w]:
y -= vel
if key[pygame.K_s]:
y += vel
WIN.fill((0,0,0))
pygame.draw.rect(WIN, (255, 0, 0), (x, y, width, height))
'''
create a rectangle with 3 arguements, the window, the rgb colour, and the x,y positions width and height
'''
pygame.display.update()
pygame.quit()
You have wrong indentations so some code is executed inside many times in for-loop but it should be executed only once after for-loop
After changing indentations code work too fast and I needed to add clock.tick(60) to reduce speed to max 60 frames per second. This way it should run with the same speed on computers with older and newer CPU.
WIN = pygame.display.set_mode((500, 500)) # the width and hieght of the window
pygame.display.set_caption('pygame tutorial') # give the window a title
x = 50
y = 50 # character x and y positions
width = 40
height = 60 # rectangle width and height
vel = 5 # velocity (speed)
run = True # boolean variable
clock = pygame.time.Clock()
while run: # forever loop
for event in pygame.event.get(): # specifying the event
if event.type == pygame.QUIT: # pygame.QUIT makes the red x work so you can close the window
run = False # run = false so the while loop stops
# --- after loop --
key = pygame.key.get_pressed()
if key [pygame.K_a]:
x -= vel
if key[pygame.K_d]:
x += vel
if key[pygame.K_w]:
y -= vel
if key[pygame.K_s]:
y += vel
WIN.fill((0,0,0))
pygame.draw.rect(WIN, (255, 0, 0), (x, y, width, height))
'''
create a rectangle with 3 arguments, the window, the rgb colour, and the x,y positions width and height
'''
pygame.display.update()
clock.tick(60) # reduce speed to max 60 frames per seconds
pygame.quit()
This question already has answers here:
How to run multiple while loops at a time in Pygame
(1 answer)
Spawning multiple instances of the same object concurrently in python
(1 answer)
How to wait some time in pygame?
(3 answers)
Closed 2 years ago.
I created a small example to explain:
import pygame
pygame.init()
class Player(pygame.sprite.Sprite):
def __init__(self, color):
super().__init__()
self.image = pygame.Surface([25, 25])
self.image.fill(color)
self.color = color
self.rect = self.image.get_rect()
class Block(pygame.sprite.Sprite):
def __init__(self, color):
super().__init__()
self.image = pygame.Surface([25, 25])
self.image.fill(color)
self.color = color
self.rect = self.image.get_rect()
def update(self):
self.rect.y +=5
pygame.time.wait(500)
# Color
red = (255, 0, 0)
black = (0, 0, 0)
white = (255, 255, 255)
screen = pygame.display.set_mode([500,500])
# Sprite List
sprite_list = pygame.sprite.Group()
block_list = pygame.sprite.Group()
# Player
player = Player(red)
player.rect.x = 250
player.rect.y = 250
sprite_list.add(player)
# Block
block = Block(black)
block.rect.x = 250
block.rect.y = 0
block_list.add(block)
sprite_list.add(block)
notDone = True
clock = pygame.time.Clock()
while notDone:
for event in pygame.event.get():
if event.type == pygame.QUIT:
notDone = False
sprite_list.update()
block_collide = pygame.sprite.spritecollide(player, block_list, False)
for block in block_collide:
print("Collision")
notDone = False
screen.fill(white)
sprite_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
I wanted to create this so the block does not move every clock tick and rather move, then wait half a second, and then move once again. I wasn't exactly sure how to delay within a loop, so I simply used pygame.time.wait, however this caused my game to freeze upon start up (when I decided to run the code). Why does this keep happening?
You can not wait inside the application loop. pygame.time.wait halts the loop and makes the application irresponsible.
Use pygame.time.get_ticks() to return the number of milliseconds since pygame.init() was called. Calculate the point in time when the block needs to be move. When the time is reached, move the block and calculate the time when the block has to be moved again:
next_block_move_time = 0
while notDone:
# [...]
# sprite_list.update() <--- DELETE
current_time = pygame.time.get_ticks()
if current_time > next_block_move_time:
# set next move time
next_block_move_time = current_time + 500 # 500 milliseconds = 0.5 seconds
# move blocks
block_list .update()
I have an image of a ufo and a missile. I'm trying to get it to where if the missile hits the ufo they both would explode and disappear and then a few moments later another ufo would respawn but the collision code isn't working. can someone explain to me how to make the code work?
pygame.display.init()
pygame.font.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
ufo = pygame.image.load("ufo.png")
rocket = pygame.image.load("rocket.png")
done = False
debug = False
fontObj = pygame.font.SysFont("Courier New", 20)
#Making my Empty Lists
missiles = [] #[x,y]
ufo_list = [] #[x,y,hspeed]
particle_list = []
#UFO Respawn Info
ufoRespawn = True
ufoHits = 0
ufoSpawnTimer = 0.0
ufoSpeed = 500.0
#MISSILE Info
launchX = 400
launchY = 550
missileSpeed = 100.0
missileDirection = 0
#creating the Starfield
myStars = [] # An (initially) empty list
for i in range(1000):
x = random.randint(0, 800)
y = random.randint(0, 600)
newStar = [x, y] # A 2-element list
myStars.append(newStar)
starSpeed = 100.0 # Rate of star movement (px / s)
starDirection = 0 # 0 = not moving, -1 = left, +1 = right
#input
while not done:
event = pygame.event.poll()
if event.type == pygame.QUIT:
done = True
keys = pygame.key.get_pressed()
if keys[pygame.K_ESCAPE]:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_d:
debug = not debug
dt = clock.tick() /1000.0
#the missile range (making it disappear after it hits the top)
for missile in missiles:
missile[1] -= missileSpeed * dt
if missile[1] < 0: missiles.remove(missle)
#triangle following mouse position
mx, my = pygame.mouse.get_pos()
if mx > launchX:
launchX += .3
if mx < launchX:
launchX -= .3
#bullets firing when pressing with mouse
mbuttons = pygame.mouse.get_pressed()
if mbuttons [0]:
x = launchX
y = launchY
newMissiles = [x,y]
missiles.append(newMissiles)
#Creating the UFOs
ufoSpawnTimer -= dt
if ufoSpawnTimer <= 0:
if random.choice (("head", "tail")) == "head":
x = 0
hspeed = random.randint (10,50)
else:
x = 800
hspeed = random.randint (-50, -10)
y = random.randint (0,300)
new_ufo = [x,y,hspeed]
ufo_list.append(new_ufo)
ufoSpawnTimer = 5.0
#Moving the Starfield
for i in range(len(myStars)):
myStars[i][0] += starSpeed * dt * starDirection
if myStars[i][0] < 0: myStars[i][0] = 800
if myStars[i][0] > 800: myStars[i][0] = 0
screen.fill ((0,0,0))
#drawing the triangle a.k.a missle launcher :D
pygame.draw.polygon(screen, (255,255,255), [[launchX, launchY], [launchX + 10, launchY + 10], \
[launchX - 10, launchY + 10]], 3)
for missile in missiles:
x = int(missile[0])
y = int(missile[1])
screen.blit(rocket, (x,y))
#drawing the ufo
for v in ufo_list:
v[0] += v[2] * dt
screen.blit(ufo,(v[0],v[1]))
#Missle distance from UFO - NEED HELP ON THIS PORTION
#Hit Detection
missileDist = ((x - v[0]) ** 2 + (y - v[1]) ** 2) ** 0.5
if **????** :
ufoRespawn = True
ufoHits += 10
#drawing th starfield
for star in myStars:
x = int(star[0])
y = int(star[1])
pygame.draw.circle(screen, (255,255,255), (x,y), 2)
pygame.display.flip()
pygame.font.quit()
pygame.display.quit()
Well there are many different ways to detect collision, And it might be worth looking at libraries that would do so, but the simplest method by far is to use pygame.sprite.spritecollide().
But before I can show how to use the function, you need to know what a pygame.sprite.Group() is and what a sprite class is.
Basicly, what a pygame.sprite.Group() is, is a way to keep track of and hold multiple sprites. In your case, it seems making a missile group for your missiles would be the best choice.
So I would create a group to hold your missiles:
missiles_group = pygame.sprite.Group(). You can add missiles to the group by saying missiles_group.add(<sprite instance name>).
As for the sprite class, please see this answer I gave to a question. To be terse, a Sprite class is a modular way to create a sprite. Instead of using just a plain image, a sprite class would hold necessary methods and attributes of a sprite. I will be using a sprite class in my example below, so if more detail is needed, please read the answer I linked to above.
With that out of the way, and without going into too much detail, here is how you fill in each function parameter to the above function.
sprite: This is the sprite that will be tested against a group of sprites
group: This is the group that will be used to test with the sprite.
dokill: This is a boolean value. If set to true, each time the sprite parameter collides with something in the group parameter, and object from the group parameter will be deleted. And visa versa if the dokill argument is set to false.
The is one more parameter that the function takes, but for what you're trying to do, it is not needed.
Incorporating the above information, here is an example. The example creates a sprite and a list of sprites. Each time the sprite collides with a sprite from the group, HIT is printed to the screen:
import pygame #import the pygame module into the namespace <module>
WIDTH = 640 # define a constant width for our window
HEIGHT = 480 # define a constant height for our window
#create a pygame window, and
#initialize it with our WIDTH and HEIGHT constants
display = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock() # create a game clock
class Sprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((20, 20))
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.rect.x = WIDTH / 2
self.rect.y = HEIGHT / 2
self.vx = 0
self.vy = 0
def update(self):
self.vx = 0
self.vy = 0
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
self.vx = -1
elif key[pygame.K_RIGHT]:
self.vx = 1
if key[pygame.K_UP]:
self.vy = -1
elif key[pygame.K_DOWN]:
self.vy = 1
self.rect.x += self.vx
self.rect.y += self.vy
# cretae a player sprite
player = Sprite()
# create a group to hold all of our sprites
sprites = pygame.sprite.Group()
# create a group to hold sprites we want to
# test collions against. These sprites will
# still be added to the sprites list
# but we need a seperate group to test for
# collisions against
collision_sprites = pygame.sprite.Group()
# add a sprite to out collison sprite group
# We also add the sprite to our sprites group
# that holds all sprites
tmp = Sprite()
tmp.update = lambda: None
sprites.add(tmp)
collision_sprites.add(tmp)
# add a player sprites to the player group
player.rect.x = 10
sprites.add(player)
running = True # our variable for controlling our game loop
while running:
for e in pygame.event.get(): # iterate ofver all the events pygame is tracking
clock.tick(60) # make our clock keep pour game at 60 FPS
if e.type == pygame.QUIT: # is the user trying to close the window?
running = False # if so break the loop
pygame.quit() # quit the pygame module
quit() # quit is for IDLE friendliness
sprites.update()
# here is where we test for collision
if pygame.sprite.spritecollide(player, collision_sprites, False):
print("HIT!")
display.fill((180, 180, 180)) # fill the pygame screen with white
sprites.draw(display)
pygame.display.flip() # update the screen
My example if fairly big, so take your time and step through it carefully. I tried to add as many good comments as I could. Good luck!