Опубликован: 12.07.2013 | Доступ: свободный | Студентов: 735 / 20 | Длительность: 37:41:00
Лекция 20:

Dodger

Importing the Modules

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 constant variables that the Pygame library uses such as the event types (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.

Setting Up the Constant Variables

There are several constant variables in this game. We use constant variables because the variable name is much more descriptive than a number. For example, from the line 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 constant variables. By changing 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 occurrence of 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 constant variables, changing the value here will change it everywhere in our program.

Instead of storing color tuples into a variable named WHITE or BLACK, we will use constant variables for the color of the text and background. Remember that the three integers in the color tuples range from 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 constant variable 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 constant variables that will describe the falling baddies. The width and height of the baddies will be between 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.

Defining Functions

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 mistake and create a bug in our program.

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 infinite loop that only breaks when a 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 freeze the game until the player presses a key or closes the window.

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 Surface 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 Surface object we just made. We can get a Rect object with this information with the get_rect() method for Surface objects.

This Rect object has no special connection to the Surface object with the text drawn on it, other than the fact that it has a copy of the width and height information from the Surface object. We can change the location of the Rect object by setting a new tuple value for its topleft attribute.

Finally, we blit the Surface object of the rendered text onto the Surface 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.

Initializing Pygame and Setting Up the Window

Now that the constant variables and functions are finished, we can start calling the Pygame functions that will set up Pygame for use in our code. Many of these function calls are to set up the GUI window and create objects that we will use in the game.

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 Surface object which will be used for the window displayed on the screen. We will specify the width and height of this Surface object (and the window) by passing a tuple with the WINDOWWIDTH and WINDOWHEIGHT constant variables. Notice that there is only one argument passed to 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 title bar at the top of the window.

In our game, we do not want the mouse cursor (the mouse cursor is the arrow that moves around the screen when we move the mouse) to be visible. This is because we want the mouse to be able to move the player's character around the screen, and the arrow cursor would get in the way of the character's image on the screen. We pass False to tell Pygame to make the cursor invisible. If we wanted to make the cursor visible again at some point in the program, we could call pygame.mouse.set_visible(True).

Fullscreen Mode

The pygame.display.set_mode() function has a second, optional parameter that you can pass to it. The value you can pass for this parameter is pygame.FULLSCREEN, like this modification to line 45 in our Dodger program:

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 proportion with the screen's resolution.) To avoid the wasted space, you should set the size of the window to a 4:3 ratio (for every 4 pixels of width, have 3 pixels for height).

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 Surface 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 default font is used, and we pass 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 background music. The background music will constantly be playing during the game, but 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.mixer.Sound() 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.mixer.music.load() function loads a sound file to play for the background music. This function does not create any objects, and only one sound file can be loaded at a time.

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.

Display the Start Screen

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 Surface object onto which to render the text, and 4) and 5) the X and Y coordinate on the Surface 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.