backlinks to this page:
Github:
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).
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.
class Snake(pygame.sprite.Sprite):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:
def __init__(self):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.imageThis 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:
file | in folder | download |
---|---|---|
014_sprites.py | pygame | Download the whole Archive with all files from Github: https://github.com/horstjens/ThePythonGameBook/archives/master |
babytux.png ![]() | pygame/data |
|
babytux_neg.png ![]() | pygame/data |
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/014_sprites.py
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 014-sprites.py real pygame sprites url: http://thepythongamebook.com/en:part2:pygame:step014 author: horst.jens@spielend-programmieren.at licence: gpl, see http://www.gnu.org/licenses/gpl.html Real pygame Sprites moving around. Create more sprites with mouse click. Shows collision detection loading images from a subfolder called 'data' all images files must be in the subfolder 'data'. The subfolder must be inside the same folder as the program itself. works with pyhton3.4 and python2.7 """ import pygame import os import random pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag pygame.init() screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens ! #winstyle = 0 # |FULLSCREEN # Set the display mode BIRDSPEED = 50.0 def write(msg="pygame is cool"): myfont = pygame.font.SysFont("None", 32) mytext = myfont.render(msg, True, (0,0,0)) mytext = mytext.convert_alpha() return mytext class BirdCatcher(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self, self.groups) self.image = pygame.Surface((100,100)) # created on the fly self.image.set_colorkey((0,0,0)) # black transparent pygame.draw.circle(self.image, (255,0,0), (50,50), 50, 2) # red circle self.image = self.image.convert_alpha() 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() class Bird(pygame.sprite.Sprite): image=[] # list of all images # not necessary: birds = {} # a dictionary of all Birds, each Bird has its own number number = 0 def __init__(self, startpos=(50,50), area=screen.get_rect()): pygame.sprite.Sprite.__init__(self, self.groups) self.pos = [0.0,0.0] self.pos[0] = startpos[0]*1.0 # float self.pos[1] = startpos[1]*1.0 # float self.image = Bird.image[0] self.rect = self.image.get_rect() self.area = area # where the sprite is allowed to move self.newspeed() self.catched = False #--- not necessary: self.number = Bird.number # get my personal Birdnumber Bird.number+= 1 # increase the number for next Bird Bird.birds[self.number] = self # store myself into the Bird dictionary #print "my number %i Bird number %i " % (self.number, Bird.number) def newspeed(self): # new birdspeed, but not 0 speedrandom = random.choice([-1,1]) # flip a coin self.dx = random.random() * BIRDSPEED * speedrandom + speedrandom self.dy = random.random() * BIRDSPEED * speedrandom + speedrandom def update(self, seconds): self.pos[0] += self.dx * seconds self.pos[1] += self.dy * seconds # -- check if out of screen if not self.area.contains(self.rect): self.image = Bird.image[1] # crash into wall # --- compare self.rect and area.rect if self.pos[0] + self.rect.width/2 > self.area.right: self.pos[0] = self.area.right - self.rect.width/2 if self.pos[0] - self.rect.width/2 < self.area.left: self.pos[0] = self.area.left + self.rect.width/2 if self.pos[1] + self.rect.height/2 > self.area.bottom: self.pos[1] = self.area.bottom - self.rect.height/2 if self.pos[1] - self.rect.height/2 < self.area.top: self.pos[1] = self.area.top + self.rect.height/2 self.newspeed() # calculate a new direction else: if self.catched: self.image = Bird.image[2] # blue rectangle else: self.image = Bird.image[0] # normal bird image #--- calculate new position on screen ----- self.rect.centerx = round(self.pos[0],0) self.rect.centery = round(self.pos[1],0) background = pygame.Surface((screen.get_width(), screen.get_height())) background.fill((255,255,255)) # fill white background.blit(write("Press left mouse button for more sprites. Press ESC to quit"),(5,10)) background = background.convert() # jpg can not have transparency screen.blit(background, (0,0)) # blit background on screen (overwriting all) clock = pygame.time.Clock() # create pygame clock object mainloop = True FPS = 60 # desired max. framerate in frames per second. # load images into classes (class variable !) try: Bird.image.append(pygame.image.load(os.path.join("data","babytux.png"))) Bird.image.append(pygame.image.load(os.path.join("data","babytux_neg.png"))) except: raise( UserWarning, "Unable to find babytux images in the folder 'data' :-( ") Bird.image.append(Bird.image[0].copy()) # copy of first image pygame.draw.rect(Bird.image[2], (0,0,255), (0,0,32,36), 1) # blue border Bird.image[0] = Bird.image[0].convert_alpha() Bird.image[1] = Bird.image[1].convert_alpha() Bird.image[2] = Bird.image[2].convert_alpha() birdgroup = pygame.sprite.Group() allgroup = pygame.sprite.Group() #assign default groups to each sprite class Bird.groups = birdgroup, allgroup BirdCatcher.groups = allgroup # one single Bird Bird() # display the BirdCatcher and name it "hunter" hunter = BirdCatcher() while mainloop: milliseconds = clock.tick(FPS) # milliseconds passed since last frame seconds = milliseconds / 1000.0 # seconds passed since last frame for event in pygame.event.get(): if event.type == pygame.QUIT: mainloop = False # pygame window closed by user elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: mainloop = False # user pressed ESC # create new Bird on mouseclick if pygame.mouse.get_pressed()[0]: Bird(pygame.mouse.get_pos()) # create a new Bird at mousepos pygame.display.set_caption("[FPS]: %.2f birds: %i" % (clock.get_fps(), len(birdgroup))) # ------ collision detecttion for bird in birdgroup: bird.catched = False # set all Bird sprites to not catched #pygame.sprite.spritecollide(sprite, group, dokill, collided = None): return Sprite_list crashgroup = pygame.sprite.spritecollide(hunter, birdgroup, False, pygame.sprite.collide_circle) # pygame.sprite.collide_circle works only if one sprite has self.radius # you can do without that argument collided and only the self.rects will be checked for crashbird in crashgroup: crashbird.catched = True # will get a blue border from Bird.update() #crashbird.kill() # this would remove him from all his groups allgroup.clear(screen, background) allgroup.update(seconds) allgroup.draw(screen) pygame.display.flip() # flip the screen 30 times a second