Putting It All Together

[ LiB ]

Putting It All Together

In this section you'll take a bit from each previous part in the chapter to create a sample game. This sample is called Snowboard!.py and can be found along with its data files in this chapters section on the CD.

Snowboard! has a structure similar to the Monkey_Toss.py sample from earlier, and you'll follow the same general steps during creation:

  1. Import the necessary libraries.

  2. Define any necessary functions, the only one in this case being a Display_Message function for displaying splash text on the screen.

  3. Define any game object classes, in this case SimpleSprite , Player , Obstacle , and FinishLine .

  4. Create a main() function and set up Pygame.

  5. Draw and update the necessary graphics utilizing groups and sprites within a while() loop.

Are you ready? Then break!

Import the Libraries

And the libraries are:

 import os import sys import random import pygame from pygame.locals import * 

'Nuff said.

Define the Functions

You want to set up a function that will display text in the game window. Let's call this function Display_Message , and use it to display a "You Win!" or a "Game Over!" message at the game's conclusion. The function will take three parameters: the actual message, the game screen, and the game background. You'll use pygame.font.Font to define the type of font to use, font.render to render the message in white (RGB values 1,1,1), and use get_rect().cen terx and centery to ensure the text placement is in the center of the window.

 # Generic function to place a message on screen def Display_Message( message, screen, background ):    font = pygame.font.Font( None, 48 )    text = font.render( message, 1, ( 1, 1, 1 ) )    textPosition = text.get_rect()    textPosition.centerx = background.get_rect().centerx    textPosition.centery = background.get_rect().centery    return screen.blit( text, textPosition ) 

As you see, our Display_Message function looks remarkably similar to the font.render example earlier in the chapter.

Define the Classes

Snowboard! will have four classes: SimpleSprite , which will be a base class for all the other classes, and a Player , Obstacle , and FinishLine class:

 class SimpleSprite:  class Player( SimpleSprite ): class Obstacle( SimpleSprite ): class FinishLine( SimpleSprite ): 

The SimpleSprite is the basis for all the others, and defines base methods for placing the sprite on the screen using blit() and then covering the sprite with the background to make it disappear. The default _init_ method can take a loaded image and set itself up as a rect() :

 # Base sprite class for all moving pieces class SimpleSprite:    def __init__( self, image ):       # Can load an image, sets up w-In a rect()       self.image = image       self.rectangle = image.get_rect()    def place( self, screen ):       #Places the sprite on the given screen       return screen.blit( self.image, self.rectangle )    def remove( self, screen, background ):       #Place under background to remove       return screen.blit( background, self.rectangle,          self.rectangle ) 

The FinishLine is a sprite that represents a movable line on the game board. The snowboarder must travel a number of screen lengths before reaching the finish, dodging obstacles on his way.

You only need an _init_ method and a move method for FinishLine to initialize it and then move it where you have established the end game to be:

 # Finish line - movable for game difficulty class FinishLine( SimpleSprite ): # Initialize and center    def __init__( self, image, centerX = 0, centerY = 0 ):       SimpleSprite.__init__( self, image )       self.rectangle.centerx = centerX       self.rectangle.centery = centerY #Finish line can move up and down depending upon game difficulty    def move( self, xIncrement, yIncrement ):       self.rectangle.centerx -= xIncrement       self.rectangle.centery -= yIncrement 

The Obstacle sprite will be used to load up tree images, which the snowboarder will have to avoid, to place on the screen. Notice how the move() method is used:

 # Class definition for the trees to avoid class Obstacle( SimpleSprite ): # Initiate an object of the class    def __init__( self, image, centerX = 0, centerY = 0 ):       # Initiate with a loaded image and set as a rectangle       SimpleSprite.__init__( self, image )       self.positiveRectangle = self.rectangle       # move obstacle to a specified location       self.positiveRectangle.centerx = centerX       self.positiveRectangle.centery = centerY       # display that the object has moved position       self.rectangle = self.positiveRectangle.move( -60, -60 ) 

