Table of Contents
Step 014 - Pygame sprites
If you analyze the previous catch-the-thief game you will notice that most of the code in the main loop takes care of cleaning, calculating and blitting the pygame surfaces (the sprites). Pygame provides a very powerful pygame.sprite class for more elegant and object-oriented sprite programming.
The advantages of using a sprite class are:
- you code the sprite class once and can have multiple instances (sprites flying around) with little extra coding
- all relevant methods (bounce from screen edge) and property's (colour, size, rotation) are coded inside the sprite class and not in the main loop
- each sprite has it's own values and functions (with a prefix, mostly self) but you can also use class-width values and functions for all sprites of the same class (like all thieves)
- you can organize the sprite instances into pygame.sprite.Groups for mass processing
- pygame provides easy-to-use collision detection between sprites
what is a class ?
Please refer to the python documentation or other tutorials for a better introduction into object-oriented programming. For quick and dirty coding, it is enough if you compare using a sprite class with the process of making cookies:
With just one Cookie_cutter you can construct many cookies. Think of the cookie cutter as the class and of the cookies as the (constructed) instances of this class. The class (the cookie cutter) defines the property (the look and feel) of all the instances (the cookies) but the class is no instance itself (you can not eat the cookie cutter).
code example of a sprite class
discussion of sprite classes
In the code example above (below the cookies) you can how a sprite class looks like. It is custom to write the class name (after the keyword class) with a beginning capital Letter, in this case Snake instead of snake. Also note that this class derives from (is a child of) pygame's Sprite class.
Directly after the class code lines comes the docstring (where you describe what the class should do). The next two lines
image = pygame.image.load("Snake.gif") image = image.convert_alpha()
describe class-wide property and functions, shared by all instances of this class. In this examples, it makes sense to load the image file from the harddisk once and not every time a new Snake sprite is born.
class prefix self
Now comes the part describing each class instance (each individual Snake). To refer to itself as an class instance, the prefix self is used. You could use another prefix but most python coders write self. Each function need at least the argument self, even if no other parameter is passed to the function. The first function that every pygame sprite class need is the function to create a new instance of the class (a new Snake should be born). Because this function is so special pygame needs two underscores before and after it's name:
A part from the usual self, you can give the new born Snake as many arguments as you need, like initial position, behaviour, color etc. In the next example, only a startpos is given, defaulting to (50,50) if the sprite is created without a startpos.
The sprite will not work until you tell pygame to do all the stuff that it needs doing to create a new sprite. This is done by calling the __init__ function of the class (Snake) parent's class (pygame.sprite.Sprite), also a good way to handle pygame's sprite groups.
storing class parameters
If you want to store any parameters for the sprite itself (so that other functions like an update or kill function can access them, you need to save the parameter into a class instance variable with a proper prefix (self).
It is best you always think of these two lines as one organic block, like flour and water:
def __init__(self, startpos=(50,50)): pygame.sprite.Sprite.__init__(self,self.groups) # never forget this line ! self.pos = startpos # store startpos into a class instance variable with the prefix self.
call the property of the class
Now you are free to code all the property's of the class instance. Remember to write the prefix self to indicate that a property (like the position) is valid or this individual snake (class instance) only. Here, the starpos argument is stored into the variable self.pos:
self.pos = startpos
To access class-wide variables, simply call the name of the class (note the capital letter at the beginning):
self.image = Snake.image
useful stuff for each class
- self.image …how the sprite look. It's a pygame surface, loaded or created, and you can use the pygame.draw commands on it.
- self.rect …very useful, get it with the command
self.rect = self.image.get_rect()after you are done with creating self.image
- self.radius … useful for circular collision detection
- self.rect.center … if you have self.rect, use this to control the postion of a sprite on the screen.
- update(self, time): … a function (best with the passed seconds since last frame as argument) where you can calculate what the sprite should do, like moving, bouncing of walls etc
- kill(self) … very useful to destroy a sprite. the call inside the class is self.kill(), in the mainloop you would call snake222.kill()
a complete sprite class
This is the complete code for the Birdcatcher class in the example game (see source code. This class consist of a red circle centred around the mouse pointer. As you can see the code to create the BirdCatcher image is shared for all instances of the class. Each individual BirdCatcher sprite has it's own self.image.
class BirdCatcher(pygame.sprite.Sprite): # class variables shared by all instances of this class image = pygame.Surface((100,100)) # created on the fly image.set_colorkey((0,0,0)) # black transparent pygame.draw.circle(self.image, (255,0,0), (50,50), 50, 2) # red circle image = self.image.convert_alpha() # code for each individual class instance def __init__(self): pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line ! self.image = BirdCatcher.image # make class-variable an instance variable self.rect = self.image.get_rect() self.radius = 50 # for collide check def update(self, seconds): # no need for seconds but the other sprites need it self.rect.center = pygame.mouse.get_pos()
before the main loop
Before the main loop starts, you define some pygame.sprite.Groups to contain all the sprites: If you have a Sprite Snake and the two groups allgroup and snakegroup you can assign that all Snake sprites should be members of both groups:
allgroup = pygame.sprite.Group() snakegroup = pygame.sprite.Group() # each Snake sprite is automatically member of both groups: Snake.groups = allgroup, snakegroup # create a single Snake named "mypython" mypython = Snake()
what to do in the mainloop
You must make sure that your sprites belong to a sprite group (like allsprites). In the mainloop, you simply call those commands each frame:
allsprites.clear(screen, background) allsprites.update(seconds) allsprites.draw(screen) pygame.display.flip()
source code on github
To run this example you need:
|014_sprites.py|| || Download the whole Archive with all files from Github:
| babytux.png ||
| babytux_neg.png ||
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/014_sprites.py
click reload in your browser if you see no code here: