In Chapters 18 and 19, you learned how to make GUI programs that have graphics and can accept input from the keyboard and mouse. You also learned how to draw different shapes. In this chapter, you’ll learn how to add sounds, music, and images to your games.
A sprite is a single two-dimensional image that is used as part of the graphics on a screen. Figure 20-1 shows some example sprites.
Figure 20-1: Some examples of sprites
The sprite images are drawn on top of a background. You can flip the sprite image horizontally so that it is facing the other way. You can also draw the same sprite image multiple times on the same window, and you can resize sprites to be larger or smaller than the original sprite image. The background image can be considered one large sprite, too. Figure 20-2 shows sprites being used together.
Figure 20-2: A complete scene, with sprites drawn on top of a background
The next program will demonstrate how to play sounds and draw sprites using pygame.
Sprites are stored in image files on your computer. There are several image formats that pygame can use. To tell what format an image file uses, look at the end of the filename (after the last period). This is called the file extension. For example, the file player.png is in the PNG format. The image formats pygame supports include BMP, PNG, JPG, and GIF.
You can download images from your web browser. On most web browsers, you do so by right-clicking the image in the web page and selecting Save from the menu that appears. Remember where on the hard drive you saved the image file, because you’ll need to copy the downloaded image file into the same folder as your Python program’s .py file. You can also create your own images with a drawing program like Microsoft Paint or Tux Paint.
The sound file formats that pygame supports are MIDI, WAV, and MP3. You can download sound effects from the internet just as you can image files, but the sound files must be in one of these three formats. If your computer has a microphone, you can also record sounds and make your own WAV files to use in your games.
This chapter’s program is the same as the Collision Detection program from Chapter 19. However, in this program we’ll use sprites instead of plain-looking squares. We’ll use a sprite of a person to represent the player instead of the black box and a sprite of cherries instead of the green food squares. We’ll also play background music, and we’ll add a sound effect when the player sprite eats one of the cherries.
In this game, the player sprite will eat cherry sprites and, as it eats the cherries, it will grow. When you run the program, the game will look like Figure 20-3.
Figure 20-3: A screenshot of the Sprites and Sounds game
Start a new file, enter the following code, and then save it as spritesAndSounds.py. You can download the image and sound files we’ll use in this program from this book’s website at https://www.nostarch.com/inventwithpython/. Place these files in the same folder as the spritesAndSounds.py program.
If you get errors after entering this code, compare the code you typed to the book’s code with the online diff tool at https://www.nostarch.com/inventwithpython#diff.
spritesAnd Sounds.py
1. import pygame, sys, time, random
2. from pygame.locals import *
3.
4. # Set up pygame.
5. pygame.init()
6. mainClock = pygame.time.Clock()
7.
8. # Set up the window.
9. WINDOWWIDTH = 400
10. WINDOWHEIGHT = 400
11. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),
0, 32)
12. pygame.display.set_caption('Sprites and Sounds')
13.
14. # Set up the colors.
15. WHITE = (255, 255, 255)
16.
17. # Set up the block data structure.
18. player = pygame.Rect(300, 100, 40, 40)
19. playerImage = pygame.image.load('player.png')
20. playerStretchedImage = pygame.transform.scale(playerImage, (40, 40))
21. foodImage = pygame.image.load('cherry.png')
22. foods = []
23. for i in range(20):
24. foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - 20),
random.randint(0, WINDOWHEIGHT - 20), 20, 20))
25.
26. foodCounter = 0
27. NEWFOOD = 40
28.
29. # Set up keyboard variables.
30. moveLeft = False
31. moveRight = False
32. moveUp = False
33. moveDown = False
34.
35. MOVESPEED = 6
36.
37. # Set up the music.
38. pickUpSound = pygame.mixer.Sound('pickup.wav')
39. pygame.mixer.music.load('background.mid')
40. pygame.mixer.music.play(-1, 0.0)
41. musicPlaying = True
42.
43. # Run the game loop.
44. while True:
45. # Check for the QUIT event.
46. for event in pygame.event.get():
47. if event.type == QUIT:
48. pygame.quit()
49. sys.exit()
50. if event.type == KEYDOWN:
51. # Change the keyboard variables.
52. if event.key == K_LEFT or event.key == K_a:
53. moveRight = False
54. moveLeft = True
55. if event.key == K_RIGHT or event.key == K_d:
56. moveLeft = False
57. moveRight = True
58. if event.key == K_UP or event.key == K_w:
59. moveDown = False
60. moveUp = True
61. if event.key == K_DOWN or event.key == K_s:
62. moveUp = False
63. moveDown = True
64. if event.type == KEYUP:
65. if event.key == K_ESCAPE:
66. pygame.quit()
67. sys.exit()
68. if event.key == K_LEFT or event.key == K_a:
69. moveLeft = False
70. if event.key == K_RIGHT or event.key == K_d:
71. moveRight = False
72. if event.key == K_UP or event.key == K_w:
73. moveUp = False
74. if event.key == K_DOWN or event.key == K_s:
75. moveDown = False
76. if event.key == K_x:
77. player.top = random.randint(0, WINDOWHEIGHT -
player.height)
78. player.left = random.randint(0, WINDOWWIDTH -
player.width)
79. if event.key == K_m:
80. if musicPlaying:
81. pygame.mixer.music.stop()
82. else:
83. pygame.mixer.music.play(-1, 0.0)
84. musicPlaying = not musicPlaying
85.
86. if event.type == MOUSEBUTTONUP:
87. foods.append(pygame.Rect(event.pos[0] - 10,
event.pos[1] - 10, 20, 20))
88.
89. foodCounter += 1
90. if foodCounter >= NEWFOOD:
91. # Add new food.
92. foodCounter = 0
93. foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - 20),
random.randint(0, WINDOWHEIGHT - 20), 20, 20))
94.
95. # Draw the white background onto the surface.
96. windowSurface.fill(WHITE)
97.
98. # Move the player.
99. if moveDown and player.bottom < WINDOWHEIGHT:
100. player.top += MOVESPEED
101. if moveUp and player.top > 0:
102. player.top -= MOVESPEED
103. if moveLeft and player.left > 0:
104. player.left -= MOVESPEED
105. if moveRight and player.right < WINDOWWIDTH:
106. player.right += MOVESPEED
107.
108.
109. # Draw the block onto the surface.
110. windowSurface.blit(playerStretchedImage, player)
111.
112. # Check whether the block has intersected with any food squares.
113. for food in foods[:]:
114. if player.colliderect(food):
115. foods.remove(food)
116. player = pygame.Rect(player.left, player.top,
player.width + 2, player.height + 2)
117. playerStretchedImage = pygame.transform.scale(playerImage,
(player.width, player.height))
118. if musicPlaying:
119. pickUpSound.play()
120.
121. # Draw the food.
122. for food in foods:
123. windowSurface.blit(foodImage, food)
124.
125. # Draw the window onto the screen.
126. pygame.display.update()
127. mainClock.tick(40)
Most of the code in this program is the same as the Collision Detection program in Chapter 19. We’ll focus only on the parts that add sprites and sounds. First, on line 12 let’s set the caption of the title bar to a string that describes this program:
12. pygame.display.set_caption('Sprites and Sounds')
In order to set the caption, you need to pass the string 'Sprites and Sounds' to the pygame.display.set_caption() function.
Now that we have the caption set up, we need the actual sprites. We’ll use three variables to represent the player, unlike the previous programs that used just one.
17. # Set up the block data structure.
18. player = pygame.Rect(300, 100, 40, 40)
19. playerImage = pygame.image.load('player.png')
20. playerStretchedImage = pygame.transform.scale(playerImage, (40, 40))
21. foodImage = pygame.image.load('cherry.png')
The player variable on line 18 will store a Rect object that keeps track of the location and size of the player. The player variable doesn’t contain the player’s image. At the beginning of the program, the top-left corner of the player is located at (300, 100), and the player has an initial height and width of 40 pixels.
The second variable that represents the player is playerImage on line 19. The pygame.image.load() function is passed a string of the filename of the image to load. The return value is a Surface object that has the graphics in the image file drawn on its surface. We store this Surface object inside playerImage.
On line 20, we’ll use a new function in the pygame.transform module. The pygame.transform.scale() function can shrink or enlarge a sprite. The first argument is a Surface object with the image drawn on it. The second argument is a tuple for the new width and height of the image in the first argument. The scale() function returns a Surface object with the image drawn at a new size. In this chapter’s program, we’ll make the player sprite stretch larger as it eats more cherries. We’ll store the original image in the playerImage variable but the stretched image in the playerStretchedImage variable.
On line 21, we call load() again to create a Surface object with the cherry image drawn on it. Be sure you have the player.png and cherry.png files in the same folder as the spritesAndSounds.py file; otherwise, pygame won’t be able to find them and will give an error.
Next you need to load the sound files. There are two modules for sound in pygame. The pygame.mixer module can play short sound effects during the game. The pygame.mixer.music module can play background music.
Call the pygame.mixer.Sound() constructor function to create a pygame.mixer.Sound object (called a Sound object for short). This object has a play() method that will play the sound effect when called.
37. # Set up the music.
38. pickUpSound = pygame.mixer.Sound('pickup.wav')
39. pygame.mixer.music.load('background.mid')
40. pygame.mixer.music.play(-1, 0.0)
41. musicPlaying = True
Line 39 calls pygame.mixer.music.load() to load the background music, and line 40 calls pygame.mixer.music.play() to start playing it. The first parameter tells pygame how many times to play the background music after the first time we play it. So passing 5 would cause pygame to play the background music six times. Here we pass the parameter -1, which is a special value that makes the background music repeat forever.
The second parameter to play() is the point in the sound file to start playing. Passing 0.0 will play the background music starting from the beginning. Passing 2.5 would start the background music 2.5 seconds from the beginning.
Finally, the musicPlaying variable has a Boolean value that tells the program whether it should play the background music and sound effects or not. It’s nice to give the player the option to run the program without the sound playing.
The M key will turn the background music on or off. If musicPlaying is set to True, then the background music is currently playing, and we should stop it by calling pygame.mixer.music.stop(). If musicPlaying is set to False, then the background music isn’t currently playing, and we should start it by calling play(). Lines 79 to 84 use if statements to do this:
79. if event.key == K_m:
80. if musicPlaying:
81. pygame.mixer.music.stop()
82. else:
83. pygame.mixer.music.play(-1, 0.0)
84. musicPlaying = not musicPlaying
Whether the music is playing or not, we want to toggle the value in musicPlaying. Toggling a Boolean value means to set a value to the opposite of its current value. The line musicPlaying = not musicPlaying sets the variable to False if it is currently True or sets it to True if it is currently False. Think of toggling as what happens when you flip a light switch on or off: toggling the light switch sets it to the opposite setting.
Remember that the value stored in playerStretchedImage is a Surface object. Line 110 draws the sprite of the player onto the window’s Surface object (which is stored in windowSurface) using blit():
109. # Draw the block onto the surface.
110. windowSurface.blit(playerStretchedImage, player)
The second parameter to the blit() method is a Rect object that specifies where on the Surface object the sprite should be drawn. The program uses the Rect object stored in player, which keeps track of the player’s position in the window.
This code is similar to the code in the previous programs, but there are a couple of new lines:
114. if player.colliderect(food):
115. foods.remove(food)
116. player = pygame.Rect(player.left, player.top,
player.width + 2, player.height + 2)
117. playerStretchedImage = pygame.transform.scale(playerImage,
(player.width, player.height))
118. if musicPlaying:
119. pickUpSound.play()
When the player sprite eats one of the cherries, its size increases by two pixels in height and width. On line 116, a new Rect object that is two pixels larger than the old Rect object will be assigned as the new value of player.
While the Rect object represents the position and size of the player, the image of the player is stored in a playerStretchedImage as a Surface object. On line 117, the program creates a new stretched image by calling scale().
Stretching an image often distorts it a little. If you keep restretching an already stretched image, the distortions add up quickly. But by stretching the original image to a new size each time—by passing playerImage, not playerStretchedImage, as the first argument for scale()—you distort the image only once.
Finally, line 119 calls the play() method on the Sound object stored in the pickUpSound variable. But it does this only if musicPlaying is set to True (which means that the sound is turned on).
In the previous programs, you called the pygame.draw.rect() function to draw a green square for each Rect object stored in the foods list. In this program, however, you want to draw the cherry sprites instead. Call the blit() method and pass the Surface object stored in foodImage, which has the cherries image drawn on it:
121. # Draw the food.
122. for food in foods:
123. windowSurface.blit(foodImage, food)
The food variable, which contains each of the Rect objects in foods on each iteration through the for loop, tells the blit() method where to draw the foodImage.
You’ve added images and sound to your game. The images, called sprites, look much better than the simple drawn shapes used in the previous programs. Sprites can be scaled (that is, stretched) to a larger or smaller size, so we can display sprites at any size we want. The game presented in this chapter also has a background and plays sound effects.
Now that we know how to create a window, display sprites, draw primitives, collect keyboard and mouse input, play sounds, and implement collision detection, we’re ready to create a graphical game in pygame. Chapter 21 brings all of these elements together for our most advanced game yet.