The movement of these sprites will be dependent upon the player's actions, and will require a complicated move method:

 def move( self, xIncrement, yIncrement ):       #Move trees up as the player moves down the slope       self.positiveRectangle.centerx -= xIncrement       self.positiveRectangle.centery -= yIncrement       # Change position for the next sprite update       if self.positiveRectangle.centery < 25:          self.positiveRectangle[ 0 ] += \             random.randrange( -640, 640 )       # Keep the rectangle values from overflowing       self.positiveRectangle[ 0 ] %= 760       self.positiveRectangle[ 1 ] %= 600       # Display that the object has moved In position       self.rectangle = self.positiveRectangle.move( -60, -60 ) 

You will also need to check, using a Collision_Watch method, for sprite collisions with the snowboarder. The rectangular box that you use to detect the collisions is actually a bit smaller than the graphics:

 def Collision_Watch( self ):       #Make the collision box smaller than graphic       return self.rectangle.inflate( -20, -20 ) 

Finally, you need to define the Player class, which is the class that will actually control the snowboarder. This class must be able to accomplish several things. First, the snowboarder's four graphicsdefault, going left, going right, and crashedall need methods, and a method must also exist to load each graphic when it is needed.

The Player class speed should be controllable, which means you need three methodsone to determine if the Player class is moving at all, one for speeding up, and one for slowing down.

The Player class also needs to watch for collisions with Obstacle classes, and remember how far it has traveled so it can know when it passes FinishLine . Altogether, this works out to some ten methods:

 class Player( SimpleSprite ):    def __init__( self, images, crashImage, centerX = 0, centerY = 0 ):    def Load_Image( self ):    def Move_Left( self ):    def Move_Right( self ):    def Decrease_Speed( self ):    def Increase_Speed( self ):    def Collision( self ):    def Collision_Watch( self ):    def Are_We_Moving( self ):    def Distance_Moved( self ): 

We start with the _init_ method that establishes the loading graphic and the initial state of the Player :

 def __init__( self, images, crashImage,       centerX = 0, centerY = 0 ):       # Initial image and player state       self.movingImages = images       self.crashImage = crashImage       # Initial Positioning - top and center       self.centerX = centerX       self.centerY = centerY       # Starts with the Player graphic facing down       self.playerPosition = 1       # Start with 0 speed - not moving       self.speed = 0       self.Load_Image() 

You use yet another version of Load_Image to pull each version of the snowboarder graphic when needed:

 # Load the correct image    def Load_Image( self ):       # If the player has crashed - special       if self.playerPosition == -1:          image = self.crashImage       else:          # All other cases the self.playerPosition determines which graphic to use          image = self.movingImages[ self.playerPosition ]       # Notice that the SimpleSprite Is re-Initialized       SimpleSprite.__init__( self, image )       self.rectangle.centerx = self.centerX       self.rectangle.centery = self.centerY 

Now tackle movement. The following simply double-check that Player class hasn't crashed into something, and then change the player's position:

 #Player Is Moving left     def Move_Left( self ):        # Check for crashing, If so drop speed        if self.playerPosition == -1:           self.speed = 1           self.playerPosition = 0        # Otherwise start moving left        elif self.playerPosition > 0:           self.playerPosition -= 1        self.Load_Image() #Player Is Moving Right def Move_Right( self ):        #Check for crashing        if self.playerPosition == -1:           self.speed = 1           self.playerPosition = 2        # Otherwise start moving right        elif self.playerPosition < ( len( self.movingImages ) - 1 ):           self.playerPosition += 1        self.Load_Image() 

When moving down the hill, the Player class will have variable speeds. First use the Are_We_Moving method to determine if the Player class is moving at all:

 # Is Player moving or does speed = 0 def Are_We_Moving( self ):        if self.speed == 0:           return 0        else:           return 1 

