Sprite Class And Sprite Groups Explained

Why Use A Sprite Class Or Group?

In a typical game there are sprites that are only used for one object, like the player character for example, and there are those sprites that are used multiple times like bullets, grenades, arrows or even platforms.

When we have many identical or similar objects like this, we need a way of organising them together to make them easier to work with. Python lists are an obvious choice here, but pygame has its own way of holding multiple sprite objects together, which is the Group class.

This class allows us to group similar sprites together and then easily pass functions to them such as blitting them on the screen, moving them around and checking for collisions.

Creating A Sprite Class

Before we can begin using groups, we first need to create Sprite classes to put into these groups. A Sprite class is much like a regular python class, but it is created as a child class of pygame’s Sprite class. With this, it inherits that class’ functionality.

We’ll begin with a simple python Class for a square:

				
					#create class for squares
class Square():
  def __init__(self, col, x, y):
    self.image = pygame.Surface((50, 50))
    self.image.fill(col)
    self.rect = self.image.get_rect()
    self.rect.center = (x, y)
				
			

This class creates a surface, fills it with a colour, derives a rectangle from it and positions it based on the x and y coordinates.

To make this into a Sprite class, the code is modified like this:

				
					#create class for squares
class Square(pygame.sprite.Sprite):
  def __init__(self, col, x, y):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.Surface((50, 50))
    self.image.fill(col)
    self.rect = self.image.get_rect()
    self.rect.center = (x, y)
				
			

The differences here are:

  • On line 2 I add pygame.sprite.Sprite into the brackets
  • On line 4 I add pygame.sprite.Sprite.__init__(self)

These two additions make this Square class a child of the Sprite class, which gives us access to more features, including being able to use Groups.

Setting Up A Sprite Group

The code below creates an empty sprite group that we can then add all our squares into once we create them.

				
					squares = pygame.sprite.Group()

print(squares)
				
			

The output of the print statement is shown below and it tells us that the group has been created successfully and currently has no sprites inside it.

output from the print statement showing that the group has zero sprites inside it

Now we can create a square using the class we setup earlier, and add it to this new group.

				
					#create square and add to squares group
square = Square("crimson", 500, 300)
squares.add(square)

print(squares)
				
			

The Square class requires three arguments, which are square colour, x and y coordinates. We pass those three here to create an instance called square, which we then put into our group using the .add() method.

The print statement now shows that the group contains one sprite.

.draw() and .update() Methods

We’ve already seen one of the methods that the Group class includes, which is .add() and we used that to add the new square into the group.

Another two methods that are built into the Group class are .draw() and .update()

This starter code includes the sections covered above.

				
					import pygame

pygame.init()

#define screen size
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600

#create game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Sprite Groups")

#frame rate
clock = pygame.time.Clock()
FPS = 60

#create class for squares
class Square(pygame.sprite.Sprite):
  def __init__(self, col, x, y):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.Surface((50, 50))
    self.image.fill(col)
    self.rect = self.image.get_rect()
    self.rect.center = (x, y)

#create sprite group for squares
squares = pygame.sprite.Group()

#create square and add to squares group
square = Square("crimson", 500, 300)
squares.add(square)

#game loop
run = True
while run:

  clock.tick(FPS)
  
  #update background
  screen.fill("cyan")

  #update sprite group
  squares.update()

  #draw sprite group
  squares.draw(screen)
  
  print(squares)

  #event handler
  for event in pygame.event.get():
    #quit program
    if event.type == pygame.QUIT:
      run = False

  #update display
  pygame.display.flip()

pygame.quit()
				
			

On lines 43 and 46 I am calling the update() and draw() methods even though I haven’t created these two methods in my Square class.

This code works because those two methods were inherited  when we setup our class as a Sprite class earlier. The draw() method required one argument, which is the surface to draw onto, and it will then draw all the sprites in the group.

The update() method is empty and exists as a placeholder for us to add our own functionality to.

Creating Multiple Instances Of A Class

Now that we have the class and group setup, let’s expand this by creating multiple squares and storing them all in this group.

First we define a list of colours to choose from. This should be done before the game loop.

				
					#colours
colours = ["crimson", "chartreuse", "coral", "darkorange", "forestgreen", "lime", "navy"]
				
			

Then we update the event handler to check for mouseclicks. We use this event to create a square at the mouse coordinates and add it to the squares group.

				
					if event.type == pygame.MOUSEBUTTONDOWN:
  #get mouse coordinates
  pos = pygame.mouse.get_pos()
  #create square
  square = Square(random.choice(colours), pos[0], pos[1])
  squares.add(square)
				
			

