Sprites¶
Sprites in many 2D games are considered to be graphical objects that you can draw on display, move and maybe check collisions with other sprites or other background graphics or objects.
Pygame as not being game engine doesn’t have true concept of sprites in that traditional sense. Pygame though provides a few helper classes to deal with sprites.
In this chapter you will first learn how to handle sprites without any classes. Finally there are two ways to handle sprites, first with custom class, second with using Pygame helper classes.
This chapter requires knowledge of Python classes and a basic knowledge about object oriented programming. This book doesn’t cover them.
Sprites without classes¶
You actually did this method already in previous Moving Things chapter.
This is the program you wrote:
import pygame as pg
import random
pg.init()
screen = pg.display.set_mode([500, 500])
square = pg.Surface((20, 20))
square.fill((255, 255, 255))
rect = square.get_rect()
clock = pg.time.Clock()
while True:
clock.tick(30)
for event in pg.event.get():
if event.type == pg.QUIT:
exit()
if event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
rect.x = random.randrange(0, 479)
rect.y = random.randrange(0, 479)
pressed = pg.key.get_pressed()
if pressed[pg.K_w]:
rect.y -= 1
if pressed[pg.K_s]:
rect.y += 1
if pressed[pg.K_a]:
rect.x -= 1
if pressed[pg.K_d]:
rect.x += 1
screen.fill((0, 0, 0))
screen.blit(square, rect)
pg.display.flip()
Here the square represents image of the sprite and rect for it’s
position. Also if you remember from Collisions chapter the Rect
objects can handle collisions for you. If these three properties are put
together you get the sprite - an image, a position and the collision handling.
This will work when you have only very limited number of objects to handle but when sprite count grows this becomes hard to maintain.
Sprites with classes¶
Common approach is to use classes to build sprites. So let’s start with a simple specifications: you want to have a sprite class which can hold your image and rect to control it’s position and to help with collision detection.
The initial class definition:
class MySprite:
def __init__(self, image, initial_pos):
self.image = image
self.rect = image.get_rect(topleft=initial_pos)
That is a very minimal implementation of the sprite with possibility to give the sprite image and initial top left position.
Let’s add two more methods to class to make class useful and reusable.
First method is move which will move sprite relatively from current
position. So for example doing .move(2, -2) would move sprite 2 pixels
right and two up. Second method to add is draw which just draws sprite
on given position on given surface - usually display surface.
The following is the full code for the class:
class MySprite:
def __init__(self, image, initial_pos):
self.image = image
self.rect = image.get_rect(topleft=initial_pos)
def move(self, x, y):
self.rect.x += x
self.rect.y += y
def draw(self, surface):
self.image.blit(surface, self.rect)
Since you already have a proper application where this class can be tested
with, modify it to use MySprite class instead of separate variables.
Here is the full program with the changes:
import pygame as pg
import random
pg.init()
screen = pg.display.set_mode([500, 500])
class MySprite:
def __init__(self, image, initial_pos):
self.image = image
self.rect = image.get_rect(topleft=initial_pos)
def move(self, x, y):
self.rect.x += x
self.rect.y += y
def draw(self, surface):
surface.blit(self.image, self.rect)
square = pg.Surface((20, 20))
square.fill((255, 255, 255))
my_sprite = MySprite(square, (10, 10))
clock = pg.time.Clock()
while True:
clock.tick(30)
for event in pg.event.get():
if event.type == pg.QUIT:
exit()
pressed = pg.key.get_pressed()
if pressed[pg.K_w]:
my_sprite.move(0, -1)
if pressed[pg.K_s]:
my_sprite.move(0, 1)
if pressed[pg.K_a]:
my_sprite.move(-1, 0)
if pressed[pg.K_d]:
my_sprite.move(1, 0)
screen.fill((0, 0, 0))
my_sprite.draw(screen)
pg.display.flip()
Collisions¶
How about adding collisions to sprite class? Since Rect object can detect
collisions and MySprite class has the rect it is easy to add collision
checking:
def collide(self, other):
return self.rect.colliderect(other.rect)
Now put that in a use. Add a second sprite that your player character can try to collide with and when colliding throw it back to top left corner of the display.
Here is the full code:
import pygame as pg
import random
pg.init()
screen = pg.display.set_mode([500, 500])
class MySprite:
def __init__(self, image, initial_pos):
self.image = image
self.rect = image.get_rect(topleft=initial_pos)
def move(self, x, y):
self.rect.x += x
self.rect.y += y
def draw(self, surface):
surface.blit(self.image, self.rect)
def collide(self, other):
return self.rect.colliderect(other.rect)
square = pg.Surface((20, 20))
square.fill((255, 255, 255))
my_sprite = MySprite(square, (10, 10))
square2 = pg.Surface((20, 20))
square2.fill((0, 255, 0))
my_sprite2 = MySprite(square2, (240, 240))
clock = pg.time.Clock()
while True:
clock.tick(30)
for event in pg.event.get():
if event.type == pg.QUIT:
exit()
pressed = pg.key.get_pressed()
if pressed[pg.K_w]:
my_sprite.move(0, -1)
if pressed[pg.K_s]:
my_sprite.move(0, 1)
if pressed[pg.K_a]:
my_sprite.move(-1, 0)
if pressed[pg.K_d]:
my_sprite.move(1, 0)
if my_sprite.collide(my_sprite2):
my_sprite.rect.topleft = (10, 10)
screen.fill((0, 0, 0))
my_sprite.draw(screen)
my_sprite2.draw(screen)
pg.display.flip()
Now when the white sprite which is movable collides with the green sprite it is respawned on top left corner.
Sprites with pygame.sprite¶
Pygame provides set of classes to work with sprites. You can find the documentation here: pygame.sprite
This is merely example how to use pygame.sprite classes.
import pygame as pg
import random
pg.init()
screen = pg.display.set_mode([500, 500])
class MySprite(pg.sprite.Sprite):
def __init__(self, image, initial_pos):
super().__init__()
self.image = image
self.rect = self.image.get_rect(topleft=initial_pos)
def move(self, x, y):
self.rect.x += x
self.rect.y += y
square = pg.Surface((20, 20))
square.fill((255, 255, 255))
my_sprite = MySprite(square, (10, 10))
player_group = pg.sprite.Group([my_sprite])
square2 = pg.Surface((20, 20))
square2.fill((0, 255, 0))
my_sprite2 = MySprite(square2, (240, 240))
enemy_group = pg.sprite.Group([my_sprite2])
clock = pg.time.Clock()
while True:
clock.tick(30)
for event in pg.event.get():
if event.type == pg.QUIT:
exit()
pressed = pg.key.get_pressed()
if pressed[pg.K_w]:
my_sprite.move(0, -1)
if pressed[pg.K_s]:
my_sprite.move(0, 1)
if pressed[pg.K_a]:
my_sprite.move(-1, 0)
if pressed[pg.K_d]:
my_sprite.move(1, 0)
if pg.sprite.groupcollide(player_group, enemy_group, False, False):
my_sprite.rect.topleft = (10, 10)
screen.fill((0, 0, 0))
player_group.draw(screen)
enemy_group.draw(screen)
pg.display.flip()
Most of the code is the same as in your custom version of sprite with classes.
Major difference is the declaration of the MySprite class and it’s
__init__ method. In pygame sprite you have to remember to call parent
class __init__ before anything else. That is achieved by using
super().__init__() call as a first line in your own sprite __init__
method.
Movement of player is done modifying player sprite directly. It could be done also via group methods but it would be quite complex.
You may notice also collision detection has been changed. Pygame sprite module
has function groupcollide(groupa, groupb, killa, killb). This does two
things. First function checks if sprite collisions between sprites in
groupa and groupb. Then if killa is True all colliding sprites
from groupa are removed. If killb is True all colliding sprites
from groupb are removed. Try it out and modify collision so that killb
value is set True.
Summary¶
Congratulations! In this section you have learned how to use sprites in Pygame:
Without classes using variables
With own custom class
With Pygame own sprites