Parallel display updates for Animations in pygame - python

I'm using pygame to create a game and it works so far.
However, I have included an animation of an explosion, by cycling through images I created from a spritesheet.
While this animation is running, everything else in the game stops, and only continues running after the animation is done:
class Explosion(Thread):
def __init__(self, coordinates):
Thread.__init__(self)
self.x, self.y = coordinates
self.explosionimgs = []
for i in range(0, 47):
self.explosionimgs.append(pygame.image.load("resources/explosion2/%d.png" % (i)))
self.explosionimg = self.explosionimgs[0]
def run(self):
for j in range (0,47):
screen.blit(self.explosionimgs[j], (self.x-80, self.y-80))
pygame.display.update()
Is there a way on how to make everything else in the game continue running while this is animation is taking place?

Is there a way on how to make everything else in the game continue running while this is animation is taking place?
Yes. Don't create another loop besides your game's main loop.
An easy way is to use pygame's Sprite class to represent a game object, because it nicely combines a Rect and a Surface and works easy with Groups.
Here's a silly simple runnable example. Note how everything keeps moving, while the Explosion class keeps chancing its image.
Just set your images from the spritesheet instead of using pygame.draw.circle:
import pygame
pygame.init()
screen = pygame.display.set_mode((600, 480))
clock = pygame.time.Clock()
run = True
class Explosion(pygame.sprite.Sprite):
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.surface.Surface((60, 60))
self.rect = self.image.get_rect(center=pos)
self.state = 0
def update(self):
self.state += 1
self.image.fill((0, 0, 0))
pygame.draw.circle(self.image, (200, 5 * self.state, 0), self.image.get_rect().center, self.state)
if self.state > 30:
self.kill()
class Ball(pygame.sprite.Sprite):
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.surface.Surface((40, 40))
self.image.fill((60, 60, 200))
self.rect = self.image.get_rect(center=pos)
self.dir = 3
def update(self):
if not screen.get_rect().contains(self.rect):
self.dir *= -1
self.image.fill((self.rect.x % 255, 60, 200))
self.rect.move_ip(self.dir, 0)
sprites = pygame.sprite.Group(Ball((200, 200)), Ball((100, 300)))
while run:
for e in pygame.event.get():
if e.type == pygame.QUIT:
run = False
if e.type == pygame.MOUSEBUTTONDOWN:
sprites.add(Explosion(e.pos))
screen.fill((0, 0, 0))
sprites.draw(screen)
sprites.update()
pygame.display.flip()
clock.tick(60)

Related

i want to kill the my last sprite while moving and keeping my current sprtie

import pygame, sys
start = True
class Player(pygame.sprite.Sprite):
def __init__(self, pos_x, pos_y):
super().__init__()
self.attack_animation = False
self.sprites = []
self.sprites.append(pygame.image.load('crossHair.png'))
self.sprites.append(pygame.image.load('crossHair_2.png'))
self.sprites.append(pygame.image.load('crossHair_3.png'))
self.sprites.append(pygame.image.load('crossHair_4.png'))
self.current_sprite = 0
self.image = self.sprites[self.current_sprite]
self.image.set_colorkey('white')
for items in self.sprites:
items.set_colorkey('white')
self.rect = self.image.get_rect()
self.rect.topleft = [pos_x,pos_y]
def attack(self):
self.attack_animation = True
self.image.set_colorkey('white')
def update(self,speed):
self.image.set_colorkey('white')
self.current_sprite += speed
if int(self.current_sprite) >= len(self.sprites):
self.attack_animation = False
self.current_sprite = 0
self.image = self.sprites[int(self.current_sprite)]
# General setup
pygame.init()
clock = pygame.time.Clock()
# Game Screen
screen_width = 400
screen_height = 400
mouse = pygame.mouse.get_pos()
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption("Sprite Animation")
# Creating the sprites and groups
moving_sprites = pygame.sprite.Group()
while True:
globals()['mouse'] = pygame.mo[![now this is the problem][1]][1]use.get_pos()
player = Player(mouse[0],mouse[1])
moving_sprites.add(player)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
player.attack()
# Drawing
screen.fill((0,0,0))
screen.set_colorkey('white')
moving_sprites.draw(screen)
moving_sprites.update(0.04)
pygame.display.flip()
clock.tick(120)
this is creatiing my player / crossHair every 120th of a second and it leaves behind it's last sprite
now the problem with that is it leaves behind my last sprite and if i put
moving_sprites = pygame.sprite.Group()
in while loop then it won't animate anybody can solve this pls answer my question...
This is not the way to animate sprites. Creating a new sprite every frame is bad practice and a waste of performance since all objects have to be recreated. Even more you load the images in the constructor of the Player class. If you do this every frame, then every frame the images have to be loaded from the volume and decoded.
Create the sprite once before the application loop and change the attributes of the sprite object in the loop. This will give you the best performance:
class Player(pygame.sprite.Sprite):
# [...]
def set_pos(self, pos_x, pos_y):
self.rect.topleft = (pos_x, pos_y)
moving_sprites = pygame.sprite.Group()
player = Player(0, 0)
moving_sprites.add(player)
while True:
player.set_pos(*pygame.mouse.get_pos())
# [...]

How to make gravity function in pygame?

Unable to move player in pygame
So, I was trying to create a simple physics system in pygame. My main goal was to add a Y gravity function that would move the player when activated. I have tried several times to make it work, but I guess I am missing something.
Here is the code:
class Physic:
def __init__(self, color, master, type, sizeX, sizeY, pos=[0,0]):
self.master = master
self.color = color
self.type = type
self.sizeX = sizeX
self.sizeY = sizeY
self.x = pos[0]
self.y = pos[1]
self.moveX = 0
self.moveY = 0
self.create_Object()
def create_Object(self):
if self.type == 'rect':
self.rect()
def rect(self):
return pygame.draw.rect(self.master, self.color, (self.x, self.y, self.sizeX, self.sizeY))
def drag(self):
for event in pygame.event.get():
if event.type == pygame.mouse.get_pressed()[0]:
self.x = pygame.mouse.get_pos()
def add_gravity(self):
self.moveY += 3.2
self.update_pos()
def update_pos(self):
self.y += self.moveY
self.x += self.moveX
In the main script I put this:
def game():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(WHITE)
player = object.Physic(BLUE, screen, 'rect', 50, 50, [POS[0], POS[1]])
player.add_gravity()
# platform = object.rect(screen, RED, [30, 30], 100, 30)
# physics.add_collider(player, platform, POS[0])
pygame.display.update()
game()
Do you know what I am missing?
Your big problem is that you are recreating the player every pass inside the main loop and so it looks like it is frozen in place.
You also need to have a limit on the frame rate so that you control the speed of the game and therefor can properly set the acceleration per frame.
There are a some other minor things that needed to be fixed to run this. I tried to change the minimum possible to run it, since the point was to fix the error rather than rewrite it on you. Obviously I had to add some wrapper code around it
Try this slightly adjusted version:
#!/usr/bin/env python
import traceback
import pygame
import sys
FRAME_RATE = 60
GRAVITY = 32
SCREEN_SIZE = (600, 800)
WHITE = pygame.Color("white")
BLUE = pygame.Color("blue")
POS = (100, 600)
class Physic:
def __init__(self, color, master, type, sizeX, sizeY, pos=(0,0)):
self.master = master
self.color = color
self.type = type
self.sizeX = sizeX
self.sizeY = sizeY
self.x = pos[0]
self.y = pos[1]
self.moveX = 0
self.moveY = 0
self.create_Object()
def create_Object(self):
if self.type == 'rect':
self.draw()
def draw(self):
return pygame.draw.rect(self.master, self.color, (self.x, self.y, self.sizeX, self.sizeY))
def drag(self):
for event in pygame.event.get():
#if event.type == pygame.mouse.get_pressed()[0]: <--- remove this
if event.type == pygame.MOUSEBUTTONDOWN:
self.x = pygame.mouse.get_pos()
def add_gravity(self):
self.moveY += GRAVITY / FRAME_RATE
self.update_pos()
def update_pos(self):
self.y += self.moveY
self.x += self.moveX
def game():
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
clock = pygame.time.Clock()
player = Physic(BLUE, screen, 'rect', 50, 50, POS)
# I added this to illustrate the gravity better ... going up and down
player.moveY = -25
player.moveX = 2
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
screen.fill(WHITE)
player.add_gravity()
player.draw()
# platform = object.rect(screen, RED, [30, 30], 100, 30)
# physics.add_collider(player, platform, POS[0])
pygame.display.update()
clock.tick(FRAME_RATE)
def main():
try:
game()
except Exception as ex:
print(traceback.format_exc())
raise
finally:
# game exit cleanup stuff
pygame.quit()
if __name__ == '__main__':
main()
An issue that I want to point out though it is not affecting this code here. You should not use an immutable object (like a list) as an default when defining an optional/named argument in a method. I.e. in the Physic __init__() I changed pos=[0,0] to pos=(0,0). Not a big deal here, but can cause really odd bugs if you had assigned it to a var, then tried to change it. It will have effects on other instances of the object because they actually share the default initialization object and if it gets modified by one of them it happens in all of them!

Object only being drawn when the game window is dragged around

I'm trying to recreate Agar.io in Python using pygame. So far I've been able to somewhat simulate the player movement, but then I tried to generate some "food cells" at a given interval using the pygame.time.set_timer() method, but even though the rest of the game elements are being drawn, such as the screen and the player, these food cells are only drawn (and added to the "food_list" list) when I drag the game window around. I think this might be an issue of me not really knowing how to deal with the event queue.
main.py, which is where the game loop is located:
import sys, pygame
from player import Player
from food import Food
import random
pygame.init()
SCREEN_SIZE = [1024, 768]
WHITE = (255, 255 , 255)
generate_food = pygame.USEREVENT + 1
screen = pygame.display.set_mode(SCREEN_SIZE)
screen.fill(WHITE)
player = Player()
food_list = []
## TODO: Make a separate GameScreen class.
## TODO: Make a list of constants
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == generate_food:
food_x = random.randrange(0, 760)
food_y = random.randrange(0, 1000)
food_list.append(Food(food_x, food_y))
pygame.time.set_timer(generate_food, 200)
screen.fill(WHITE)
for f in food_list:
f.draw(screen)
player.update()
player.draw(screen)
pygame.display.flip()
food.py:
import pygame
import random
from cell import Cell
DARK_GREEN = (0, 102, 0)
class Food(Cell):
def __init__(self, x, y):
super().__init__()
self.size = 2
self.image = pygame.Surface([2, 2])
self.image.fill(DARK_GREEN)
self.rect = self.image.get_rect()
self.x = x
self.y = y
def draw(self, screen):
pygame.draw.circle(screen, DARK_GREEN, (self.x, self.y), self.size, 0)
def getX(self):
return self.x
def getY(self):
return self.y
Actually the timer is restarted continuously in the application loop. However pygame.time.set_timer() crates a timer that repeatedly create a USEREVENT in the event queue. Hence it is sufficient to start the timer once before the application loop:
pygame.time.set_timer(generate_food, 200) # <--- ADD
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == generate_food:
food_x = random.randrange(0, 760)
food_y = random.randrange(0, 1000)
food_list.append(Food(food_x, food_y))
# pygame.time.set_timer(generate_food, 200) <--- DELTE

A surface shown unexpectedly with pygame

I'm in the process of making a snake game, it mostly went well so far, but it displayed a block of the snake at the top left that I can't get rid of.
I checked that I didn't draw the surface there(0,0). I'm stuck. Please help me out, thanks!!
BTW it's my first time asking a question so any suggestion on that is also appreciated.
Edit: I found that using a regular class instead of sprite solved the problem, but I need the collide and other functions in sprite.
import pygame
class snake(pygame.sprite.Sprite):
speed=5
init_length=10
direction=0
x=[]
y=[]
updateCountMax = 2
updateCount = 0
length=10
# image=pygame.Surface((11,11)).convert().fill((0,128,255))
def __init__(self,init_x,init_y,image,screen):
pygame.sprite.Sprite.__init__(self)
for i in range(0,self.init_length):
self.x.append(init_x)
self.y.append(init_y)
# for i in range(0,self.length):
# print(f"{self.x[i]},{self.y[i]}")
for x in self.x:
print(x)
for y in self.y:
print(y)
self.image=image
self.screen=screen
self.rect=self.image.get_rect()
# self.rect.center=(self.x,self.y)
def move_R(self):
# self.x+=self.speed
self.direction=0
def move_L(self):
# self.x-=self.speed
self.direction=1
def move_U(self):
# self.y-=self.speed
self.direction=2
def move_D(self):
# self.y+=self.speed
self.direction=3
def update(self):
# self.updateCount = self.updateCount + 1
# if self.updateCount < self.updateCountMax:
for i in range(self.length-1,0,-1):
# print("self.x[" + str(i) + "] = self.x[" + str(i-1) + "]")
self.x[i] = self.x[i-1]
self.y[i] = self.y[i-1]
if(self.direction==0):
self.x[0]+=self.speed
elif(self.direction==1):
self.x[0]-=self.speed
elif(self.direction==2):
self.y[0]-=self.speed
elif(self.direction==3):
self.y[0]+=self.speed
# self.rect.center=(self.x,self.y)
# self.updateCount = 0
# for i in range(0,self.length):
# print(f"{self.x[i]},{self.y[i]}")
self.draw()
def draw(self):
for i in range(0,self.length):
self.screen.blit(self.image,(self.x[i],self.y[i]))
# print(f"rendered at {self.x[i]},{self.y[i]}")
# self.rect.center=(self.x[i],self.y[i])
class app:
width=1200
height=900
title="Snake"
done=False
def __init__(self):
pygame.init()
self.image=pygame.Surface((11,11))
self.image.fill((0,128,255))
pygame.display.set_caption(self.title)
self.screen = pygame.display.set_mode((self.width, self.height))
self.screen.fill((0,0,0))
self.clock=pygame.time.Clock()
self.snakes=pygame.sprite.Group()
self.player1=snake(500,10,self.image,self.screen)
self.snakes.add(self.player1)
self.background = pygame.Surface(self.screen.get_size())
self.background = self.background.convert()
self.background.fill((255,255,255))
def loop(self):
while(not self.done):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
pygame.event.pump()
keys = pygame.key.get_pressed()
if (keys[pygame.K_RIGHT]):
self.player1.move_R()
if (keys[pygame.K_LEFT]):
self.player1.move_L()
if (keys[pygame.K_UP]):
self.player1.move_U()
if (keys[pygame.K_DOWN]):
self.player1.move_D()
if (keys[pygame.K_ESCAPE]):
self.done = True
self.screen.blit(self.background,(0,0))
self.screen.fill((0,0,0))
self.player1.update()
self.snakes.draw(self.screen)
pygame.display.update()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__" :
theApp = app()
theApp.loop()
You add player1 to the snakes sprite group and draw that with self.snakes.draw(self.screen). However, you also draw the player in self.player1.update(), in the last line.
Remove self.snakes.draw(self.screen) to get rid of the phantom snake.
BTW: you create and set a self.background but you immediately overwrite it with self.screen.fill((0,0,0)), so you don't need a background at all.
What you see in the top left corner is the self.image of the player1 sprite. The draw method of sprite groups blits the images at the rect.topleft coordinates of the sprites and since you never move the player1.rect, the image will be blitted at the default (0, 0) coordinates. So just remove the line self.snakes.draw(self.screen) to fix this.
I also suggest that you use pygame.Rects instead of the self.x and self.y lists. You can create rect instances with the init_x and init_y coords as the topleft attribute and put them into a self.rects list. That allows you to simplify the update and draw methods, and the rects can also be used for the collision detection. I've already refactored your code (it kind of became a mini code review):
import pygame
class Snake(pygame.sprite.Sprite): # Use upper camelcase names for classes (see PEP 8).
def __init__(self, init_x, init_y, image,screen):
pygame.sprite.Sprite.__init__(self)
# These are instance attributes now (use class attributes if
# the values should be shared between the instances).
self.speed = 5
self.init_length = 10
self.direction = 0
self.updateCountMax = 2
self.updateCount = 0
self.length = 10
# The body parts are rects now.
self.rects = []
for i in range(self.init_length):
# Append pygame.Rect instances.
self.rects.append(pygame.Rect(init_x, init_y, 11, 11))
self.image = image
self.screen = screen
self.rect = self.rects[0] # I use the first rect as the self.rect.
def update(self):
for i in range(self.length-1, 0, -1):
# Update the topleft (x, y) positions of the rects.
self.rects[i].topleft = self.rects[i-1].topleft
if self.direction == 0:
self.rects[0].x += self.speed
elif self.direction == 1:
self.rects[0].x -= self.speed
elif self.direction == 2:
self.rects[0].y -= self.speed
elif self.direction == 3:
self.rects[0].y += self.speed
def draw(self):
# Iterate over the rects to blit them (I draw the outlines as well).
for rect in self.rects:
self.screen.blit(self.image, rect)
pygame.draw.rect(self.screen, (0, 255, 0), rect, 1)
class App:
width = 1200
height = 900
title = "Snake"
done = False
def __init__(self):
pygame.init()
self.image = pygame.Surface((11, 11))
self.image.fill((0, 128, 255))
pygame.display.set_caption(self.title)
self.screen = pygame.display.set_mode((self.width, self.height))
self.clock = pygame.time.Clock()
self.snakes = pygame.sprite.Group()
self.player1 = Snake(500, 10, self.image, self.screen)
self.snakes.add(self.player1)
def loop(self):
while not self.done:
# Handle the events.
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
keys = pygame.key.get_pressed()
# In Python we simply set the values of the
# attributes directly instead of using getter
# and setter methods.
if keys[pygame.K_RIGHT]:
self.player1.direction = 0
if keys[pygame.K_LEFT]:
self.player1.direction = 1
if keys[pygame.K_UP]:
self.player1.direction = 2
if keys[pygame.K_DOWN]:
self.player1.direction = 3
if keys[pygame.K_ESCAPE]:
self.done = True
# Update the game.
self.player1.update()
# Draw everything.
self.screen.fill((0, 0, 0))
self.player1.draw()
pygame.draw.rect(self.screen, (255, 0, 0), self.player1.rect, 1)
pygame.display.update()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__" :
the_app = App()
the_app.loop()

pygame sprite and draw method not working as expected

Please any help would be greatly appreciated.
I am writing a game with Pygame and after creating all the classes and methods I need. When I run the game I see five alien characters of my game showing up from the left side of the screen joined together before I actually see what i wanted my code to display (aliens moving at random positions down the screen).
here is my code:
class Alien():
def __init__(self, image):
self.x = random.randrange(20,width - 20)
self.y = random.randrange(-200, -20)
self.speed = random.randint(1, 5)
self.image = pygame.image.load(os.path.join("../images", image)).convert_alpha()
self.rect = self.image.get_rect()
def move_down(self):
self.rect.y += self.speed
def draw(self, screen):
screen.blit(self.image, self.rect)
its implementation
for i in range (20):
aliens = Alien("alien.png")
enemies.append(aliens)
done = False
while done == False:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(white)
for i in range(len(enemies)):
enemies[i].move_down()
enemies[i].draw(screen)
enemies[i].check_landed()
pygame.display.flip()
clock.tick(30)
note: I have removed some code for clarity.
result
You store the position of the aliens in the fields self.x and self.y, but to draw them, you actually don't use self.x and self.y, but self.rect.
You create self.rect by calling self.image.get_rect(), and when you call get_rect() on a Surface, the position of the Rect is always (0, 0).
So the x coordinate is always 0, hence they are all on the left side of the screen.
I suggest rewriting your code to:
class Alien():
# pass the Surface to the instance instead of the filename
# this way, you only load the image once, not once for each alien
def __init__(self, image):
self.speed = random.randint(1, 5)
self.image = image
# since we're going to use a Rect of drawing, let's use the
# same Rect to store the correct position of the alien
self.rect = self.image.get_rect(top=random.randrange(-200, -20), left=random.randrange(20,width - 20))
def move_down(self):
# the Rect class has a lot of handy functions like move_ip
self.rect.move_ip(0, self.speed)
def draw(self, screen):
screen.blit(self.image, self.rect)
# load the image once. In more complex games, you usually
# want to abstract the loading/storing of images
alienimage = pygame.image.load(os.path.join("../images", 'alien.png')).convert_alpha()
for _ in range (20):
aliens = Alien(alienimage)
enemies.append(aliens)
done = False
while done == False:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(white)
# just use a "regular" for loop to loop through all enemies
for enemy in enemies:
enemy.move_down()
enemy.draw(screen)
enemy.check_landed()
pygame.display.flip()
clock.tick(30)
You could go further and use the Sprite- and Group-class to further generalize your code, but that's another topic.

Categories