In this lesson, we’ll create a few interesting and useful sprite variations: (1) a Brick sprite that positions itself at a random location in the game window, (2) a mouse following sprite, and (3) a label sprite that lets you display text at a given position. We’ll add these Sprite subclasses to the mySprites.py module we used for our Box sprite last time, and show how to use each one.
A Brick Sprite
In our first example, we’re going to create a Brick sprite to represent a brick wall image. The initializer method will position the sprite at some random location in the game window. This sprite will be static (i.e., not move) once instantiated so you will notice that it does not have an update() method defined.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import pygame import random class Brick(pygame.sprite.Sprite): '''A simple Sprite subclass to represent static Brick sprites.''' def __init__(self, screen): # Call the parent __init__() method pygame.sprite.Sprite.__init__(self) # Set the image and rect attributes for the bricks self.image = pygame.image.load("bricks.png") self.rect = self.image.get_rect() self.rect.centerx = random.randrange(0, screen.get_width()) self.rect.centery = random.randrange(0, screen.get_height()) |
As we’ve done before, we receive a parameter referring to the screen in the __init__() method (game window). This is necessary because in the last two lines of the method need to call get_width() and get_height() for the game window so that the brick wall image can be randomly positioned within it.
The mainline code is pretty standard, the only really new code is in the Entities (IDEA) section:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# I - Import and Initialize import pygame import mySprites pygame.init() def main(): '''This function defines the 'mainline logic' for our game.''' # Display screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Lots Of Bricks") # Entities background = pygame.Surface(screen.get_size()) background.fill((255, 255, 255)) screen.blit(background, (0, 0)) # Create 10 random bricks using a loop and a list bricks = [] for i in range(10): bricks.append(mySprites.Brick(screen)) # Add list of 10 Brick Sprites to one OrderedUpdates Sprite Group allSprites = pygame.sprite.OrderedUpdates(bricks) # ACTION # Assign keepGoing = True clock = pygame.time.Clock() # Loop while keepGoing: # Time clock.tick(30) # Events for event in pygame.event.get(): if event.type == pygame.QUIT: keepGoing = False # Refresh screen allSprites.clear(screen, background) # The Brick Sprite has NO update() method, so next line does nothing. allSprites.update() allSprites.draw(screen) pygame.display.flip() # Close the game window pygame.quit() # Call the main function |
On lines 18 to 24 we create an empty bricks list, then use a basic for loop to instantiate 10 Brick sprites and append() them to the list. Now you can appreciate why lists (see U3-6) are useful data structures for organizing data/objects in a game! On line 24 we add the entire list of Brick sprites to the allSprites group. This way, in the main game loop (ALTER), all 10 Brickobjects can be drawn with only a few lines of code. Using this technique I can handle any number of bricks without adding any new lines of code.
The rest of the main code is the same as we used for our Box sprite last time. Because the Brick class is not overriding the update() method inherited from the Sprite class, the call to allSprites.update() on line 46 will not do anything.
A Mouse-Following Sprite
Next, let’s create a Sprite subclass called Circle that will update the position of a blue circle based on the current position of the mouse pointer. Add this class to the mySprites.py module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Circle(pygame.sprite.Sprite): '''Mouse-following Circle Sprite subclass.''' def __init__(self): '''Initializer to set the image for our Circle Sprite.''' # Call the parent __init__() method pygame.sprite.Sprite.__init__(self) # Customize the image and rect for the circle self.image = pygame.Surface((50, 50)) self.image.fill((0, 0, 0)) self.image.set_colorkey((0,0,0)) pygame.draw.circle(self.image, (0, 0, 255), (25, 25), 25, 0) self.rect = self.image.get_rect() def update(self): '''Move the center of the circle to where the mouse is pointing.''' self.rect.center = pygame.mouse.get_pos() |
By now you should be familiar with most of this code. On line 10, I set the image background to a colour different than the colour I will choose for my circle, in this case, black. On line 11, the set_colorkey() method indicates that I would like any black pixels on the image to be transparent, so that the screen background shows through. To understand this better, try commenting out line 11 and see what happens. Line 12 draws a blue circle onto the image of our sprite.
The easiest way to make the sprite follow the mouse pointer is to set the (x, y) coordinate for the centre of our rect to the mouse’s current (x, y) position. This is exactly what line 17 does every time update() is called.
Here’s the IDEA sections for the main code, only lines 19 and 20 of the Entities (IDEA) section is different from the last example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# I - Import and Initialize import pygame import mySprites pygame.init() def main(): '''This function defines the 'mainline logic' for our game.''' # Display screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Move the circle with the mouse!") # Entities background = pygame.Surface(screen.get_size()) background.fill((255, 255, 255)) screen.blit(background, (0, 0)) # Create a Circle sprite object and add to an OrderedUpdates Sprite Group circle = mySprites.Circle() allSprites = pygame.sprite.OrderedUpdates(circle) # From here down the code is *identical* to our Brick Sprite example above... |
A Text Label Sprite
In U4-6 we learned how to instantiate Font and SysFont objects and use their render() methods to draw text on the screen. Putting text into a Sprite object makes it easy to place text in the game window and update it as needed, for example, to keep track of a user’s score. Add the following code to our mySprites.py module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Label(pygame.sprite.Sprite): '''An mutatable text Label Sprite subclass''' def __init__(self, message, x_y_center): # Call the parent __init__() method pygame.sprite.Sprite.__init__(self) self.font = pygame.font.SysFont("None", 30) self.text = message self.center = x_y_center def setText(self, message): '''Mutator for text to be displayed on the label.''' self.text = message def update(self): '''Render and center the label text on each Refresh.''' self.image = self.font.render(self.text, 1, (0, 0, 0)) self.rect = self.image.get_rect() self.rect.center = self.center |
The Label class does the same job as any other sprite, except the image of this class will actually be rendered text. The font, text, and center instance variables are custom for this Label class. To make it possible to change the text for a Labelafter it is instantiated, I provide a setText() mutator method. Since the text could change between any frames, the image and rect attributes are (re)initialized each time the update() method is called rather than only once in the initializer.
Sprites become really handy when you use more than one in your program: You think once about how to make a general case work (as you define the class), and then you can make as many instances as you want – and they’ll all work independently, the same way, without any additional code.
In the demo code below we’ll create 3 instances of our Label class. The first two labels (label1 and label2) will be used for static (unchanging) text. The labelEvent sprite will be used to dynamically report on different types of events as they occur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# I - Import and Initialize import pygame import mySprites pygame.init() def main(): '''This function defines the 'mainline logic' for our game.''' # Display screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Label demo") # Entities background = pygame.Surface(screen.get_size()) background.fill((255, 255, 255)) screen.blit(background, (0, 0)) # Create 3 Label sprites and add to an OrderedUpdates Sprite Group label1 = mySprites.Label("Hi. I'm a label.", (100,100)) label2 = mySprites.Label("I'm another label.", (400,400)) # Will update labelEvent.text during mouse Event Handling labelEvent = mySprites.Label("", (320,200)) allSprites = pygame.sprite.OrderedUpdates(label1, label2, labelEvent) # ACTION # Assign clock = pygame.time.Clock() keepGoing = True # Loop while keepGoing: # Time clock.tick(30) # Events for event in pygame.event.get(): if event.type == pygame.QUIT: keepGoing = False elif event.type == pygame.MOUSEMOTION: labelEvent.setText("mouse: " + str(pygame.mouse.get_pos())) elif event.type == pygame.MOUSEBUTTONDOWN: labelEvent.setText("button press") elif event.type == pygame.KEYDOWN: labelEvent.setText("key down") # Refresh screen allSprites.clear(screen, background) allSprites.update() allSprites.draw(screen) pygame.display.flip() # Close the game window pygame.quit() # Call the main function main() |
As you can see in the Event Handling (ALTER) section, whenever a mouse or keyboard event occurs, the text of the labelEventsprite is updated using the setText() mutator method.
You Try!
- Start a new page in your Learning Journal titled “4-10 More Sprites”. Carefully read the notes above and in your own words summarize the key ideas from each section.