Then we define, increase, and decrease speed, which basically alters from 1 to 10 variables that the game code will use to increase or decrease the Obstacle movement rates:

 # Subtract 1 from speed def Decrease_Speed( self ):        if self.speed > 0:           self.speed -= 1 # Add 1 to speed up to 10, # Double check to see If we crash    def Increase_Speed( self ):       if self.speed < 10:          self.speed += 1       # player crashed       if self.playerPosition == -1:          self.playerPosition = 1          self.Load_Image() 

Next, you need to keep track of the distance the Player class has moved. You do this with two variables, xIncrement and yIncrement . These start at 0 and then increase as the Player class moves down the virtual hill. Additionally, if Player is facing straight down, she travels a little bit faster than when she is traversing the hill. The distance is also modified by self.speed :

 def Distance_Moved( self ):       xIncrement, yIncrement = 0, 0       if self.isMoving():           # Are we facing straight down, then faster          if self.playerPosition == 1:             xIncrement = 0             yIncrement = 2 * self.speed          else:             xIncrement = ( self.playerPosition - 1 ) * self.speed             yIncrement = self.speed       return xIncrement, yIncrement 

Finally, set up collisions. This includes the same sort of Collision_Watch you saw earlier with Obstacle , and also a Collsion method that can change the Player classes' graphic if necessary:

 def Collision_Watch( self ):       #Slightly smaller box       return self.rectangle.inflate( -20, -20 ) # Change graphic If necessary def Collision( self ):       #Change graphic to player crashed       self.speed = 0       self.playerPosition = -1       self.Load_Image() 

Create main() and Set Up Pygame

The main() function is where all of the fun happens. The game needs a number of variables defined, some of which change constantly and others that never change at all (called constants). The first trick is to get all of these straight.

 def main():    #First set Constants (all capitalized by convention)    # Time to wait between frames    WAIT_TIME = 20    # Set the course to be 25 screens long at 480 pixels per screen    COURSE_DEPTH = 25 * 480    # Seeds the number of trees on the screen    NUMBER_TREES = 5     # Secondly set Variables    # vertical distance traveled    distanceTraveled = 0    # time to generate next frame    nextTime = 0     # The course has not been completed    courseOver = 0 # Randomly generated obstacle sprites    allTrees = [] # All screen position sprites that have changed and are now "dirty"    dirtyRectangles = [] # current time clock    timePack = None # Total time to finish course    timeLeft = 60 