Since this uses the random module to pick a color, make sure you import it right at the beginning of the code.

				
					import pygame
import random
				
			

This is the result of running the updated code. I can now click and create multiple squares with each one being added to the group. Since I am calling the group’s draw() method, it draws all the squares on the screen without me having to define a draw() method myself.

Adding Functionality To The Class

I mentioned previously that our class has inherited an update() method, which is why we don’t get an error when we run squares.update() in the main loop. But this method is empty and doesn’t do anything, so to overwrite it, we simply define our own update method.

Add this code into the squares class after the init() method.

				
					def update(self):
  self.rect.move_ip(0, 5)
				
			

Now as you create the squares, they all fall off the bottom of the screen. I didn’t have to apply this to each square individually or iterate through a list. By applying this method to the group, it automatically applies it to each square inside that group.

There is one issue that isn’t immediately obvious here, but it becomes clearer when we print out the group again after creating all these squares.

Although the squares all seem to fall off the screen and disappear, they are still stores inside the group. We can’t see them anymore, but Pygame is still continuing to process them and move them infinitely down.

This might not seem like much of an issue but each of these sprites is taking up memory and processing time and in a larger game with many sprites, this could affect performance.

And this brings us onto another useful feature of groups.

Deleting Sprites Using The .kill() Method

When a sprite is no longer needed, we need to make sure it is removed from the group so that it is no longer taking up any processing power. This can be done using the .kill() method, demonstrated with the code below.

Modify the update method from before so it looks like this. The code now checks if the top of the square has gone beyond the bottom of the screen, in which case we run self.kill(), which deletes that instance.

				
					def update(self):
  self.rect.move_ip(0, 5)
  #check if sprite has gone off screen
  if self.rect.top > SCREEN_HEIGHT:
    self.kill()
				
			

Now we can see the number of sprites in the group updating so any sprites that fall off the bottom of the screen are removed.

Sprite Collision Using Groups

Another feature of groups is that they simplify collision checks by allowing us to quickly check for collision with all the items in a group.
This is done using the spritecollide() method, which takes the following arguments:
  • sprite – the individual sprite being checked.
  • group – the sprite group containing all the sprites to check for collision with the sprite
  • dokill – a boolean value that when set to True, will trigger the self.kill() method to remove the colliding sprite from the group.
  • collided – is the callback function for the type of collision required. If nothing is passed then this will be done using rectangle collision, but other methods like circle and mask collision can be passed here instead.

I’ve previously covered collision in detail these two articles: Top 3 Collision Types In Pygame & How To Use Pygame Masks For Pixel Perfect Collision

Conclusion And Complete Code

Sprites and Groups may seem like a complicated concept at first, but once you have an understanding of how they work, the additional methods that they include make it much easier to work with images in Pygame.

The final code for this tutorial is below:

				
					import pygame
import random

pygame.init()

#define screen size
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600

#create game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Sprite Groups")

#frame rate
clock = pygame.time.Clock()
FPS = 60

#colours
colours = ["crimson", "chartreuse", "coral", "darkorange", "forestgreen", "lime", "navy"]

#create class for squares
class Square(pygame.sprite.Sprite):
  def __init__(self, col, x, y):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.Surface((50, 50))
    self.image.fill(col)
    self.rect = self.image.get_rect()
    self.rect.center = (x, y)

  def update(self):
    self.rect.move_ip(0, 5)
    #check if sprite has gone off screen
    if self.rect.top > SCREEN_HEIGHT:
      self.kill()

#create sprite group for squares
squares = pygame.sprite.Group()

#create square and add to squares group
square = Square("crimson", 500, 300)
squares.add(square)

#game loop
run = True
while run:

  clock.tick(FPS)
  
  #update background
  screen.fill("cyan")

  #update sprite group
  squares.update()

  #draw sprite group
  squares.draw(screen)

  print(squares)

  #event handler
  for event in pygame.event.get():
    if event.type == pygame.MOUSEBUTTONDOWN:
      #get mouse coordinates
      pos = pygame.mouse.get_pos()
      #create square
      square = Square(random.choice(colours), pos[0], pos[1])
      squares.add(square)
    #quit program
    if event.type == pygame.QUIT:
      run = False

  #update display
  pygame.display.flip()

pygame.quit()
				
			

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *