Dodger
Start of the Main Game Code
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 infinite loop started on line 69 is technically not the "game loop". (The main game loop handles events and drawing the window while the game is running.) Instead, this 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 empty list. The 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 blitted to the 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 cheats by holding down the "z" and "x" keys, respectively.
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 loop iterates. When the 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 background music to begin playing. We can do this with a call to pygame.mixer.music.play(). 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
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.
Event Handling
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 mouse cursor over the window.
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 constant variable was imported from the 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 cheat has been activated. We will also check if the "x" key has been pressed to activate the slow cheat in a similar way.
Pygame's keyboard events always use the ASCII values of lowercase letters, not uppercase. What this means for your code is that you should always use 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 constant variables to use instead.
We can check if the player has pressed the left arrow key with the condition: 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 left arrow key is pushed down, then the code on line 93 sets 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 constant variables for the key attribute of keyboard-related Event objects:
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 cheat. In that case, we set reverseCheat to False and reset the score to 0. The score reset is to discourage the player for using the cheats.
Lines 108 to 110 do the same thing for the "x" key and the slow cheat. When the "x" key is released, 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 left arrow key, then 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.
The move_ip() Method for Rect objects
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 mouse button, but the game does respond when the player moves the mouse. This gives the player two ways of controlling the player character in the game: the keyboard and the mouse.
If the event's type is MOUSEMOTION, then we want to move the player's character to the location of the mouse cursor. 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 mouse cursor moved in the window.
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.
Adding New Baddies
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 cheats are not enabled. Remember that 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 'surface'. 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.