Related
I would like individual cars to drive across the screen from left to right with a certain time interval.It should be an endless loop. It is ended by program end
With my code, they all start at the same time. In addition, rect.y changes from the default value 300 to 0.
import pygame
import random
WIDTH = 800
HEIGHT = 600
FPS = 30
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Car(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.images = []
for i in range(5):
img = pygame.image.load(f"Bilder/Gegenstaende/quer_auto_{i}.png")
self.images.append(img)
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect()
self.rect.x = 100
self.rect.y = 300
self.last = pygame.time.get_ticks()
self.delay = 5000
def update(self):
self.rect.x += 5
current = pygame.time.get_ticks()
if current - self.last > self.delay:
self.last = current
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect()
self.rect.x = 100
background = pygame.image.load("Bilder/starfield.png")
background_rect = background.get_rect()
cars = pygame.sprite.Group()
for i in range(3):
m = Car()
cars.add(m)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
cars.update()
screen.fill((250,250,250))
screen.blit(background,background_rect)
cars.draw(screen)
pygame.display.flip()
pygame.quit()
(Assuming you want these cars to be robots) First create variable and lets name them car_move_right and car_move_left . If you want the car to move towards the right at the beginning of the game, set the car_move_right to True and the other variable to False, and vice versa. Then in the main loop, add an if statement like this:
(Taking carX to be X-coordinate of car)
if car_move_right:
carX += ai_move_speed
if carX == WINDOWWIDTH or carX > WINDOWWIDTH:
car_move_right = False
car_move_left = True
if car_move_left:
carX -= ai_move_speed
if carX == 0 or carX < 0:
car_move_left = False
car_move_right = True
Please note: I had written this code for robot movement which also included going up and down. Not the cleanest and best, but I think it would work for you.
rect.y changes to 0 because you reset the rect, and default rect's x and y values are 0.
if current - self.last > self.delay:
self.last = current
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect() ##<--- here
self.rect.x = 100
Also you should consider storing your car's position in a python variable, since self.rect.x can only store integers.
For the timer thing, you should move your update function outside of your car class since it does not have any information about other cars and it needs it.
Here is an example implementation.
import pygame
WIDTH = 800
HEIGHT = 600
FPS = 30
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Car(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50)).convert()
self.rect = self.image.get_rect()
self.rect.x = 100
self.rect.y = 300
def finished(self):
return self.rect.x >= WIDTH - self.rect.width
class Driver:
def __init__(self, cars):
self.cars = cars
self.currentCar = 0
self.delay = 1000
self.current = pygame.time.get_ticks()
self.last = pygame.time.get_ticks()
self.timePassed = False
def updateCar(self, car):
if self.timePassed:
car.rect.x += 1 #5
def runTimer(self):
self.current = pygame.time.get_ticks()
self.timePassed = False
if self.current - self.last > self.delay:
self.timePassed = True
def Run(self):
self.runTimer()
car = self.cars.sprites()[self.currentCar]
if not car.finished():
self.updateCar(car)
else:
self.current = pygame.time.get_ticks()
self.last = pygame.time.get_ticks()
self.currentCar += 1
def allFinished(self):
return self.currentCar >= len(self.cars)
cars = pygame.sprite.Group()
for i in range(3):
m = Car()
cars.add(m)
driver = Driver(cars)
while True:
if not driver.allFinished():
driver.Run()
#btw, no need to fill if you have background image the size of window
screen.fill((250,250,250))
#screen.blit(background,background_rect)
cars.draw(screen)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
Create 10 car classes. Give them a different name, like car1, car2, etc.
Let's focus on car1 for now. Let car1X represent the x-coordinate of car1.
Now for the speed of movement. If you want each car to have the same speed, just create a variable named ai_move_speed. Now, if you want the speed to differ, then instead of ai_move_speed, make 10 variables and name these variables as ai1_move_speed, ai2_move_speed, etc. For now, set these variables to an integer value of 2.
For the movement of left to right, create a variable called car1_move_right and set it to True.
As each car will go from left to right, then die, We can simply add this code to our main loop :
if car1_move_right:
car1X += ai_move_speed
Now once the game is run, car1 will go from its x-coordinate towards the left side. Repeat this process for the other cars.
But if you want the cars to go back to their original position if they go out of the boundary or die, then just add this line of code at the end of the above code:
if carX == 800:
carX = 100
Now so far we fixed movement. Now if you want the cars to start 1 after the other, delete the entire code from before. Now you can customize when a car starts. For car1 let's simply create a variable and call this variable c1_time. Set it to 5000 for now. Now I want to introduce you to a pygame function called pygame.time.get_ticks(). It gets the number of milliseconds since pygame.init() has been called in milliseconds. Remember the c1_time? We set it to an integer value of 5000 because 1000 milliseconds is 1 second, so car1 will start moving after 5 seconds after the interpreter reads pygame.init().
Now create this variable : time_init and store this code inside it : pygame.time.get_ticks(). After that create an if statement as so :
if car1_move_right:
if time_init >= c1_time:
car1X += ai_move_speed
if car1X == 800:
car1X = 100
Now what this above code does is that 5 seconds after the game starts, it will start moving car1. If car1 goes out of boundary it gets it back again. Repeat these steps for the other cars. This is the simplest and bug-free way I could do it.
PS - Feel free to change any variable name. Just make sure to change it everywhere in the code.
PPS - Please read the entire thing. Also upvote if you found this helpful cuz I spent a lot of time on this.
I want to display an each object of the class "Obstacle" on the screen. The objects are stored in the list obstacle_list. I dont get an error message, but the window just freezes and no objects are displayed on the screen. I think the problem must be somewhere in def spawn_obstacle(self): but I have no idea where I made a mistake.
import pygame
import random
import time
import math
# Initialize pygame
pygame.init()
# Create window (width, height)
screen = pygame.display.set_mode(((800, 600)))
ScreenHeight = screen.get_height()
ScreenWidth = screen.get_width()
# Background picture
background = pygame.image.load("background.jpg")
# Title and Icon
pygame.display.set_caption("F22-Raptor Simulator")
icon = pygame.image.load("jet1.png")
pygame.display.set_icon(icon)
# Object List (obstacles)
obstacle_list = []
class Obstacle(object):
def __init__(self): # removed unused parameters
self.obstacleImg = 'rock.png' # pygame.image.load("rock.png")
self.obstacleX = random.randint(600, 700)
self.obstacleY = random.randint(0, ScreenHeight - 64)
self.obstacleX_change = random.uniform(-0.3, -0.2)
def __repr__(self):
return f'Obstacle(image={self.obstacleImg!r}, X={self.obstacleX}, Y={self.obstacleY}, change={self.obstacleX_change})'
def spawn_obstacle(self):
image = pygame.image.load(self.obstacleImg)
screen.blit(image, (self.obstacleX, self.obstacleY))
# Keep window running (Infinite-Loop)
running = True
# Timer
timer1_start = time.time()
timer1_current = 0
# Counter while-loop to display objects
count_object_display = 0
# While-Loop (Everything that takes place during the game is inside here)
while running:
timer1_current = time.time()
if timer1_current - timer1_start >= 1:
timer1_start = time.time() # Timer of start set to current time
obstacle = Obstacle() # Create instance of class obstacle
obstacle_list.append(obstacle)
print(obstacle)
# Insert Background
screen.blit(background, (0, 0))
# End game / close window
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
while count_object_display <= len(obstacle_list)-1:
obstacle_list[count_object_display].spawn_obstacle()
count_object_display += 1
if count_object_display > len(obstacle_list)-1:
count_object_display = 0
# Update after each iteration of the while-loop
pygame.display.update()
You have an infinite loop here:
while count_object_display <= len(obstacle_list)-1:
obstacle_list[count_object_display].spawn_obstacle()
count_object_display += 1
if count_object_display > len(obstacle_list)-1:
count_object_display = 0
You enter the while loop and set count_object_display to 1.
In the next line, 1 > 0 so you set count_object_display back to 0.
And since 0 <= 0 the while loop runs forever.
I don't know what you're trying to do there, but maybe just use a simple for loop, like:
for o in obstacle_list:
o.spawn_obstacle()
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'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'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()