There are a number of images and sounds you will be using (located in the Data folder under Chapter 4's code listing on the CD), so we need to tell Python where they are exactly and what you will call them:

 # The paths to the sounds    collisionFile = os.path.join( "data", "THUMP.wav" )    chimeFile = os.path.join( "data", "MMMMM1.wav" )    startFile = os.path.join( "data", "THX.wav" )    applauseFile = os.path.join( "data", "WOW2.wav" )    gameOverFile = os.path.join( "data", "BUZZER.wav" )    # The paths to the Images    # Place all snowbaord files Into girlFiles    girlFiles = []    girlFiles.append( os.path.join( "data", "surferLeft.gif" ) )    girlFiles.append( os.path.join( "data", "surfer.gif" ) )    girlFiles.append( os.path.join( "data", "surferRight.gif" ) )    girlCrashFile = os.path.join( "data", "surferCrashed.gif" )    treeFile = os.path.join( "data", "tree.gif" )    timePackFile = os.path.join( "data", "time.gif" )    game_background = os.path.join("data", "background2.png") 

Now, to initialize Pygame, set the game surface to be 640x480 pixels, make the box caption "Snowboard!", and make the mouse invisible, as the game code doesn't use it:

 # initializing pygame    pygame.init()    screen = pygame.display.set_mode( ( 640, 480 ) )    pygame.display.set_caption( "Snowboard!" )    # Make mouse.set_visable = false/0    pygame.mouse.set_visible( 0 ) 

Now that Pygame has been initialized and you have a window to play in, set the background to the nice snowy-hill-looking background2.png image:

 # Grab and convert the background image    background = pygame.image.load( game_background ).convert()    # blit the background onto screen and update the entire display    screen.blit( background, ( 0, 0 ) )    pygame.display.update() 

Now you need to use Pygame to load the sounds and images to which you have established the paths:

 # First load up the sounds using mixer    collisionSound = pygame.mixer.Sound( collisionFile )    chimeSound = pygame.mixer.Sound( chimeFile )    startSound = pygame.mixer.Sound( startFile )    applauseSound = pygame.mixer.Sound( applauseFile )    gameOverSound = pygame.mixer.Sound( gameOverFile )    # Next we load the images, convert to pixel format    # and use colorkey for transparency    loadedImages = []    # Load all the snowboard files which are In girlFiles    # Then append them Into LoadedImages[]    for file in girlFiles:       surface = pygame.image.load( file ).convert()       surface.set_colorkey( surface.get_at( ( 0, 0 ) ) )       loadedImages.append( surface )    # load the crashed surfer image    girlCrashImage = pygame.image.load( girlCrashFile ).convert()    girlCrashImage.set_colorkey( girlCrashImage.get_at( ( 0, 0 ) ) )    # load the tree image    treeImage = pygame.image.load( treeFile ).convert()    treeImage.set_colorkey( treeImage.get_at( ( 0, 0 ) ) )    # load the timePack image    timePackImage = pygame.image.load( timePackFile ).convert()    timePackImage.set_colorkey( surface.get_at( ( 0, 0 ) ) ) 

There are three last things you need to do before jumping into the while() game loop. The first is initialize the Player snowboarder. Secondly, set up all the Obstacle trees on the course. Finally, play the start up THX sound, just for effect:

 # initialize the girl-snowboarder    centerX = screen.get_width() / 2    # Create and Instance of Player called theGirl    # Use the crashimage, center horizontally and 25 pixels from the top    theGirl = Player( loadedImages, girlCrashImage, centerX, 25 )     # place tree Objects in randomly generated spots    for i in range( NUMBER_TREES ):       allTrees.append( Obstacle( treeImage,          random.randrange( 0, 760 ), random.randrange( 0, 600 ) ) )    # Play start - up sound for effect    startSound.play()    pygame.time.set_timer( USEREVENT, 1000 ) 

Drawing and Updating within the while Loop

Now you need to set up the while loop that updates all the sprites, keeps track of time, and renders everything. The while loop will be set to run until the course is over:

 while not courseOver: 

Then there are a few things you need to do with timing to make sure the game flows smoothly:

 currentTime = pygame.time.get_ticks()       # Wait In case we are moving too fast       if currentTime < nextTime:          pygame.time.delay( nextTime - currentTime )        # Update the time       nextTime = currentTime + WAIT_TIME 

Then check for sprites that are "dirty" (that have changed and need to be updated). We remove any sprites that need to be removed and check to see whether a timePack should to be drawn (a timePack will increase the time left before the loop is exited, giving the player more time to reach the finish line):

 # remove objects from screen that should be removed       dirtyRectangles.append( theGirl.remove( screen,          background ) )       # Check all the trees       for tree in allTrees:          dirtyRectangles.append( tree.remove( screen,             background ) )        # Check timepack       if timePack is not None:          dirtyRectangles.append( timePack.remove( screen,             background ) ) 

Now throw in the event code that listens for a player hitting the keyboard. Use Pygame's built in poll() method to fill the event queue. The player's commands directly affect the Player instance ( theGirl ) by calling the appropriate methods:

 # get next event from event queue using poll() method        event = pygame.event.poll()       # if player quits program or presses the escape key       if event.type == QUIT or \          ( event.type == KEYDOWN and event.key == K_ESCAPE ):          sys.exit()       # if the up arrow key was pressed, slow down!       elif event.type == KEYDOWN and event.key == K_UP:          theGirl.Decrease_Speed()       # if down arrow key was pressed, speed up!       elif event.type == KEYDOWN and event.key == K_DOWN:          theGirl.Increase_Speed()       # if right arrow key was pressed, move player right       elif event.type == KEYDOWN and event.key == K_RIGHT:          theGirl.Move_Right()       # if left arrow key was pressed, move player left       elif event.type == KEYDOWN and event.key == K_LEFT:          theGirl.Move_Left()       # Update the time that the player has left       elif event.type == USEREVENT:          timeLeft -= 1 

Use random to randomly create timePacks on the screen as the player travels down the mountain:

 # 1 in 100 odds of creating new timePack       if timePack is None and not random.randrange( 100 ):          timePack = FinishLine( timePackImage,             random.randrange( 0, 640 ), 480 ) 

Now, as the theGirl class instance moves down the mountain, you need to make sure the sprites that handle the trees and the timePack are updated and redrawn. This only happens if Are_We_Moving is true:

 # update obstacles and timePack positions if the player Is moving       # First check Are_We_Moving       if theGirl.Are_We_Moving():          # Check theGirl x and y Incremented distance          xIncrement, yIncrement = theGirl.Distance_Moved()          # Move all the tree sprites accordingly          for tree in allTrees:             tree.move( xIncrement, yIncrement )          # If there Is a timePack move It as well          if timePack is not None:             timePack.move( xIncrement, yIncrement )             if timePack.rectangle.bottom < 0:                timePack = None          distanceTraveled += yIncrement 

Next handle the meat of the collision detection. Check all grouped tree sprites in the timePack using the Collision_Watch method:

 # check for collisions with the trees       treeBoxes = []       for tree in allTrees:          treeBoxes.append( tree.Collision_Watch() )       # Retrieve a list of the obstacles colliding with the theGirl       Collision = theGirl.Collision_Watch().collidelist( treeBoxes )       # When colliding play a sound and subtract from the time left       if Collision != -1:          collisionSound.play()          allTrees[ Collision ].move( 0, -540 )          theGirl.Collision()          timeLeft -= 5       # Determine whether theGirl has collided with a timePack       # A timePack must exist first       if timePack is not None:          if theGirl.Collision_Watch().colliderect( timePack.rectangle ):             # Play a sound and Increase the time left             chimeSound.play()             timePack = None             timeLeft += 5 

There are only a few things left to do before yoou can exit the while() loop. First you want to draw any dirty or changed objects, mainly the trees and the timePacks . You also want to check to see if theGirl has reached the finish line, and, if so, exit the loop. Finally, you want to check the time; once timeLeft has reached 0 the game will also exit the loop:

 # place objects on screen       dirtyRectangles.append( theGirl.place( screen ) )       for tree in allTrees:          dirtyRectangles.append( tree.place( screen ) )       if timePack is not None:          dirtyRectangles.append( timePack.place( screen ) )       # update whatever has changed       pygame.display.update( dirtyRectangles )       dirtyRectangles = []       # check to see If we have reached the end of the course       if distanceTraveled > COURSE_DEPTH:          # Set a flag that says we have won!          courseOver = 1       # check to see If our time has run out        elif timeLeft <= 0:       break 

Whew! Now, just a bit of wrap-up code at the end of main() and after exiting the while() loop. If you have exited the while loop and courseOver is set to 1, that means the player reached the end of the course and should get praise. Otherwise she lost.

 if courseOver:       applauseSound.play()       message = "You Win!"    else:       gameOverSound.play()       message = "Game Over!" 

Of course, you use your handy-dandy Display_Message function to tell the player what happened :

 pygame.display.update( Display_Message( message, screen,       background ) ) 

Use the event queue to wait for the player to gracefully exit the program:

 # wait until player wants to close program    while 1:       event = pygame.event.poll()       if event.type == QUIT or \          ( event.type == KEYDOWN and event.key == K_ESCAPE ):          break 

Finally, close off the main() function and make sure main is called with this typical end to the Python program:

 if __name__ == "__main__":    main() 

[ LiB ]

Game Programming with Pyton, Lua and Ruby
Game Programming with Pyton, Lua and Ruby
Year: 2005
Pages: 133

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net