How to Create Animated Sprites Using Sprite Sheets in Pygame

how to animate the sprite?

Create a sprite class with a list of images. Add an attribute that counts the frames. Get image from image list in the update method based on number of frames:

class MySprite(pygame.sprite.Sprite):

def __init__(self, x, y, image_list):
super().__init__()
self.frame = 0
self.image_list = image_list
self.image = self.image_list[0]
self.rect = self.image.get_rect()
self.rect.center = (x, y)

def update(self, event_list):

animation_interval = 20
self.frame += 1
if self.frame // animation_interval >= len(self.image_list):
self.frame = 0
self.image = self.image_list[self.frame // animation_interval]

for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN:
mouse = event.pos
print(mouse[:])

However, instead of constantly creating and killing the sprite every frame, you need to create the sprite once before the application loop. Change the position of the sprite and update the sprite in the application loop:

file_list = ['crossHair.png', 'crossHair_2.png', 'crossHair_3.png']
image_list = []
for name in file_list:
image = pygame.image.load(name)
image.set_colorkey(WHITE)
image_list.append(image)

# create sprite
sprite1 = MySprite(0, 0, image_list)
placeSP_group = pygame.sprite.OrderedUpdates()
placeSP_group.add([sprite1])

clock = pygame.time.Clock()
running = True
while running:

# handle events
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
running = False

# update sprite
x, y = pygame.mouse.get_pos()
placeSP_group.sprites()[0].rect.center = (x, y)
placeSP_group.update(event_list)

# draw scene
window_game.fill('white')
window_game.blit(backGround,(0,0)).size
placeSP_group.draw(window_game)
pygame.display.flip()

clock.tick(FPS)

pygame.quit()
exit()

See also:

  • how to create an illusion of animations in pygame
  • Animated sprite from few images
  • How do I create animated sprites using Sprite Sheets in Pygame?

pygame spritesheet sprite animation

Load four images as

self.image_up = pygame.image.load(...)
self.image_down = pygame.image.load(...)
self.image_left = pygame.image.load(...)
self.image_right = pygame.image.load(...)

and later when you need it replace

self.image = self.image_up 

or

self.image = self.image_down

etc.


If you have all positions in one file then you can use pygame.Surface.subsurface to cut off part of image and create new one

spritesheet = pygame.image.load(...)

self.image_up = spritesheet.subsurface( Rect(0,0,10,10) )
self.image_down = spritesheet.subsurface( Rect(...) )
self.image_left = spritesheet.subsurface( Rect(...) )
self.image_right = spritesheet.subsurface( Rect(...) )

My simple example (with all positions in one file) on GitHub: pygame-spritesheet

Use left/right arrows to move object and it will use different image.

How do I use Sprite Sheets in Pygame?

It really isn't very hard to do… but the best sample code I found in a quick search is also a usable library that does the work for you: spritesheet, right from the pygame wiki.

So, you can start off by just using that. I'd give you an example tailored to your use case, but you haven't given us any idea what your code looks like or what you want to do, so I can't possibly give you anything better than is already on that page, so:

import spritesheet
...
ss = spritesheet.spritesheet('somespritesheet.png')
# Sprite is 16x16 pixels at location 0,0 in the file...
image = ss.image_at((0, 0, 16, 16))
images = []
# Load two images into an array, their transparent bit is (255, 255, 255)
images = ss.images_at((0, 0, 16, 16),(17, 0, 16,16), colorkey=(255, 255, 255))

Meanwhile, you can read the (very simple) code in that spritesheet class to understand how it works.

Animated sprite from few images

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.

Infinitely animating sprite in pygame

Make a list of the images for animation:

image_list = [pos1, pos2, pos3, pos4, pos5]

Add a variable that will store an index of the image list:

current_image = 0

Increment the index in the application loop and reset the index if it is equal to the length of the list:

run = True
while run:
# [...]

current_image += 1
if current_image == len(image_list):
current_image = 0

# [...]

Get the current image by subscription:

screen.blit(image_list[current_image], x, y)

Pygame: What is the best way to display these ships and their movement from sprite sheet?

What is the best way can't be answered, because in the end that's subjective; and depends on your own goals. Best performance? Easiest code? Most flexible code?

You could start with something like this I hacked together during my last boring meeting:

import pygame
import random
from pygame import Vector2

SPRITE_SHEET = None

GREEN_SHIP = pygame.Rect(0, 292, 32, 32)
RED_SHIP = pygame.Rect(0, 324, 32, 32)
BLUE_SHIP = pygame.Rect(0, 356, 32, 32)
YELLOW_SHIP = pygame.Rect(0, 388, 32, 32)

class EnemyController:

def __init__(self):
self.direction = Vector2(1, 0)

def update(self, sprite, events, dt):
if not pygame.display.get_surface().get_rect().contains(sprite.rect):
self.direction *= -1
sprite.direction = self.direction

class PlayerController:

movement = {
pygame.K_UP: Vector2( 0, -1),
pygame.K_DOWN: Vector2( 0, 1),
pygame.K_LEFT: Vector2(-1, 0),
pygame.K_RIGHT: Vector2( 1, 0)
}

def update(self, sprite, events, dt):
pressed = pygame.key.get_pressed()
v = Vector2(0, 0)
for key in PlayerController.movement:
if pressed[key]:
v += PlayerController.movement[key]

sprite.direction = v

for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
sprite.groups()[0].add(Explosion(sprite.pos))

class Animation:
def __init__(self, frames, speed, sprite):
self.sprite = sprite
self.speed = speed
self.ticks = 0
self.frames = frames
self.running = 0
self.start()

def cycle_func(self, iterable):
saved = []
for element in iterable:
yield element
saved.append(element)
if hasattr(self.sprite, 'on_animation_end'):
self.sprite.on_animation_end()
while saved:
for element in saved:
yield element
if hasattr(self.sprite, 'on_animation_end'):
self.sprite.on_animation_end()
def stop(self):
self.running = 0
if self.idle_image:
self.sprite.image = self.idle_image

def start(self):
if not self.running:
self.running = 1
self.cycle = self.cycle_func(self.frames)
self.sprite.image = next(self.cycle)

def update(self, dt):
self.ticks += dt
if self.ticks >= self.speed:
self.ticks = self.ticks % self.speed
if self.running:
self.sprite.image = next(self.cycle)

class AnimatedSprite(pygame.sprite.Sprite):

def __init__(self, pos, frames, speed):
super().__init__()
self.animation = Animation(frames, speed, self)
self.rect = self.image.get_rect(center=pos)
self.pos = Vector2(pos)
self.animation.start()

def update(self, events, dt):
self.animation.update(dt)

class Explosion(AnimatedSprite):

frames = None

def __init__(self, pos):
if not Explosion.frames:
Explosion.frames = parse_sprite_sheet(SPRITE_SHEET, pygame.Rect(0, 890, 64, 64), 6, 4)

super().__init__(pos, Explosion.frames, 50)

def on_animation_end(self):
self.kill()

class DirectionalImageSprite(pygame.sprite.Sprite):

directions = [(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(0,0)]

def __init__(self, pos, directional_images_rect):
super().__init__()
images = parse_sprite_sheet(SPRITE_SHEET, directional_images_rect, 9, 1)
self.images = { x: img for (x, img) in zip(DirectionalImageSprite.directions, images) }
self.direction = Vector2(0, 0)
self.image = self.images[(self.direction.x, self.direction.y)]
self.rect = self.image.get_rect(center=pos)
self.pos = pos

class SpaceShip(DirectionalImageSprite):

def __init__(self, pos, controller, directional_images_rect):
super().__init__(pos, directional_images_rect)
self.controller = controller
self.speed = 3

def update(self, events, dt):
super().update(events, dt)

if self.controller:
self.controller.update(self, events, dt)

self.image = self.images[(self.direction.x, self.direction.y)]
if self.direction.length():
self.pos = self.pos + self.direction.normalize() * self.speed

self.rect.center = int(self.pos[0]), int(self.pos[1])

def parse_sprite_sheet(sheet, start_rect, frames_in_row, lines):
frames = []
rect = start_rect.copy()
for _ in range(lines):
for _ in range(frames_in_row):
frame = sheet.subsurface(rect)
frames.append(frame)
rect.move_ip(rect.width, 0)
rect.move_ip(0, rect.height)
rect.x = start_rect.x
return frames

def main():
screen = pygame.display.set_mode((800, 600))
global SPRITE_SHEET
SPRITE_SHEET = pygame.image.load("ipLRR.png").convert_alpha()
clock = pygame.time.Clock()
dt = 0
all_sprites = pygame.sprite.Group(
SpaceShip((400, 300), PlayerController(), YELLOW_SHIP),
SpaceShip((400, 100), EnemyController(), GREEN_SHIP)
)

while True:
events = pygame.event.get()

for e in events:
if e.type == pygame.QUIT:
return

all_sprites.update(events, dt)

screen.fill((0, 0, 0))
all_sprites.draw(screen)
pygame.display.flip()
dt = clock.tick(120)

main()

Sample Image

I pass the function parse_sprite_sheet the image, along with the position and size of the first frame of the animation/bunch of related sub-images (using a Rect). Also, I pass the number of images in the row and the number of rows (since the explosion animation uses 4 rows with 4 images each). Then I use subsurface the get the part of the sprite sheet I'm interested in in a nested loop.


The Animation class is updated by the sprite it's attached to and changes the image of the sprite when enough time has passed.

Also a method named on_animation_end is called on the sprite once the animation ends. I use this to kill the Explosion sprites once the explosion is done.


For the directional images of SpaceShip, I define the directions in a list once (in the correct order) and then attach each direction an image by creation a dictionary.

This way I can easily look up the correct image, since the direction the SpaceShip is heading is stored in the direction attribute.


That's it basically.
Some animations in your sprite sheet are a little more tricky, as the size of the tile changes, but it's doable.

You'll get the idea.



Related Topics



Leave a reply



Submit