|[ LiB ]|
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:
Import the necessary libraries.
Define any necessary functions, the only one in this case being a Display_Message function for displaying splash text on the screen.
Define any game object classes, in this case SimpleSprite , Player , Obstacle , and FinishLine .
Create a main() function and set up Pygame.
Draw and update the necessary graphics utilizing groups and sprites within a while() loop.
Are you ready? Then break!
And the libraries are:
import os import sys import random import pygame from pygame.locals import *
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.
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()
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 )
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 ]|