pygame.FULLSCREEN flag
move_ip() Method for Rect objects
The last three chapters have gone over the Pygame
The Dodger game has the player control a small man (which we call the player's character) who must
Just for fun, we will also add some
Let's review some of the basic data types used in the Pygame library:
pygame.Rect - Rect objects represent a rectangular space's location and size. The location can be determined by the Rect object's topleft attribute (or the topright, bottomleft, and bottomright attributes). These corner attributes are a tuple of integers for the X and Y coordinates. The size can be determined by the width and height attributes, which are integers of how many pixels long or high the rectangle area is. Rect objects have a colliderect() method to check if they are intersecting with another Rect object.
pygame.Surface - Surface objects are areas of colored pixels. Surface objects represent a rectangular image, while Rect objects only represent a rectangular space and location. Surface objects have a blit () method that is used to draw the image on one Surface object onto another Surface object. The Surface object returned by the pygame.display.set_mode() function is special because anything drawn on that Surface have things drawn on them, but we cannot see this because it only exists in the computer's memory. We can only see a Surface object when it is "pygame.event.Event - The Event data type in the pygame.event module generates Event objects whenever the user provides keyboard, mouse, or another kind of input. The pygame.event.get() function returns a list of Event objects. You can check what type of event the Event object is by checking its type attribute. QUIT, KEYDOWN, and MOUSEBUTTONUP are examples of some pygame.font.Font - The pygame.font module has the Font data type which represent the Font object by calling the pygame.font.SysFont() constructor function. The arguments to pass are a string of the font name and an integer of the font size, however it is common to pass None for the font name to get the default Font object is pygame.font.SysFont(None, 48).
pygame.time.Clock - The Clock object in the pygame.time module are very helpful for keeping our games from running as fast as possible. (This is often too fast for the player to keep up with the computer, and makes the games not fun.) The Clock object has a tick() method, which we pass how many frames per second (fps) we want the game to run at. The higher the fps, the faster the game runs. Normally we use 40 fps. Notice that the pygame.time module is a different module than the time module which contains the sleep() function.
Type in the following code and save it to a file named dodger.py. This game also requires some other image and sound files which you can download from the URL http://inventwithpython.com/resources.
You can download this code from the URL http://inventwithpython.com/chapter20.
dodger.py
This code can be downloaded from http://inventwithpython.com/dodger.py
If you get errors after typing this code in, compare it to the book's code with the online
diff tool at http://inventwithpython.com/diff or email the author at
al@inventwithpython.com
1. import pygame, random, sys
2. from pygame.locals import *
3.
4. WINDOWWIDTH = 600
5. WINDOWHEIGHT = 600
6. TEXTCOLOR = (255, 255, 255)
7. BACKGROUNDCOLOR = (0, 0, 0)
8. FPS = 40
9. BADDIEMINSIZE = 10
10. BADDIEMAXSIZE = 40
11. BADDIEMINSPEED = 1
12. BADDIEMAXSPEED = 8
13. ADDNEWBADDIERATE = 6
14. PLAYERMOVERATE = 5
15.
16. def terminate():
17. pygame.quit()
18. sys.exit()
19.
20. def waitForPlayerToPressKey():
21. while True:
22. for event in pygame.event.get():
23. if event.type == QUIT:
24. terminate()
25. if event.type == KEYDOWN:
26. if event.key == K_ESCAPE: # pressing escape quits
27. terminate()
28. return
29.
30. def playerHasHitBaddie(playerRect, baddies):
31. for b in baddies:
32. if playerRect.colliderect(b['rect']):
33. return True
34. return False
35.
36. def drawText(text, font, surface, x, y):
37. textobj = font.render(text, 1, TEXTCOLOR)
38. textrect = textobj.get_rect()
39. textrect.topleft = (x, y)
40. surface.blit(textobj, textrect) 41.
42. # set up pygame, the window, and the mouse cursor
43. pygame.init()
44. mainClock = pygame.time.Clock()
45. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
46. pygame.display.set_caption('Dodger')
47. pygame.mouse.set_visible(False) 48.
49. # set up fonts
50. font = pygame.font.SysFont(None, 48)
51.
52. # set up sounds
53. gameOverSound = pygame.mixer.Sound('gameover.wav')
54. pygame.mixer.music.load('background.mid')
55.
56. # set up images
57. playerImage = pygame.image.load('player.png')
58. playerRect = playerImage.get_rect()
59. baddieImage = pygame.image.load('baddie.png')
60.
61. # show the "Start" screen
62. drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
63. drawText('Press a key to start.', font, windowSurface, (WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
64. pygame.display.update()
65. waitForPlayerToPressKey()
66.
67.
68. topScore = 0
69. while True:
70. # set up the start of the game
71. baddies = []
72. score = 0
73. playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT -50)
74. moveLeft = moveRight = moveUp = moveDown = False
75. reverseCheat = slowCheat = False
76. baddieAddCounter = 0
77. pygame.mixer.music.play(-1, 0.0) 78.
79. while True: # the game loop runs while the game part is playing
80. score += 1 # increase score 81.
82. for event in pygame.event.get():
83. if event.type == QUIT:
84. terminate() 85.
86. if event.type == KEYDOWN:
87. if event.key == ord('z'):
88. reverseCheat = True
89. if event.key == ord('x'):
90. slowCheat = True
91. if event.key == K_LEFT or event.key == ord ('a'):
92. moveRight = False
93. moveLeft = True
94. if event.key == K_RIGHT or event.key == ord('d'):
95. moveLeft = False
96. moveRight = True
97. if event.key == K_UP or event.key == ord ('w'):
98. moveDown = False
99. moveUp = True
100. if event.key == K_DOWN or event.key == ord ('s'):
101. moveUp = False
102. moveDown = True
103.
104. if event.type == KEYUP:
105. if event.key == ord('z'):
106. reverseCheat = False
107. score = 0
108. if event.key == ord('x'):
109. slowCheat = False
110. score = 0
111. if event.key == K_ESCAPE:
112. terminate() 113.
114. if event.key == K_LEFT or event.key == ord ('a'):
115. moveLeft = False
116. if event.key == K_RIGHT or event.key == ord('d'):
117. moveRight = False
118. if event.key == K_UP or event.key == ord ('w'):
119. moveUp = False
120. if event.key == K_DOWN or event.key == ord ('s'):
121. moveDown = False
122.
123. if event.type == MOUSEMOTION:
124. # If the mouse moves, move the player
where the cursor is.
125. playerRect.move_ip(event.pos[0] -
playerRect.centerx, event.pos[1] - playerRect.centery)
126.
127. # Add new baddies at the top of the screen, if needed.
128. if not reverseCheat and not slowCheat:
129. baddieAddCounter += 1
130. if baddieAddCounter == ADDNEWBADDIERATE:
131. baddieAddCounter = 0
132. baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)
133. newBaddie = {'rect': pygame.Rect (random.randint(0, WINDOWWIDTH-baddieSize),
0 -baddieSize, baddieSize, baddieSize),
134. 'speed': random.randint (BADDIEMINSPEED, BADDIEMAXSPEED),
135. 'surface':pygame.transform.scale (baddieImage, (baddieSize, baddieSize)),
136. }
137.
138. baddies.append(newBaddie)
139.
140. # Move the player around.
141. if moveLeft and playerRect.left > 0:
142. playerRect.move_ip(-1 * PLAYERMOVERATE, 0)
143. if moveRight and playerRect.right < WINDOWWIDTH:
144. playerRect.move_ip(PLAYERMOVERATE, 0)
145. if moveUp and playerRect.top > 0:
146. playerRect.move_ip(0, -1 * PLAYERMOVERATE)
147. if moveDown and playerRect.bottom < WINDOWHEIGHT:
148. playerRect.move_ip(0, PLAYERMOVERATE) 149.
150. # Move the mouse cursor to match the player.
151. pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)
152.
153. # Move the baddies down.
154. for b in baddies:
155. if not reverseCheat and not slowCheat:
156. b['rect'].move_ip(0, b['speed'])
157. elif reverseCheat:
158. b['rect'].move_ip(0, -5)
159. elif slowCheat:
160. b['rect'].move_ip(0, 1)
161.
162. # Delete baddies that have fallen past the bottom.
163. for b in baddies[:]:
164. if b['rect'].top > WINDOWHEIGHT:
165. baddies.remove(b)
166.
167. # Draw the game world on the window.
168. windowSurface.fill(BACKGROUNDCOLOR)
169.
170. # Draw the score and top score.
171. drawText('Score: %s' % (score), font, windowSurface, 10, 0)
172. drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)
173.
174. # Draw the player's rectangle
175. windowSurface.blit(playerImage, playerRect)
176.
177. # Draw each baddie
178. for b in baddies:
179. windowSurface.blit(b ['surface'] , b ['rect'])
180.
181. pygame.display.update() 182 .
183. # Check if any of the baddies have hit the player.
184. if playerHasHitBaddie(playerRect, baddies):
185. if score > topScore:
186. topScore = score # set new top score
187. break 188 .
189. mainClock.tick(FPS) 190 .
191. # Stop the game and show the "Game Over" screen.
192. pygame.mixer.music.stop()
193. gameOverSound.play() 194 .
195. drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
196. drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)
197. pygame.display.update()
198. waitForPlayerToPressKey()
199.
200. gameOverSound.stop()
When you run this program, the game will look like this:
(рис 20.1) A screenshot of the Dodger game in action.
1. import pygame, random, sys 2. from pygame.locals import *
The Dodger game will import the same modules that our previous Pygame games have: pygame, random, sys, and pygame.locals. The pygame.locals module contains several QUIT, KEYDOWN, etc.) and keyboard keys (K_ESCAPE, K_LEFT, etc.). By using the from pygame.locals import * syntax, we can just type QUIT instead of pygame.locals.QUIT.
There are several windowSurface.fill(BACKGROUNDCOLOR) we know that the argument being sent is a color for the background. However, the line windowSurface.fill (BACKGROUNDCOLOR) is not as clear what the argument being passed means.
We can also easily change some simple aspects about our game without having the change much of the code by changing the values stored in these WINDOWWIDTH on line 4, we automatically change the code everywhere WINDOWWIDTH is used. If we had used the value 600 instead, then we would have to change each 600 in the code. This would be especially confusing because 600 would also be used for the height of the window as well, and we would not want to change those values.
4. WINDOWWIDTH = 600 5. WINDOWHEIGHT = 600 6. TEXTCOLOR = (255, 255, 255) 7. BACKGROUNDCOLOR = (0, 0, 0)
Here we set the height and width of the main window. Since the rest of our code works off of these
Instead of storing color tuples into a variable named WHITE or BLACK, we will use 0 to 255 and stand for red, green, and blue.
8. FPS = 40
Just so the computer does not run the game too fast for the user to handle, we will call mainClock.tick() on each iteration of the game loop to slow it down. We need to pass an integer to mainClock.tick() so that the function knows how long to pause the program. This integer will be the number of frames per second we want the game to run. A "frame" is the drawing of graphics on the screen for a single iteration through the game loop. We will set up a FPS to 40, and always call mainClock.tick (FPS). You can change FPS to a higher value to have the game run faster or a lower value to slow the game down.
9. BADDIEMINSIZE = 10 10. BADDIEMAXSIZE = 40 11. BADDIEMINSPEED = 1 12. BADDIEMAXSPEED = 8 13. ADDNEWBADDIERATE = 6
Here we set some more BADDIEMINSIZE and BADDIEMAXSIZE. The rate at which the baddies fall down the screen will be between BADDIEMINSPEED and BADDIEMAXSPEED pixels per iteration through the game loop. And a new baddie will be added to the top of the window every ADDNEWBADDIERATE iterations through the game loop.
14. PLAYERMOVERATE = 5
The PLAYERMOVERATE will store the number of pixels the player's character moves in the window on each iteration through the game loop (if the character is moving). By increasing this number, you can increase the speed the character moves. If you set PLAYERMOVERATE to 0, then the player's character won't be able to move at all (the player would move 0 pixels per iteration). This wouldn't be a very fun game.
We will create several functions for our game. By putting code into functions, we can avoid having to type the same code several times in our program. And because the code is in one place, if we find a bug the code only needs to be fixed in one place.
16. def terminate(): 17. pygame.quit() 18. sys.exit()
There are several places in our game that we want to terminate the program. In our other programs, this just required a single call to sys.exit(). But since Pygame requires that we call both pygame.quit() and sys.exit(), we will put them into a function called terminate() and just call the function. This keeps us from repeating the same code over and over again. And remember, the more we type, the more likely we will make a
20. def waitForPlayerToPressKey(): 21. while True: 22. for event in pygame.event.get():
There are also a couple places where we want the game to pause and wait for the player to press a key. We will create a new function called waitForPlayerToPressKey() to do this. Inside this function, we have an KEYDOWN or QUIT event is received. At the start of the loop, we call pygame.event.get() to return a list of Event objects to check out.
23. if event.type == QUIT: 24. terminate()
If the player has closed the window while the program is waiting for the player to press a key, Pygame will generate a QUIT event and we should terminate the program. We will call our terminate() function here, rather than call pygame.quit() and sys.exit () themselves.
25. if event.type == KEYDOWN: 26. if event.key == K_ESCAPE: # pressing escape quits 27. terminate() 28. return
If we receive a KEYDOWN event, then we should first check if it is the Esc key that was pressed. If we are waiting for the player to press a key, and the player presses the Esc key, we want to terminate the program. If that wasn't the case, then execution will skip the if-block on line 27 and go straight to the return statement, which exits the waitForPlayerToPressKey() function.
If a QUIT or KEYDOWN event is not generated, then this loop will keep looping until it is. This will
30. def playerHasHitBaddie(playerRect, baddies) : 31. for b in baddies: 32. if playerRect.colliderect(b['rect'] ) : 33. return True 34. return False
We will also define a function named playerHasHitBaddie() which will return True if the player's character has collided with one of the baddies. The baddies parameter is a list of baddie data structures. These data structures are just dictionaries, so it is accurate to say that baddies is a list of dictionary objects. Each of these dictionaries has a 'rect' key, and the value for that key is a Rect object that represents the baddie's size and location.
playerRect is also a Rect object. Remember that Rect objects have a method named colliderect() that returns True if the Rect object has collided with the Rect object that is passed to the method. Otherwise, colliderect() will return False.
We can use this method in our playerHasHitBaddie() function. First we iterate through each baddie data structure in the baddies list. If any of these baddies collide with the player's character, then playerHasHitBaddie() will return True. If the code manages to iterate through all the baddies in the baddies list without colliding with any of them, we will return False.
36. def drawText(text, font, surface, x, y) : 37. textobj = font.render(text, 1, TEXTCOLOR) 38. textrect = textobj.get_rect() 39. textrect.topleft = (x, y) 40. surface.blit(textobj, textrect)
Drawing text on the window involves many different steps. First, we must create a object that has the string rendered in a specific font on it. The render() method does this. Next, we need to know the size and location of the object we just made. We can get a Rect object with this information with the get_rect() method for objects.
This Rect object has no special connection to the object with the text drawn on it, other than the fact that it has a copy of the width and height information from the object. We can change the location of the Rect object by setting a new tuple value for its topleft attribute.
Finally, we object of the rendered text onto the object that was passed to our drawText() function. Displaying text in Pygame take a few more steps than simply calling the print() function, but if we put this code into a single function (drawText()), then we only need to call the function instead of typing out all the code every time we want to display text on the screen.
Now that the
42. # set up pygame, the window, and the mouse cursor
43. pygame.init()
44. mainClock = pygame.time.Clock()
45. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
46. pygame.display.set_caption('Dodger')
47. pygame.mouse.set_visible(False)
Line 43 sets up the Pygame library. Remember, the pygame.init() function must be called before we can use any of Pygame's functions or data types. Line 44 creates a pygame.time.Clock() object and stores it in the mainClock variable. This object will help us keep the program from running too fast.
Line 45 creates a new object which will be used for the window displayed on the screen. We will specify the width and height of this object (and the window) by passing a tuple with the WINDOWWIDTH and WINDOWHEIGHT pygame.display.set_mode(): a tuple. The arguments for pygame.display.set_mode() are not two integers but a tuple of two integers.
On line 46, the caption of the window is set to the string 'Dodger'. This caption will appear in the
In our game, we do not want the False to tell Pygame to make the cursor pygame.mouse.set_visible(True).
The pygame.display.set_mode() function has a second, pygame.FULLSCREEN, like this
45. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), pygame.FULLSCREEN)
Passing pygame.FULLSCREEN will make the program take up the entire space of the screen. It will still be WINDOWWIDTH and WINDOWHEIGHT in size for the windows width and height, but the image will be stretched larger to fit the screen. There may be wasted space along the top and bottom (or the left and right) sides of the screen if you did not set the window size in
If you do not use the fullscreen mode, then you do not need to worry about using a 4:3 ratio for the width and height. Just use whatever width and height works best for your game.
49. # set up fonts 50. font = pygame.font.SysFont(None, 48)
We need to create a Font object to use when we create a object with the image of text drawn on it. (This process is called "rendering".) We want to create a generic font, so we will use the default Font object that the pygame.font.SysFont() constructor function returns. We pass None so that the 48 so that the font has a size of 48 points.
52. # set up sounds
53. gameOverSound = pygame.mixer.Sound('gameover.wav')
54. pygame.mixer.music.load('background.mid')
Next we want to create the Sound objects and also set up the Sound objects will only be played when we specifically want them to. In this case, the Sound object will be played when the player loses the game.
You can use any .wav or .mid file for this game. You can download these sound files from this book's website at the URL http://inventwithpython.com/resources. Or you can use your own sound files for this game, as long as they have the filenames of gameover.wav and background.mid. (Or you can change the strings used on lines 53 and 54 to match the filenames.)
The pygame. constructor function creates a new Sound object and stores a reference to this object in the gameOverSound variable. In your own games, you can create as many Sound objects as you like, each with a different sound file that it will play.
The pygame. function loads a sound file to play for the
56. # set up images
57. playerImage = pygame.image.load('player.png')
58. playerRect = playerImage.get_rect()
59. baddieImage = pygame.image.load('baddie.png')
Next we will load the image files that used for the player's character and the baddies on the screen. The image for the character is stored in player.png and the image for the baddies is stored in baddie.png. All the baddies look the same, so we only need one image file for them. You can download these images from the book's website at the URL http://inventwithpython.com/resources.
When the game first starts, we want to display the name of the game on the screen. We also want to instruct the player that they can start the game by pushing any key. This screen appears so that the player has time to get ready to start playing after running the program. Also, before each game starts, we want to reset the value of the top score back to 0.
61. # show the "Start" screen
62. drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3),
(WINDOWHEIGHT / 3))
63. drawText('Press a key to start.', font, windowSurface,
(WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
64. pygame.display.update()
65. waitForPlayerToPressKey()
On lines 62 and 63, we call our drawText() function and pass it five arguments: 1) the string of the text we want to appear, 2) the font that we want the string to appear in, 3) the object onto which to render the text, and 4) and 5) the X and Y coordinate on the object to draw the text at.
This may seem like many arguments to pass for a function call, but keep in mind that this function call replaces five lines of code each time we call it. This shortens our program and makes it easier to find bugs since there is less code to check.
The waitForPlayerToPressKey() function will pause the game by entering into a loop that checks for any KEYDOWN events. Once a KEYDOWN event is generated, the execution breaks out of the loop and the program continues to run.
68. topScore = 0 69. while True:
We have finished defining the helper functions and variables that we need for this game. Line 68 is the start of the main game code. The value in the topScore variable starts at 0 only when the program first runs. Whenever the player loses and has a score larger than the current top score, the top score is replaced with the player's score.
The while loop will iterate each time the player starts a new game. We will set up the code so that when the player loses and we need to reset the game, the program's execution will go back to the start of this loop.
70 . # set up the start of the game 71. baddies = [] 72 . score = 0
At the very beginning, we want to set the baddies list to an baddies list is a list of dictionary objects with the following keys:
'rect' - The Rect object that describes where and what size the baddie is.
'speed' - How fast the baddie falls down the screen. This integer represents pixels per iteration through the game loop.
'surface ' - The Surface object that has the scaled image of the baddie image drawn on it. This is the Surface object that will be Surface object returned by pygame.display.set_mode() and drawn on the screen.
Next, we want to reset the player's score to 0.
73. playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT -50)
The starting location of the player will be in the center of the screen and 50 pixels up from the bottom. The tuple that we set the topleft attribute to will change the location of the playerRect object. The first item in the tuple is the X-coordinate of the left edge. The second item in the tuple is the Y coordinate of the top edge.
74. moveLeft = moveRight = moveUp = moveDown = False 75. reverseCheat = slowCheat = False 76. baddieAddCounter = 0
Also at the start of the game, we want to have the movement variables moveLeft, moveRight, moveUp, and moveDown set to False. The reverseCheat and slowCheat variables will be set to True only when the player enables these
The baddieAddCounter variable is used for a counter to tell the program when to add a new baddie at the top of the screen. The value in baddieAddCounter will be incremented by one each time the game baddieAddCounter counter is equal to the value in ADDNEWBADDIERATE, then the baddieAddCounter counter is reset back to 0 and a new baddie is added to the top of the screen.
77. pygame.mixer.music.play(-1, 0.0)
At the start of the game, we want the pygame.. The first argument is the number of times the music should repeat itself. -1 is a special value that tells Pygame we want the music to repeat endlessly. The second argument is a float that says how many seconds into the music we want it to start playing. Passing 0.0 means we want to play the music starting from the beginning of the music file. (Passing 2.0, for example, would have started the music two seconds into the music file.)
The game loop contains the code that is executed while the game is being played. The game loop constantly updates the state of the game world by changing the position of the player and baddies, handling events generated by Pygame, and drawing the state of the game world on the screen. All of this happens several dozen times a second, which makes it seem that the game is happening in real time to the player.
79. while True: # the game loop runs while the game part is playing 80. score += 1 # increase score
Line 79 is the start of the main game loop. In the main game loop, we will increase the player's score, handle any events that were generated, add any baddies to the top of the screen if needed, move the baddies down a little, and then draw everything on the screen. This code will be executed over and over again as the program execution iterates through the game loop. The loop will only exit when the player either loses the game or quits the program.
First, we will increment the player's score. The longer the player can go without losing, the higher their score will be.
There are four different types of events we will handle in our game: QUIT, KEYDOWN, KEYUP, and MOUSEMOTION. The QUIT event is generated by Pygame if the player closes the program's window or shuts down the computer. In that case, we want the program to close itself. The KEYDOWN and KEYUP events are generated when the player pushes down and releases the keyboard keys, respectively. These events will be how we can tell which direction the player wants to move the character. The player could also have pressed the Esc key to signal that they want to shut down the program. Each time the player moves the mouse, Pygame will generate a MOUSEMOTION event which will tell us the X and Y coordinates of the
82. for event in pygame.event.get(): 83. if event.type == QUIT: 84. terminate()
Line 82 is the start of the event-handling code. First we call pygame.event.get(), which returns a list of Event objects. Each Event object represents an event that has been created since the last call to pygame.event.get(). We will check the type attribute of the event object to see what type of event it is, and handle the event accordingly.
If the type attribute of the Event object is equal to QUIT, then this tells us that the user has closed the program somehow. The QUIT pygame.locals module, but since we imported that module with the line from pygame.locals import * instead of simply import pygame.locals, we only need to type QUIT and not pygame.locals.QUIT.
86. if event.type == KEYDOWN:
87. if event.key == ord('z') :
88. reverseCheat = True
89. if event.key == ord('x') :
90. slowCheat = True
If the event's type is KEYDOWN, then we know that the player has pressed down a key. The Event object for keyboard events will also have a key attribute that is set to the numeric ASCII value of the key pressed. The ord() function will return the ASCII value of the letter passed to it.
For example, on line 87, we can check if the event describes the "z" key being pressed down by checking if event.key == ord('z'). If this condition is True, then we want to set the reverseCheat variable to True to indicate that the reverse
Pygame's keyboard events always use the ASCII values of event.key == ord('z') instead of event.key == ord('Z') . Otherwise, your program may act as though the key hasn't been pressed at all.
91. if event.key == K_LEFT or event.key == ord('a'):
92. moveRight = False
93. moveLeft = True
94. if event.key == K_RIGHT or event.key == ord('d'):
95. moveLeft = False
96. moveRight = True
97. if event.key == K_UP or event.key == ord ( ' w' ) :
98. moveDown = False
99. moveUp = True
100. if event.key == K_DOWN or event.key == ord('s ' ) :
101. moveUp = False
102. moveDown = True
We also want to check if the event was generated by the player pressing one of the arrow keys. There is not an ASCII value for every key on the keyboard, such as the arrow keys or the Esc key. Instead, Pygame provides some
We can check if the player has pressed the event.key == K_LEFT. Again, the reason we can use K_LEFT instead of pygame.locals.K_LEFT is because we imported pygame.locals with the line from pygame.locals import * instead of import pygame.locals.
Noticed that pressing down on one of the arrow keys not only sets one of the movement variables to True, but it also sets the movement variable in the opposite direction to False. For example, if the moveLeft to True, but it also sets moveRight to False. This prevents the player from confusing the program into thinking that the player's character should move in two opposite directions at the same time.
Here is a list of commonly-used
| Pygame | Keyboard Key | Pygame | Keyboard Key |
|---|---|---|---|
K_LEFT
| K_HOME
| Home | |
K_RIGHT
| Right arrow | K_END
| End |
K_UP
| Up arrow | K_PAGEUP
| PgUp |
K_DOWN
| Down arrow | K_PAGEDOWN
| PgDn |
K_ESCAPE
| Esc | K_F1
| F1 |
K_BACKSPACE
| Backspace | K_F2
| F2 |
K_TAB
| Tab | K_F3
| F3 |
K_RETURN
| Return or Enter | K_F4
| F4 |
K_SPACE
| Space bar | K_F5
| F5 |
K_DELETE
| Del | K_F6
| F6 |
K_LSHIFT
| Left Shift | K_F7
| F7 |
K_RSHIFT
| Right Shift | K_F8
| F8 |
K_LCTRL
| Left Ctrl | K_F9
| F9 |
K_RCTRL
| Right Ctrl | K_F10
| F10 |
K_LALT
| Left Alt | K_F11
| F11 |
K_RALT
| Right Alt | K_F12
| F12 |
104. if event.type == KEYUP:
105. if event.key == ord('z') :
106. reverseCheat = False
107. score = 0
108. if event.key == ord('x'):
109. slowCheat = False
110. score = 0
The KEYUP event is created whenever the player stops pressing down on a keyboard key and it returns to its normal, up position. KEYUP objects with a type of KEYUP also have a key attribute just like KEYDOWN events.
On line 105, we check if the player has released the "z" key, which will deactivate the reverse reverseCheat to False and reset the score to 0. The score reset is to discourage the player for using the
Lines 108 to 110 do the same thing for the "x" key and the slow slowCheat is set to False and the player's score is reset to 0.
111. if event.key == K_ESCAPE: 112. terminate()
At any time during the game, the player can press the Esc key on the keyboard to quit the game. Here we check if the key that was released was the Esc key by checking event.key == K_ESCAPE. If so, we call our terminate() function which will exit the program.
114. if event.key == K_LEFT or event.key == ord (' a ' ) :
115. moveLeft = False
116. if event.key == K_RIGHT or event.key == ord('d'):
117. moveRight = False
118. if event.key == K_UP or event.key == ord ('w'):
119. moveUp = False
120. if event.key == K_DOWN or event.key == ord
(' s ' ) :
121. moveDown = False
Lines 114 to 121 check if the player has stopped holding down one of the arrow keys (or the corresponding WASD key). In that event, we will set the corresponding movement variable to False. For example, if the player was holding down the moveLeft would have been set to True on line 93. When they release it, the condition on line 114 will evaluate to True, and the moveLeft variable will be set to False.
123. if event.type == MOUSEMOTION: 124. # If the mouse moves, move the player where the cursor is. 125. playerRect.move_ip(event.pos[0] - playerRect.centerx, event.pos[1] - playerRect.centery)
Now that we have handled the keyboard events, let's handle any mouse events that may have been generated. In the Dodger game we don't do anything if the player has clicked a
If the event's type is MOUSEMOTION, then we want to move the player's character to the location of the MOUSEMOTION event is generated whenever the mouse is moved. Event objects with a type of MOUSEMOTION also have an attribute named pos. The pos attribute stores a tuple of the X and Y coordinates of where the
The move_ip() method for Rect objects will move the location of the Rect object horizontally or vertically by a number of pixels. For example, playerRect.move_ip (10, 20) would move the Rect object 10 pixels to the right and 20 pixels down. To move the Rect object left or up, pass negative values. For example, playerRect.move_ip(-5, -15) will move the Rect object left by 5 pixels and up 15 pixels.
The "ip" at the end of move_ip() stands for "in place". This is because the method changes the Rect object itself, in its own place. There is also a move() method which does not change the Rect object, but instead creates a new Rect object that has the new location. This is useful if you want to keep the original Rect object's location the same but also have a Rect object with the new location.
127. # Add new baddies at the top of the screen, if needed. 128. if not reverseCheat and not slowCheat: 129. baddieAddCounter += 1
On each iteration of the game loop, we want to increment the baddieAddCounter variable by one. However, we only want to do this if the reverseCheat and slowCheat: are only set to True as long as the "z" and "x" keys are being held down, respectively. And while those keys are being held down, baddieAddCounter is not incremented. This means that no new baddies will appear at the top of the screen.
130. if baddieAddCounter == ADDNEWBADDIERATE:
131. baddieAddCounter = 0
132. baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)
133. newBaddie = {'rect': pygame.Rect (random.randint(0, WINDOWWIDTH-baddieSize), 0 -
baddieSize, baddieSize, baddieSize),
134. 'speed': random.randint (BADDIEMINSPEED, BADDIEMAXSPEED),
135. 'surface':pygame.transform.scale (baddieImage, (baddieSize, baddieSize)),
136. }
When the baddieAddCounter reaches the value in ADDNEWBADDIERATE, then the condition on line 130 is True and it is time to add a new baddie to the top of the screen. First, the baddieAddCounter counter is reset back to 0 (otherwise, when it keeps incrementing it will always be greater than ADDNEWBADDIERATE and never equal to it. This will cause baddies to stop appearing at the top of the screen.)
Line 132 generates a size for the baddie in pixels. The size will be between BADDIEMINSIZE and BADDIEMAXSIZE, which we have set to 10 and 40 in this program.
Line 133 is where a new baddie data structure is created. Remember, the data structure for baddies is simply a dictionary with keys 'rect', 'speed', and '. The 'rect' key holds a reference to a Rect object which stores the location and size of the baddie. The call to the pygame.Rect() constructor function has four parameters: the X-coordinate of the top edge of the area, the Y coordinate of the left edge of the area, the width in pixels, and the height in pixels.
We want the baddie to appear randomly across the top of the window, so we pass random.randint(0, WINDOWWIDTH-baddieSize) for the X-coordinate of the left edge. This will evaluate to a random place across the top of the window. The reason we pass WINDOWWIDTH-baddieSize instead of WINDOWWIDTH is because this value is for the left edge of the baddie. If the left edge of the baddie is too far on the right side of the screen, then part of the baddie will be off the edge of the window and not visible.
We want the bottom edge of the baddie to be just above the top edge of the window. The Y-coordinate of the top edge of the window is 0, so to put the baddie's bottom edge there, we want to set the top edge to 0 - baddieSize.
The baddie's width and height should be the same (the image is a square), so we will pass baddieSize for the third and fourth argument.
The rate of speed that the baddie moves down the screen will be set in the 'speed' key, and is set to a random integer between BADDIEMINSPEED and BADDIEMAXSPEED.
138. baddies.append(newBaddie)
Line 138 will add the newly created baddie data structure to the list of baddie data structures. Our program will use this list to check if the player has collided with any of the baddies and to know where to draw baddies on the window.
140. # Move the player around. 141. if moveLeft and playerRect.left > 0: 142. playerRect.move_ip(-1 * PLAYERMOVERATE, 0)
The four movement variables moveLeft, moveRight, moveUp and moveDown are set to True and False when Pygame generates the KEYDOWN and KEYUP events, respectively. (This code is from line 86 to line 121.)
If the player's character is moving left and the left edge of the player's character is greater than 0 (which is the left edge of the window), then we want to move the character's Rect object (stored in playerRect).
We will always move the playerRect object by the number of pixels in PLAYERMOVERATE. To get the negative form of an integer, you can simply multiple it by -1. So on line 142, since 5 is stored in PLAYERMOVERATE, the expression -1 * PLAYERMOVERATE evaluates to -5.
This means that calling playerRect.move_ip(-1 * PLAYERMOVERATE, 0) will change the location of playerRect by 5 pixels to the left of its current location.
143. if moveRight and playerRect.right < WINDOWWIDTH: 144. playerRect.move_ip(PLAYERMOVERATE, 0) 145. if moveUp and playerRect.top > 0: 146. playerRect.move_ip(0, -1 * PLAYERMOVERATE) 147. if moveDown and playerRect.bottom < WINDOWHEIGHT: 148. playerRect.move_ip(0, PLAYERMOVERATE)
We want to do the same thing for the other three directions: right, up, and down. Each of the three if statements in lines 143 to 148 checks that their movement variable is set to True and that the edge of the Rect object of the player is inside the window before calling the move_ip() method to move the Rect object.
150. # Move the mouse cursor to match the player. 151. pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)
Line 151 moves the pygame.mouse.set_pos() function moves the Rect object because we pass the centerx and centery attributes of playerRect for the coordinates. The pygame.mouse.set_visible(False) on line 47.
The reason we want the
153. # Move the baddies down. 154. for b in baddies:
Now we want to loop through each baddie data structure in the baddies list to move them down a little.
155. if not reverseCheat and not slowCheat: 156. b['rect'].move_ip(0, b['speed'])
If neither of the reverseCheat or slowCheat to True, respectively), then move the baddie's location down a number of pixels equal to its speed, which is stored in the 'speed' key.
157. elif reverseCheat: 158. b ['rect'] .move_ip(0, -5)
If the reverse -5 for the second argument to move_ip() will move the Rect object
159. elif slowCheat: 160. b['rect'].move_ip(0, 1)
If the slow 'speed' key of the baddie's data structure) will be ignored while the slow
162. # Delete baddies that have fallen past the bottom. 163. for b in baddies [:] :
After moving the baddies down the window, we want to remove any baddies that fell below the bottom edge of the window from the baddies list. Remember that we while we are iterating through a list, we should not modify the contents of the list by adding or removing items. So instead of iterating through the baddies list with our baddies loop we will iterate through a copy of the baddies list.
Remember that a list slice will evaluate a copy of a list's items. For example, spam [2:4] will return a new list with the items from index 2 up to (but not including) index 4. Leaving the first index blank will indicate that index 0 should be used. For example, spam [:4] will return a list with items from the start of the list up to (but not including) the item at index 4. Leaving the second index blank will indicate that up to (and including) the last index should be used. For example, spam[2:] will return a list with items from index 2 all the way to (and including) the last item in the list.
But leaving both indexes in the slice blank is a way to represent the entire list. The baddies[:] expression is a list slice of the whole list, so it evaluates to a copy of the entire list. This is useful because while we are iterating on the copy of the list, we can modify the original list and remove any baddie data structures that have fallen past the bottom edge of the window.
Our for loop on line 163 uses a variable b for the current item in the iteration through baddies[:].
164. if b [ 'rect'] .top > WINDOWHEIGHT: 165. baddies.remove(b)
Let's evaluate the expression b['rect'].top. b is the current baddie data structure from the baddies[:] list. Each baddie data structure in the list is a dictionary with a 'rect' key, which stores a Rect object. So b['rect'] is the Rect object for the baddie. Finally, the top is the Y-coordinate of the top edge of the rectangular area. Remember that in the b['rect'].top > WINDOWHEIGHT will check if the top edge of the baddie is below the bottom of the window.
If this condition is True, then the we will remove the baddie data structure from the baddies list.
It isn't enough that our game updates the state of the game world in its memory. Our program will also have to display the game world to the player. We can do this by drawing the graphics of the baddies and player's character on the screen. Because the game loop is executed several times a second, drawing the baddies and player in new positions makes their movement look smooth and natural. But every element on the screen must be drawn one at a time by calling the
167. # Draw the game world on the window. 168. windowSurface.fill(BACKGROUNDCOLOR)
Now that we have updated all the data structures for the baddies and the player's character, let's draw everything on the screen. First, before we draw anything else on the windowSurface, we want to black out the entire screen to erase anything drawn on it in a previous iteration through the game loop.
Remember that the object in windowSurface is the special object because it was the one returned by pygame.display.set_mode(). This mean that anything drawn on that object will appear on the screen, but only after the pygame.display.update() function is called.
170. # Draw the score and top score.
171. drawText('Score: %s' % (score), font, windowSurface, 10, 0)
172. drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)
Next we will render the text for score and top score to the top left corner of the window. The 'Score: %s' % (score) uses string 'Score: ' + str(score). We pas this string, the Font object stored in the font variable, the object on which to draw the text on, and the X and Y coordinates of where the text should be placed. Remember that our drawText() will handle the call to the render() and methods.
For the top score, we do the exact same thing. We pass 40 for the Y-coordinate instead of 0 (like we do for the score) so that the top score text appears beneath the score text.
174. # Draw the player's rectangle 175. windowSurface.blit(playerImage, playerRect)
Remember that the information about the player is kept in two different variables. playerImage is a object that contains all the colored pixels that make up the player's character's image. playerRect is a Rect object that stores the information about the size and location of the player's character.
We call the method on windowSurface and pass playerImage and playerRect. This draws the player character's image on windowSurface at the
177. # Draw each baddie 178. for b in baddies: 179. windowSurface.blit(b['surface'], b['rect'])
We use a for loop here to draw every baddie on the windowSurface object. Remember that each item in the baddies list is a dictionary with ' and 'rect' keys containing the object with the baddie image and the Rect object with the position and size information, respectively.
181. pygame.display.update()
Now that we have finished drawing everything to the windowSurface object, we should draw this pygame.display.update().
183. # Check if any of the baddies have hit the player. 184. if playerHasHitBaddie(playerRect, baddies): 185. if score > topScore: 186. topScore = score # set new top score 187. break
Now let's check if the player has collided with any of the baddies. We already wrote a function to check for this: playerHasHitBaddie(). This function will return True if the player's character has collided with any of the baddies in the baddies list. Otherwise, the function will return False.
If the player's character has hit a baddie, then we check if the player's current score is greater than the top score. If it is, we set the new top score to be the player's current score. Either way, we break out of the game loop. The program's execution will jump down to line 191.
189. mainClock.tick(FPS)
To keep the computer from running through the game loop as fast as possible (which would be much too fast for the player to keep up with), we call mainClock.tick() to pause for a brief amount of time. The pause will be long enough to ensure that about 40 (the value we stored inside the FPS variable) iterations through the game loop occur each second.
191. # Stop the game and show the "Game Over" screen. 192. pygame.mixer.music.stop() 193. gameOverSound.play()
When the player loses, we want to stop playing the stop() function in the pygame. module to stop the play() method on the Sound object stored in gameOverSound.
195. drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
196. drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)
197. pygame.display.update()
198. waitForPlayerToPressKey()
Now we want to display text on the window to tell the player that the game is over, and they should press a key to start playing a new game. The two calls to our drawText() function will draw this text to the windowSurface object, and the call to pygame.display.update() will draw this object to the screen.
After displaying this text, we want the game to stop until the player presses a key, so we call our waitForPlayerToPressKey() function.
200. gameOverSound.stop()
After the player presses a key, the program execution will return from the waitForPlayerToPressKey() call on line 198. Depending on how long the player takes to press a key, the "game over" sound effect may or may not still be playing. We want to stop this sound effect before this loop ends and we start a new game, so we have a call to gameOverSound.stop() here.
That's it for our graphical game. You may find that the game is too easy or too hard. But the game is very easy to modify because we took the time to use
For example, if you want the game to run slower in general, change the FPS variable on line 8 to a smaller value such as 20. This will make both the baddies and the player's character move slower since the game loop will only be executed 20 times a second instead of 40.
If you just want to slow down the baddies and not the player, then change BADDIEMAXSPEED to a smaller value such as 4. This will make all the baddies move between 1 (the value in BADDIEMINSPEED) and 4 pixels per iteration through the game loop instead of 1 and 8.
If you want the game to have fewer but larger baddies instead of many fast baddies, then increase ADDNEWBADDIERATE to 12, BADDIEMINSIZE to 40, and BADDIEMAXSIZE to 80. Now that baddies are being added every 12 iterations through the game loop instead of every 6 iterations, there will be half as many baddies as before. But to keep the game interesting, the baddies are now much larger than before.
While the basic game remains the same, you can modify any of the
Unlike our previous text-based games, Dodger really looks like the kind of modern computer game we usually play. It has graphics and music and uses the mouse. While Pygame provides functions and data types as building blocks, it is you the programmer who puts them together to create fun, interactive games.
And it is all because you know exactly how to instruct the computer to do it, step by step, line by line. You can speak the computer's language, and get it to do large amounts of number crunching and drawing for you. This is a very useful skill, and I hope you will continue to learn more about Python programming. (And there is still more to learn!)
Here are several websites that can teach you more about programming Python:
| http://www.python.org/doc/ | More Python tutorials and the documentation of all the Python modules and functions. |
| http://www.pygame.org/docs/ | Complete documentation on the modules and functions for Pygame. |
| http://inventwithpython.com | This book's website, which includes all the source code for these programs and additional information. This site also has the image and sound files used in the Pygame programs. |
| http://inventwithpython.com/traces | A web application that helps you trace through the execution of the programs in this book, step by step. |
| http://inventwithpython.com/videos | Videos that accompany the programs in this book. |
| http://gamedevlessons.com | A helpful website about how to design and program video games. |
| al@inventwithpython.com | The author's email address. Feel free to email Al your questions about this book or about Python programming. |
Or you can find out more about Python by searching the World Wide Web. Go to the search engine website http://google.com and search for "Python programming" or "Python tutorials" to find web sites that can teach you more about Python programming.
Now get going and invent your own games. And good luck!
pygame.FULLSCREEN flag
move_ip() Method for Rect objects
The last three chapters have gone over the Pygame
The Dodger game has the player control a small man (which we call the player's character) who must
Just for fun, we will also add some
Let's review some of the basic data types used in the Pygame library:
pygame.Rect - Rect objects represent a rectangular space's location and size. The location can be determined by the Rect object's topleft attribute (or the topright, bottomleft, and bottomright attributes). These corner attributes are a tuple of integers for the X and Y coordinates. The size can be determined by the width and height attributes, which are integers of how many pixels long or high the rectangle area is. Rect objects have a colliderect() method to check if they are intersecting with another Rect object.
pygame.Surface - Surface objects are areas of colored pixels. Surface objects represent a rectangular image, while Rect objects only represent a rectangular space and location. Surface objects have a blit () method that is used to draw the image on one Surface object onto another Surface object. The Surface object returned by the pygame.display.set_mode() function is special because anything drawn on that Surface have things drawn on them, but we cannot see this because it only exists in the computer's memory. We can only see a Surface object when it is "pygame.event.Event - The Event data type in the pygame.event module generates Event objects whenever the user provides keyboard, mouse, or another kind of input. The pygame.event.get() function returns a list of Event objects. You can check what type of event the Event object is by checking its type attribute. QUIT, KEYDOWN, and MOUSEBUTTONUP are examples of some pygame.font.Font - The pygame.font module has the Font data type which represent the Font object by calling the pygame.font.SysFont() constructor function. The arguments to pass are a string of the font name and an integer of the font size, however it is common to pass None for the font name to get the default Font object is pygame.font.SysFont(None, 48).
pygame.time.Clock - The Clock object in the pygame.time module are very helpful for keeping our games from running as fast as possible. (This is often too fast for the player to keep up with the computer, and makes the games not fun.) The Clock object has a tick() method, which we pass how many frames per second (fps) we want the game to run at. The higher the fps, the faster the game runs. Normally we use 40 fps. Notice that the pygame.time module is a different module than the time module which contains the sleep() function.
Type in the following code and save it to a file named dodger.py. This game also requires some other image and sound files which you can download from the URL http://inventwithpython.com/resources.
You can download this code from the URL http://inventwithpython.com/chapter20.
dodger.py
This code can be downloaded from http://inventwithpython.com/dodger.py
If you get errors after typing this code in, compare it to the book's code with the online
diff tool at http://inventwithpython.com/diff or email the author at
al@inventwithpython.com
1. import pygame, random, sys
2. from pygame.locals import *
3.
4. WINDOWWIDTH = 600
5. WINDOWHEIGHT = 600
6. TEXTCOLOR = (255, 255, 255)
7. BACKGROUNDCOLOR = (0, 0, 0)
8. FPS = 40
9. BADDIEMINSIZE = 10
10. BADDIEMAXSIZE = 40
11. BADDIEMINSPEED = 1
12. BADDIEMAXSPEED = 8
13. ADDNEWBADDIERATE = 6
14. PLAYERMOVERATE = 5
15.
16. def terminate():
17. pygame.quit()
18. sys.exit()
19.
20. def waitForPlayerToPressKey():
21. while True:
22. for event in pygame.event.get():
23. if event.type == QUIT:
24. terminate()
25. if event.type == KEYDOWN:
26. if event.key == K_ESCAPE: # pressing escape quits
27. terminate()
28. return
29.
30. def playerHasHitBaddie(playerRect, baddies):
31. for b in baddies:
32. if playerRect.colliderect(b['rect']):
33. return True
34. return False
35.
36. def drawText(text, font, surface, x, y):
37. textobj = font.render(text, 1, TEXTCOLOR)
38. textrect = textobj.get_rect()
39. textrect.topleft = (x, y)
40. surface.blit(textobj, textrect) 41.
42. # set up pygame, the window, and the mouse cursor
43. pygame.init()
44. mainClock = pygame.time.Clock()
45. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
46. pygame.display.set_caption('Dodger')
47. pygame.mouse.set_visible(False) 48.
49. # set up fonts
50. font = pygame.font.SysFont(None, 48)
51.
52. # set up sounds
53. gameOverSound = pygame.mixer.Sound('gameover.wav')
54. pygame.mixer.music.load('background.mid')
55.
56. # set up images
57. playerImage = pygame.image.load('player.png')
58. playerRect = playerImage.get_rect()
59. baddieImage = pygame.image.load('baddie.png')
60.
61. # show the "Start" screen
62. drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
63. drawText('Press a key to start.', font, windowSurface, (WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
64. pygame.display.update()
65. waitForPlayerToPressKey()
66.
67.
68. topScore = 0
69. while True:
70. # set up the start of the game
71. baddies = []
72. score = 0
73. playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT -50)
74. moveLeft = moveRight = moveUp = moveDown = False
75. reverseCheat = slowCheat = False
76. baddieAddCounter = 0
77. pygame.mixer.music.play(-1, 0.0) 78.
79. while True: # the game loop runs while the game part is playing
80. score += 1 # increase score 81.
82. for event in pygame.event.get():
83. if event.type == QUIT:
84. terminate() 85.
86. if event.type == KEYDOWN:
87. if event.key == ord('z'):
88. reverseCheat = True
89. if event.key == ord('x'):
90. slowCheat = True
91. if event.key == K_LEFT or event.key == ord ('a'):
92. moveRight = False
93. moveLeft = True
94. if event.key == K_RIGHT or event.key == ord('d'):
95. moveLeft = False
96. moveRight = True
97. if event.key == K_UP or event.key == ord ('w'):
98. moveDown = False
99. moveUp = True
100. if event.key == K_DOWN or event.key == ord ('s'):
101. moveUp = False
102. moveDown = True
103.
104. if event.type == KEYUP:
105. if event.key == ord('z'):
106. reverseCheat = False
107. score = 0
108. if event.key == ord('x'):
109. slowCheat = False
110. score = 0
111. if event.key == K_ESCAPE:
112. terminate() 113.
114. if event.key == K_LEFT or event.key == ord ('a'):
115. moveLeft = False
116. if event.key == K_RIGHT or event.key == ord('d'):
117. moveRight = False
118. if event.key == K_UP or event.key == ord ('w'):
119. moveUp = False
120. if event.key == K_DOWN or event.key == ord ('s'):
121. moveDown = False
122.
123. if event.type == MOUSEMOTION:
124. # If the mouse moves, move the player
where the cursor is.
125. playerRect.move_ip(event.pos[0] -
playerRect.centerx, event.pos[1] - playerRect.centery)
126.
127. # Add new baddies at the top of the screen, if needed.
128. if not reverseCheat and not slowCheat:
129. baddieAddCounter += 1
130. if baddieAddCounter == ADDNEWBADDIERATE:
131. baddieAddCounter = 0
132. baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)
133. newBaddie = {'rect': pygame.Rect (random.randint(0, WINDOWWIDTH-baddieSize),
0 -baddieSize, baddieSize, baddieSize),
134. 'speed': random.randint (BADDIEMINSPEED, BADDIEMAXSPEED),
135. 'surface':pygame.transform.scale (baddieImage, (baddieSize, baddieSize)),
136. }
137.
138. baddies.append(newBaddie)
139.
140. # Move the player around.
141. if moveLeft and playerRect.left > 0:
142. playerRect.move_ip(-1 * PLAYERMOVERATE, 0)
143. if moveRight and playerRect.right < WINDOWWIDTH:
144. playerRect.move_ip(PLAYERMOVERATE, 0)
145. if moveUp and playerRect.top > 0:
146. playerRect.move_ip(0, -1 * PLAYERMOVERATE)
147. if moveDown and playerRect.bottom < WINDOWHEIGHT:
148. playerRect.move_ip(0, PLAYERMOVERATE) 149.
150. # Move the mouse cursor to match the player.
151. pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)
152.
153. # Move the baddies down.
154. for b in baddies:
155. if not reverseCheat and not slowCheat:
156. b['rect'].move_ip(0, b['speed'])
157. elif reverseCheat:
158. b['rect'].move_ip(0, -5)
159. elif slowCheat:
160. b['rect'].move_ip(0, 1)
161.
162. # Delete baddies that have fallen past the bottom.
163. for b in baddies[:]:
164. if b['rect'].top > WINDOWHEIGHT:
165. baddies.remove(b)
166.
167. # Draw the game world on the window.
168. windowSurface.fill(BACKGROUNDCOLOR)
169.
170. # Draw the score and top score.
171. drawText('Score: %s' % (score), font, windowSurface, 10, 0)
172. drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)
173.
174. # Draw the player's rectangle
175. windowSurface.blit(playerImage, playerRect)
176.
177. # Draw each baddie
178. for b in baddies:
179. windowSurface.blit(b ['surface'] , b ['rect'])
180.
181. pygame.display.update() 182 .
183. # Check if any of the baddies have hit the player.
184. if playerHasHitBaddie(playerRect, baddies):
185. if score > topScore:
186. topScore = score # set new top score
187. break 188 .
189. mainClock.tick(FPS) 190 .
191. # Stop the game and show the "Game Over" screen.
192. pygame.mixer.music.stop()
193. gameOverSound.play() 194 .
195. drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
196. drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)
197. pygame.display.update()
198. waitForPlayerToPressKey()
199.
200. gameOverSound.stop()
When you run this program, the game will look like this:
(рис 20.1) A screenshot of the Dodger game in action.
1. import pygame, random, sys 2. from pygame.locals import *
The Dodger game will import the same modules that our previous Pygame games have: pygame, random, sys, and pygame.locals. The pygame.locals module contains several QUIT, KEYDOWN, etc.) and keyboard keys (K_ESCAPE, K_LEFT, etc.). By using the from pygame.locals import * syntax, we can just type QUIT instead of pygame.locals.QUIT.
There are several windowSurface.fill(BACKGROUNDCOLOR) we know that the argument being sent is a color for the background. However, the line windowSurface.fill (BACKGROUNDCOLOR) is not as clear what the argument being passed means.
We can also easily change some simple aspects about our game without having the change much of the code by changing the values stored in these WINDOWWIDTH on line 4, we automatically change the code everywhere WINDOWWIDTH is used. If we had used the value 600 instead, then we would have to change each 600 in the code. This would be especially confusing because 600 would also be used for the height of the window as well, and we would not want to change those values.
4. WINDOWWIDTH = 600 5. WINDOWHEIGHT = 600 6. TEXTCOLOR = (255, 255, 255) 7. BACKGROUNDCOLOR = (0, 0, 0)
Here we set the height and width of the main window. Since the rest of our code works off of these
Instead of storing color tuples into a variable named WHITE or BLACK, we will use 0 to 255 and stand for red, green, and blue.
8. FPS = 40
Just so the computer does not run the game too fast for the user to handle, we will call mainClock.tick() on each iteration of the game loop to slow it down. We need to pass an integer to mainClock.tick() so that the function knows how long to pause the program. This integer will be the number of frames per second we want the game to run. A "frame" is the drawing of graphics on the screen for a single iteration through the game loop. We will set up a FPS to 40, and always call mainClock.tick (FPS). You can change FPS to a higher value to have the game run faster or a lower value to slow the game down.
9. BADDIEMINSIZE = 10 10. BADDIEMAXSIZE = 40 11. BADDIEMINSPEED = 1 12. BADDIEMAXSPEED = 8 13. ADDNEWBADDIERATE = 6
Here we set some more BADDIEMINSIZE and BADDIEMAXSIZE. The rate at which the baddies fall down the screen will be between BADDIEMINSPEED and BADDIEMAXSPEED pixels per iteration through the game loop. And a new baddie will be added to the top of the window every ADDNEWBADDIERATE iterations through the game loop.
14. PLAYERMOVERATE = 5
The PLAYERMOVERATE will store the number of pixels the player's character moves in the window on each iteration through the game loop (if the character is moving). By increasing this number, you can increase the speed the character moves. If you set PLAYERMOVERATE to 0, then the player's character won't be able to move at all (the player would move 0 pixels per iteration). This wouldn't be a very fun game.
We will create several functions for our game. By putting code into functions, we can avoid having to type the same code several times in our program. And because the code is in one place, if we find a bug the code only needs to be fixed in one place.
16. def terminate(): 17. pygame.quit() 18. sys.exit()
There are several places in our game that we want to terminate the program. In our other programs, this just required a single call to sys.exit(). But since Pygame requires that we call both pygame.quit() and sys.exit(), we will put them into a function called terminate() and just call the function. This keeps us from repeating the same code over and over again. And remember, the more we type, the more likely we will make a
20. def waitForPlayerToPressKey(): 21. while True: 22. for event in pygame.event.get():
There are also a couple places where we want the game to pause and wait for the player to press a key. We will create a new function called waitForPlayerToPressKey() to do this. Inside this function, we have an KEYDOWN or QUIT event is received. At the start of the loop, we call pygame.event.get() to return a list of Event objects to check out.
23. if event.type == QUIT: 24. terminate()
If the player has closed the window while the program is waiting for the player to press a key, Pygame will generate a QUIT event and we should terminate the program. We will call our terminate() function here, rather than call pygame.quit() and sys.exit () themselves.
25. if event.type == KEYDOWN: 26. if event.key == K_ESCAPE: # pressing escape quits 27. terminate() 28. return
If we receive a KEYDOWN event, then we should first check if it is the Esc key that was pressed. If we are waiting for the player to press a key, and the player presses the Esc key, we want to terminate the program. If that wasn't the case, then execution will skip the if-block on line 27 and go straight to the return statement, which exits the waitForPlayerToPressKey() function.
If a QUIT or KEYDOWN event is not generated, then this loop will keep looping until it is. This will
30. def playerHasHitBaddie(playerRect, baddies) : 31. for b in baddies: 32. if playerRect.colliderect(b['rect'] ) : 33. return True 34. return False
We will also define a function named playerHasHitBaddie() which will return True if the player's character has collided with one of the baddies. The baddies parameter is a list of baddie data structures. These data structures are just dictionaries, so it is accurate to say that baddies is a list of dictionary objects. Each of these dictionaries has a 'rect' key, and the value for that key is a Rect object that represents the baddie's size and location.
playerRect is also a Rect object. Remember that Rect objects have a method named colliderect() that returns True if the Rect object has collided with the Rect object that is passed to the method. Otherwise, colliderect() will return False.
We can use this method in our playerHasHitBaddie() function. First we iterate through each baddie data structure in the baddies list. If any of these baddies collide with the player's character, then playerHasHitBaddie() will return True. If the code manages to iterate through all the baddies in the baddies list without colliding with any of them, we will return False.
36. def drawText(text, font, surface, x, y) : 37. textobj = font.render(text, 1, TEXTCOLOR) 38. textrect = textobj.get_rect() 39. textrect.topleft = (x, y) 40. surface.blit(textobj, textrect)
Drawing text on the window involves many different steps. First, we must create a object that has the string rendered in a specific font on it. The render() method does this. Next, we need to know the size and location of the object we just made. We can get a Rect object with this information with the get_rect() method for objects.
This Rect object has no special connection to the object with the text drawn on it, other than the fact that it has a copy of the width and height information from the object. We can change the location of the Rect object by setting a new tuple value for its topleft attribute.
Finally, we object of the rendered text onto the object that was passed to our drawText() function. Displaying text in Pygame take a few more steps than simply calling the print() function, but if we put this code into a single function (drawText()), then we only need to call the function instead of typing out all the code every time we want to display text on the screen.
Now that the
42. # set up pygame, the window, and the mouse cursor
43. pygame.init()
44. mainClock = pygame.time.Clock()
45. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
46. pygame.display.set_caption('Dodger')
47. pygame.mouse.set_visible(False)
Line 43 sets up the Pygame library. Remember, the pygame.init() function must be called before we can use any of Pygame's functions or data types. Line 44 creates a pygame.time.Clock() object and stores it in the mainClock variable. This object will help us keep the program from running too fast.
Line 45 creates a new object which will be used for the window displayed on the screen. We will specify the width and height of this object (and the window) by passing a tuple with the WINDOWWIDTH and WINDOWHEIGHT pygame.display.set_mode(): a tuple. The arguments for pygame.display.set_mode() are not two integers but a tuple of two integers.
On line 46, the caption of the window is set to the string 'Dodger'. This caption will appear in the
In our game, we do not want the False to tell Pygame to make the cursor pygame.mouse.set_visible(True).
The pygame.display.set_mode() function has a second, pygame.FULLSCREEN, like this
45. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), pygame.FULLSCREEN)
Passing pygame.FULLSCREEN will make the program take up the entire space of the screen. It will still be WINDOWWIDTH and WINDOWHEIGHT in size for the windows width and height, but the image will be stretched larger to fit the screen. There may be wasted space along the top and bottom (or the left and right) sides of the screen if you did not set the window size in
If you do not use the fullscreen mode, then you do not need to worry about using a 4:3 ratio for the width and height. Just use whatever width and height works best for your game.
49. # set up fonts 50. font = pygame.font.SysFont(None, 48)
We need to create a Font object to use when we create a object with the image of text drawn on it. (This process is called "rendering".) We want to create a generic font, so we will use the default Font object that the pygame.font.SysFont() constructor function returns. We pass None so that the 48 so that the font has a size of 48 points.
52. # set up sounds
53. gameOverSound = pygame.mixer.Sound('gameover.wav')
54. pygame.mixer.music.load('background.mid')
Next we want to create the Sound objects and also set up the Sound objects will only be played when we specifically want them to. In this case, the Sound object will be played when the player loses the game.
You can use any .wav or .mid file for this game. You can download these sound files from this book's website at the URL http://inventwithpython.com/resources. Or you can use your own sound files for this game, as long as they have the filenames of gameover.wav and background.mid. (Or you can change the strings used on lines 53 and 54 to match the filenames.)
The pygame. constructor function creates a new Sound object and stores a reference to this object in the gameOverSound variable. In your own games, you can create as many Sound objects as you like, each with a different sound file that it will play.
The pygame. function loads a sound file to play for the
56. # set up images
57. playerImage = pygame.image.load('player.png')
58. playerRect = playerImage.get_rect()
59. baddieImage = pygame.image.load('baddie.png')
Next we will load the image files that used for the player's character and the baddies on the screen. The image for the character is stored in player.png and the image for the baddies is stored in baddie.png. All the baddies look the same, so we only need one image file for them. You can download these images from the book's website at the URL http://inventwithpython.com/resources.
When the game first starts, we want to display the name of the game on the screen. We also want to instruct the player that they can start the game by pushing any key. This screen appears so that the player has time to get ready to start playing after running the program. Also, before each game starts, we want to reset the value of the top score back to 0.
61. # show the "Start" screen
62. drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3),
(WINDOWHEIGHT / 3))
63. drawText('Press a key to start.', font, windowSurface,
(WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
64. pygame.display.update()
65. waitForPlayerToPressKey()
On lines 62 and 63, we call our drawText() function and pass it five arguments: 1) the string of the text we want to appear, 2) the font that we want the string to appear in, 3) the object onto which to render the text, and 4) and 5) the X and Y coordinate on the object to draw the text at.
This may seem like many arguments to pass for a function call, but keep in mind that this function call replaces five lines of code each time we call it. This shortens our program and makes it easier to find bugs since there is less code to check.
The waitForPlayerToPressKey() function will pause the game by entering into a loop that checks for any KEYDOWN events. Once a KEYDOWN event is generated, the execution breaks out of the loop and the program continues to run.
68. topScore = 0 69. while True:
We have finished defining the helper functions and variables that we need for this game. Line 68 is the start of the main game code. The value in the topScore variable starts at 0 only when the program first runs. Whenever the player loses and has a score larger than the current top score, the top score is replaced with the player's score.
The while loop will iterate each time the player starts a new game. We will set up the code so that when the player loses and we need to reset the game, the program's execution will go back to the start of this loop.
70 . # set up the start of the game 71. baddies = [] 72 . score = 0
At the very beginning, we want to set the baddies list to an baddies list is a list of dictionary objects with the following keys:
'rect' - The Rect object that describes where and what size the baddie is.
'speed' - How fast the baddie falls down the screen. This integer represents pixels per iteration through the game loop.
'surface ' - The Surface object that has the scaled image of the baddie image drawn on it. This is the Surface object that will be Surface object returned by pygame.display.set_mode() and drawn on the screen.
Next, we want to reset the player's score to 0.
73. playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT -50)
The starting location of the player will be in the center of the screen and 50 pixels up from the bottom. The tuple that we set the topleft attribute to will change the location of the playerRect object. The first item in the tuple is the X-coordinate of the left edge. The second item in the tuple is the Y coordinate of the top edge.
74. moveLeft = moveRight = moveUp = moveDown = False 75. reverseCheat = slowCheat = False 76. baddieAddCounter = 0
Also at the start of the game, we want to have the movement variables moveLeft, moveRight, moveUp, and moveDown set to False. The reverseCheat and slowCheat variables will be set to True only when the player enables these
The baddieAddCounter variable is used for a counter to tell the program when to add a new baddie at the top of the screen. The value in baddieAddCounter will be incremented by one each time the game baddieAddCounter counter is equal to the value in ADDNEWBADDIERATE, then the baddieAddCounter counter is reset back to 0 and a new baddie is added to the top of the screen.
77. pygame.mixer.music.play(-1, 0.0)
At the start of the game, we want the pygame.. The first argument is the number of times the music should repeat itself. -1 is a special value that tells Pygame we want the music to repeat endlessly. The second argument is a float that says how many seconds into the music we want it to start playing. Passing 0.0 means we want to play the music starting from the beginning of the music file. (Passing 2.0, for example, would have started the music two seconds into the music file.)
The game loop contains the code that is executed while the game is being played. The game loop constantly updates the state of the game world by changing the position of the player and baddies, handling events generated by Pygame, and drawing the state of the game world on the screen. All of this happens several dozen times a second, which makes it seem that the game is happening in real time to the player.
79. while True: # the game loop runs while the game part is playing 80. score += 1 # increase score
Line 79 is the start of the main game loop. In the main game loop, we will increase the player's score, handle any events that were generated, add any baddies to the top of the screen if needed, move the baddies down a little, and then draw everything on the screen. This code will be executed over and over again as the program execution iterates through the game loop. The loop will only exit when the player either loses the game or quits the program.
First, we will increment the player's score. The longer the player can go without losing, the higher their score will be.
There are four different types of events we will handle in our game: QUIT, KEYDOWN, KEYUP, and MOUSEMOTION. The QUIT event is generated by Pygame if the player closes the program's window or shuts down the computer. In that case, we want the program to close itself. The KEYDOWN and KEYUP events are generated when the player pushes down and releases the keyboard keys, respectively. These events will be how we can tell which direction the player wants to move the character. The player could also have pressed the Esc key to signal that they want to shut down the program. Each time the player moves the mouse, Pygame will generate a MOUSEMOTION event which will tell us the X and Y coordinates of the
82. for event in pygame.event.get(): 83. if event.type == QUIT: 84. terminate()
Line 82 is the start of the event-handling code. First we call pygame.event.get(), which returns a list of Event objects. Each Event object represents an event that has been created since the last call to pygame.event.get(). We will check the type attribute of the event object to see what type of event it is, and handle the event accordingly.
If the type attribute of the Event object is equal to QUIT, then this tells us that the user has closed the program somehow. The QUIT pygame.locals module, but since we imported that module with the line from pygame.locals import * instead of simply import pygame.locals, we only need to type QUIT and not pygame.locals.QUIT.
86. if event.type == KEYDOWN:
87. if event.key == ord('z') :
88. reverseCheat = True
89. if event.key == ord('x') :
90. slowCheat = True
If the event's type is KEYDOWN, then we know that the player has pressed down a key. The Event object for keyboard events will also have a key attribute that is set to the numeric ASCII value of the key pressed. The ord() function will return the ASCII value of the letter passed to it.
For example, on line 87, we can check if the event describes the "z" key being pressed down by checking if event.key == ord('z'). If this condition is True, then we want to set the reverseCheat variable to True to indicate that the reverse
Pygame's keyboard events always use the ASCII values of event.key == ord('z') instead of event.key == ord('Z') . Otherwise, your program may act as though the key hasn't been pressed at all.
91. if event.key == K_LEFT or event.key == ord('a'):
92. moveRight = False
93. moveLeft = True
94. if event.key == K_RIGHT or event.key == ord('d'):
95. moveLeft = False
96. moveRight = True
97. if event.key == K_UP or event.key == ord ( ' w' ) :
98. moveDown = False
99. moveUp = True
100. if event.key == K_DOWN or event.key == ord('s ' ) :
101. moveUp = False
102. moveDown = True
We also want to check if the event was generated by the player pressing one of the arrow keys. There is not an ASCII value for every key on the keyboard, such as the arrow keys or the Esc key. Instead, Pygame provides some
We can check if the player has pressed the event.key == K_LEFT. Again, the reason we can use K_LEFT instead of pygame.locals.K_LEFT is because we imported pygame.locals with the line from pygame.locals import * instead of import pygame.locals.
Noticed that pressing down on one of the arrow keys not only sets one of the movement variables to True, but it also sets the movement variable in the opposite direction to False. For example, if the moveLeft to True, but it also sets moveRight to False. This prevents the player from confusing the program into thinking that the player's character should move in two opposite directions at the same time.
Here is a list of commonly-used
| Pygame | Keyboard Key | Pygame | Keyboard Key |
|---|---|---|---|
K_LEFT
| K_HOME
| Home | |
K_RIGHT
| Right arrow | K_END
| End |
K_UP
| Up arrow | K_PAGEUP
| PgUp |
K_DOWN
| Down arrow | K_PAGEDOWN
| PgDn |
K_ESCAPE
| Esc | K_F1
| F1 |
K_BACKSPACE
| Backspace | K_F2
| F2 |
K_TAB
| Tab | K_F3
| F3 |
K_RETURN
| Return or Enter | K_F4
| F4 |
K_SPACE
| Space bar | K_F5
| F5 |
K_DELETE
| Del | K_F6
| F6 |
K_LSHIFT
| Left Shift | K_F7
| F7 |
K_RSHIFT
| Right Shift | K_F8
| F8 |
K_LCTRL
| Left Ctrl | K_F9
| F9 |
K_RCTRL
| Right Ctrl | K_F10
| F10 |
K_LALT
| Left Alt | K_F11
| F11 |
K_RALT
| Right Alt | K_F12
| F12 |
104. if event.type == KEYUP:
105. if event.key == ord('z') :
106. reverseCheat = False
107. score = 0
108. if event.key == ord('x'):
109. slowCheat = False
110. score = 0
The KEYUP event is created whenever the player stops pressing down on a keyboard key and it returns to its normal, up position. KEYUP objects with a type of KEYUP also have a key attribute just like KEYDOWN events.
On line 105, we check if the player has released the "z" key, which will deactivate the reverse reverseCheat to False and reset the score to 0. The score reset is to discourage the player for using the
Lines 108 to 110 do the same thing for the "x" key and the slow slowCheat is set to False and the player's score is reset to 0.
111. if event.key == K_ESCAPE: 112. terminate()
At any time during the game, the player can press the Esc key on the keyboard to quit the game. Here we check if the key that was released was the Esc key by checking event.key == K_ESCAPE. If so, we call our terminate() function which will exit the program.
114. if event.key == K_LEFT or event.key == ord (' a ' ) :
115. moveLeft = False
116. if event.key == K_RIGHT or event.key == ord('d'):
117. moveRight = False
118. if event.key == K_UP or event.key == ord ('w'):
119. moveUp = False
120. if event.key == K_DOWN or event.key == ord
(' s ' ) :
121. moveDown = False
Lines 114 to 121 check if the player has stopped holding down one of the arrow keys (or the corresponding WASD key). In that event, we will set the corresponding movement variable to False. For example, if the player was holding down the moveLeft would have been set to True on line 93. When they release it, the condition on line 114 will evaluate to True, and the moveLeft variable will be set to False.
123. if event.type == MOUSEMOTION: 124. # If the mouse moves, move the player where the cursor is. 125. playerRect.move_ip(event.pos[0] - playerRect.centerx, event.pos[1] - playerRect.centery)
Now that we have handled the keyboard events, let's handle any mouse events that may have been generated. In the Dodger game we don't do anything if the player has clicked a
If the event's type is MOUSEMOTION, then we want to move the player's character to the location of the MOUSEMOTION event is generated whenever the mouse is moved. Event objects with a type of MOUSEMOTION also have an attribute named pos. The pos attribute stores a tuple of the X and Y coordinates of where the
The move_ip() method for Rect objects will move the location of the Rect object horizontally or vertically by a number of pixels. For example, playerRect.move_ip (10, 20) would move the Rect object 10 pixels to the right and 20 pixels down. To move the Rect object left or up, pass negative values. For example, playerRect.move_ip(-5, -15) will move the Rect object left by 5 pixels and up 15 pixels.
The "ip" at the end of move_ip() stands for "in place". This is because the method changes the Rect object itself, in its own place. There is also a move() method which does not change the Rect object, but instead creates a new Rect object that has the new location. This is useful if you want to keep the original Rect object's location the same but also have a Rect object with the new location.
127. # Add new baddies at the top of the screen, if needed. 128. if not reverseCheat and not slowCheat: 129. baddieAddCounter += 1
On each iteration of the game loop, we want to increment the baddieAddCounter variable by one. However, we only want to do this if the reverseCheat and slowCheat: are only set to True as long as the "z" and "x" keys are being held down, respectively. And while those keys are being held down, baddieAddCounter is not incremented. This means that no new baddies will appear at the top of the screen.
130. if baddieAddCounter == ADDNEWBADDIERATE:
131. baddieAddCounter = 0
132. baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)
133. newBaddie = {'rect': pygame.Rect (random.randint(0, WINDOWWIDTH-baddieSize), 0 -
baddieSize, baddieSize, baddieSize),
134. 'speed': random.randint (BADDIEMINSPEED, BADDIEMAXSPEED),
135. 'surface':pygame.transform.scale (baddieImage, (baddieSize, baddieSize)),
136. }
When the baddieAddCounter reaches the value in ADDNEWBADDIERATE, then the condition on line 130 is True and it is time to add a new baddie to the top of the screen. First, the baddieAddCounter counter is reset back to 0 (otherwise, when it keeps incrementing it will always be greater than ADDNEWBADDIERATE and never equal to it. This will cause baddies to stop appearing at the top of the screen.)
Line 132 generates a size for the baddie in pixels. The size will be between BADDIEMINSIZE and BADDIEMAXSIZE, which we have set to 10 and 40 in this program.
Line 133 is where a new baddie data structure is created. Remember, the data structure for baddies is simply a dictionary with keys 'rect', 'speed', and '. The 'rect' key holds a reference to a Rect object which stores the location and size of the baddie. The call to the pygame.Rect() constructor function has four parameters: the X-coordinate of the top edge of the area, the Y coordinate of the left edge of the area, the width in pixels, and the height in pixels.
We want the baddie to appear randomly across the top of the window, so we pass random.randint(0, WINDOWWIDTH-baddieSize) for the X-coordinate of the left edge. This will evaluate to a random place across the top of the window. The reason we pass WINDOWWIDTH-baddieSize instead of WINDOWWIDTH is because this value is for the left edge of the baddie. If the left edge of the baddie is too far on the right side of the screen, then part of the baddie will be off the edge of the window and not visible.
We want the bottom edge of the baddie to be just above the top edge of the window. The Y-coordinate of the top edge of the window is 0, so to put the baddie's bottom edge there, we want to set the top edge to 0 - baddieSize.
The baddie's width and height should be the same (the image is a square), so we will pass baddieSize for the third and fourth argument.
The rate of speed that the baddie moves down the screen will be set in the 'speed' key, and is set to a random integer between BADDIEMINSPEED and BADDIEMAXSPEED.
138. baddies.append(newBaddie)
Line 138 will add the newly created baddie data structure to the list of baddie data structures. Our program will use this list to check if the player has collided with any of the baddies and to know where to draw baddies on the window.
140. # Move the player around. 141. if moveLeft and playerRect.left > 0: 142. playerRect.move_ip(-1 * PLAYERMOVERATE, 0)
The four movement variables moveLeft, moveRight, moveUp and moveDown are set to True and False when Pygame generates the KEYDOWN and KEYUP events, respectively. (This code is from line 86 to line 121.)
If the player's character is moving left and the left edge of the player's character is greater than 0 (which is the left edge of the window), then we want to move the character's Rect object (stored in playerRect).
We will always move the playerRect object by the number of pixels in PLAYERMOVERATE. To get the negative form of an integer, you can simply multiple it by -1. So on line 142, since 5 is stored in PLAYERMOVERATE, the expression -1 * PLAYERMOVERATE evaluates to -5.
This means that calling playerRect.move_ip(-1 * PLAYERMOVERATE, 0) will change the location of playerRect by 5 pixels to the left of its current location.
143. if moveRight and playerRect.right < WINDOWWIDTH: 144. playerRect.move_ip(PLAYERMOVERATE, 0) 145. if moveUp and playerRect.top > 0: 146. playerRect.move_ip(0, -1 * PLAYERMOVERATE) 147. if moveDown and playerRect.bottom < WINDOWHEIGHT: 148. playerRect.move_ip(0, PLAYERMOVERATE)
We want to do the same thing for the other three directions: right, up, and down. Each of the three if statements in lines 143 to 148 checks that their movement variable is set to True and that the edge of the Rect object of the player is inside the window before calling the move_ip() method to move the Rect object.
150. # Move the mouse cursor to match the player. 151. pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)
Line 151 moves the pygame.mouse.set_pos() function moves the Rect object because we pass the centerx and centery attributes of playerRect for the coordinates. The pygame.mouse.set_visible(False) on line 47.
The reason we want the
153. # Move the baddies down. 154. for b in baddies:
Now we want to loop through each baddie data structure in the baddies list to move them down a little.
155. if not reverseCheat and not slowCheat: 156. b['rect'].move_ip(0, b['speed'])
If neither of the reverseCheat or slowCheat to True, respectively), then move the baddie's location down a number of pixels equal to its speed, which is stored in the 'speed' key.
157. elif reverseCheat: 158. b ['rect'] .move_ip(0, -5)
If the reverse -5 for the second argument to move_ip() will move the Rect object
159. elif slowCheat: 160. b['rect'].move_ip(0, 1)
If the slow 'speed' key of the baddie's data structure) will be ignored while the slow
162. # Delete baddies that have fallen past the bottom. 163. for b in baddies [:] :
After moving the baddies down the window, we want to remove any baddies that fell below the bottom edge of the window from the baddies list. Remember that we while we are iterating through a list, we should not modify the contents of the list by adding or removing items. So instead of iterating through the baddies list with our baddies loop we will iterate through a copy of the baddies list.
Remember that a list slice will evaluate a copy of a list's items. For example, spam [2:4] will return a new list with the items from index 2 up to (but not including) index 4. Leaving the first index blank will indicate that index 0 should be used. For example, spam [:4] will return a list with items from the start of the list up to (but not including) the item at index 4. Leaving the second index blank will indicate that up to (and including) the last index should be used. For example, spam[2:] will return a list with items from index 2 all the way to (and including) the last item in the list.
But leaving both indexes in the slice blank is a way to represent the entire list. The baddies[:] expression is a list slice of the whole list, so it evaluates to a copy of the entire list. This is useful because while we are iterating on the copy of the list, we can modify the original list and remove any baddie data structures that have fallen past the bottom edge of the window.
Our for loop on line 163 uses a variable b for the current item in the iteration through baddies[:].
164. if b [ 'rect'] .top > WINDOWHEIGHT: 165. baddies.remove(b)
Let's evaluate the expression b['rect'].top. b is the current baddie data structure from the baddies[:] list. Each baddie data structure in the list is a dictionary with a 'rect' key, which stores a Rect object. So b['rect'] is the Rect object for the baddie. Finally, the top is the Y-coordinate of the top edge of the rectangular area. Remember that in the b['rect'].top > WINDOWHEIGHT will check if the top edge of the baddie is below the bottom of the window.
If this condition is True, then the we will remove the baddie data structure from the baddies list.
It isn't enough that our game updates the state of the game world in its memory. Our program will also have to display the game world to the player. We can do this by drawing the graphics of the baddies and player's character on the screen. Because the game loop is executed several times a second, drawing the baddies and player in new positions makes their movement look smooth and natural. But every element on the screen must be drawn one at a time by calling the
167. # Draw the game world on the window. 168. windowSurface.fill(BACKGROUNDCOLOR)
Now that we have updated all the data structures for the baddies and the player's character, let's draw everything on the screen. First, before we draw anything else on the windowSurface, we want to black out the entire screen to erase anything drawn on it in a previous iteration through the game loop.
Remember that the object in windowSurface is the special object because it was the one returned by pygame.display.set_mode(). This mean that anything drawn on that object will appear on the screen, but only after the pygame.display.update() function is called.
170. # Draw the score and top score.
171. drawText('Score: %s' % (score), font, windowSurface, 10, 0)
172. drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)
Next we will render the text for score and top score to the top left corner of the window. The 'Score: %s' % (score) uses string 'Score: ' + str(score). We pas this string, the Font object stored in the font variable, the object on which to draw the text on, and the X and Y coordinates of where the text should be placed. Remember that our drawText() will handle the call to the render() and methods.
For the top score, we do the exact same thing. We pass 40 for the Y-coordinate instead of 0 (like we do for the score) so that the top score text appears beneath the score text.
174. # Draw the player's rectangle 175. windowSurface.blit(playerImage, playerRect)
Remember that the information about the player is kept in two different variables. playerImage is a object that contains all the colored pixels that make up the player's character's image. playerRect is a Rect object that stores the information about the size and location of the player's character.
We call the method on windowSurface and pass playerImage and playerRect. This draws the player character's image on windowSurface at the
177. # Draw each baddie 178. for b in baddies: 179. windowSurface.blit(b['surface'], b['rect'])
We use a for loop here to draw every baddie on the windowSurface object. Remember that each item in the baddies list is a dictionary with ' and 'rect' keys containing the object with the baddie image and the Rect object with the position and size information, respectively.
181. pygame.display.update()
Now that we have finished drawing everything to the windowSurface object, we should draw this pygame.display.update().
183. # Check if any of the baddies have hit the player. 184. if playerHasHitBaddie(playerRect, baddies): 185. if score > topScore: 186. topScore = score # set new top score 187. break
Now let's check if the player has collided with any of the baddies. We already wrote a function to check for this: playerHasHitBaddie(). This function will return True if the player's character has collided with any of the baddies in the baddies list. Otherwise, the function will return False.
If the player's character has hit a baddie, then we check if the player's current score is greater than the top score. If it is, we set the new top score to be the player's current score. Either way, we break out of the game loop. The program's execution will jump down to line 191.
189. mainClock.tick(FPS)
To keep the computer from running through the game loop as fast as possible (which would be much too fast for the player to keep up with), we call mainClock.tick() to pause for a brief amount of time. The pause will be long enough to ensure that about 40 (the value we stored inside the FPS variable) iterations through the game loop occur each second.
191. # Stop the game and show the "Game Over" screen. 192. pygame.mixer.music.stop() 193. gameOverSound.play()
When the player loses, we want to stop playing the stop() function in the pygame. module to stop the play() method on the Sound object stored in gameOverSound.
195. drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
196. drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)
197. pygame.display.update()
198. waitForPlayerToPressKey()
Now we want to display text on the window to tell the player that the game is over, and they should press a key to start playing a new game. The two calls to our drawText() function will draw this text to the windowSurface object, and the call to pygame.display.update() will draw this object to the screen.
After displaying this text, we want the game to stop until the player presses a key, so we call our waitForPlayerToPressKey() function.
200. gameOverSound.stop()
After the player presses a key, the program execution will return from the waitForPlayerToPressKey() call on line 198. Depending on how long the player takes to press a key, the "game over" sound effect may or may not still be playing. We want to stop this sound effect before this loop ends and we start a new game, so we have a call to gameOverSound.stop() here.
That's it for our graphical game. You may find that the game is too easy or too hard. But the game is very easy to modify because we took the time to use
For example, if you want the game to run slower in general, change the FPS variable on line 8 to a smaller value such as 20. This will make both the baddies and the player's character move slower since the game loop will only be executed 20 times a second instead of 40.
If you just want to slow down the baddies and not the player, then change BADDIEMAXSPEED to a smaller value such as 4. This will make all the baddies move between 1 (the value in BADDIEMINSPEED) and 4 pixels per iteration through the game loop instead of 1 and 8.
If you want the game to have fewer but larger baddies instead of many fast baddies, then increase ADDNEWBADDIERATE to 12, BADDIEMINSIZE to 40, and BADDIEMAXSIZE to 80. Now that baddies are being added every 12 iterations through the game loop instead of every 6 iterations, there will be half as many baddies as before. But to keep the game interesting, the baddies are now much larger than before.
While the basic game remains the same, you can modify any of the
Unlike our previous text-based games, Dodger really looks like the kind of modern computer game we usually play. It has graphics and music and uses the mouse. While Pygame provides functions and data types as building blocks, it is you the programmer who puts them together to create fun, interactive games.
And it is all because you know exactly how to instruct the computer to do it, step by step, line by line. You can speak the computer's language, and get it to do large amounts of number crunching and drawing for you. This is a very useful skill, and I hope you will continue to learn more about Python programming. (And there is still more to learn!)
Here are several websites that can teach you more about programming Python:
| http://www.python.org/doc/ | More Python tutorials and the documentation of all the Python modules and functions. |
| http://www.pygame.org/docs/ | Complete documentation on the modules and functions for Pygame. |
| http://inventwithpython.com | This book's website, which includes all the source code for these programs and additional information. This site also has the image and sound files used in the Pygame programs. |
| http://inventwithpython.com/traces | A web application that helps you trace through the execution of the programs in this book, step by step. |
| http://inventwithpython.com/videos | Videos that accompany the programs in this book. |
| http://gamedevlessons.com | A helpful website about how to design and program video games. |
| al@inventwithpython.com | The author's email address. Feel free to email Al your questions about this book or about Python programming. |
Or you can find out more about Python by searching the World Wide Web. Go to the search engine website http://google.com and search for "Python programming" or "Python tutorials" to find web sites that can teach you more about Python programming.
Now get going and invent your own games. And good luck!
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.