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:
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).
|dough||a cookie cutter||many cookies|
|some coding||class||many class instances (sprites)|
class Snake(pygame.sprite.Sprite): """the pygame Snake""" image = pygame.image.load("Snake.gif") image = image.convert_alpha() def __init__(self, startpos): pygame.sprite.Sprite.__init__(self, self.groups) self.pos = startpos self.image = Snake.image self.rect = self.image.get_rect() def update(self): self.rect.center = self.pos
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.
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.
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.
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
self.rect = self.image.get_rect()after you are done with creating self.image
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 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()
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()
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: