Because we have a nice clean structure to our program, the only things that need to change from the version we created in part I are the GameObject class and the definition and call to the processEvents routine.
Changes to the main routine
To accomodate the new, fancier event processing for our game,
we'll need to make a couple of small changes to our main
routine:
Thus our new global variable section now looks like this:
# ===================================================================== # GLOBAL VARIABLES # - list all variables that need to be globally accessible # define the size of the pygame display window scrSize = scrWidth, scrHeight = 640,480 gameScreen = None # the main display screen backImage = None # the loaded background image scrRefreshRate = 40 # the pause (in milliseconds) between updates keepPlaying = True # flag to identify if the game should continue |
And our new main routine now looks like this:
# ===================================================================== # MAIN GAME CONTROL ROUTINE # - sets up the game and runs the main game update loop # until instructed to quit def main(): # identify any global variables the main routine needs to access global gameScreen, backImage, scrRefreshRate, keepPlaying # initialize pygame and the game's display screen gameSetup() # create the list of screen update sections (rectangles) updateSections = None # create an array of objects to add to the display, # giving each of them # an image, xcoord, ycoord, facing, and speed gameObjList = [ GameObject('ship.gif', 80, 60, 270, 2), GameObject('ship.gif', 560, 60, 180, 0), GameObject('ship.gif', 560, 420, 90, 1) ] # indicate which game object is currently 'selected' selectedObj = 0 gameObjList[selectedObj].changeSelected(True) # create a group for the game objects objGroup = pygame.sprite.RenderUpdates(*gameObjList) # run the main game loop keepPlaying = True while keepPlaying: # handle any pending events # (processEvents needs the list of objects that might # be affected, and which object is currently selected) selectedObj = processEvents(gameObjList, selectedObj) # update the display of the object groups objGroup.clear(gameScreen, backImage) # run the updates on each object in the group objGroup.update() # update the display rectangles updateSections = objGroup.draw(gameScreen) # update the buffered display pygame.display.update(updateSections) # switch to the new display image pygame.display.flip() # pause before initiating the next loop cycle pygame.time.delay(scrRefreshRate) |
Changing the ship constructor
Our new, fancier ship controls mean that we have to keep track
of a bit more information about each ship (game object), which
means we need to spruce up our constructor a bit:
# ===================================================================== # GAME OBJECT # controls the basic movable in-game objects (ships in this case) # # Each game object has several properties: # # origImage: the loaded image to represent that object # image: the current display image for the object # (rotated and zoomed appropriately from the original) # position = x,y: the x,y coordinates of the object # facingDir: the direction the object is currently facing # velocity: the horizontal/vertical distance covered # by the object each game step # scale: the scaling (zoom) factor currently used for the object # selected: a true/false value indicating if the object is # currently selected class GameObject(pygame.sprite.Sprite): # the constructor (initialization routine) for the # movable game objects def __init__(self, image, x, y, direction, speed): # initialize a pygame sprite for the object pygame.sprite.Sprite.__init__(self) # load an image for the object self.origImage = pygame.image.load(image) self.image = self.origImage # set up the initial position for the object self.position = self.x, self.y = x, y # set up the initial direction for the object self.facingDir = direction # set up the initial scale (zoom) for the object self.scale = 1.0 # set up the initial speed for the object self.velocity = 1 # initially treat the object as unselected self.selected = False |
Changing the ship constructor
Our new, fancier ship controls also mean that our update routine
needs to be a bit more complex:
# the update routine adjusts the object's current position and # image based on its speed and direction def update(self): # calculate the object's new position based on its old position, # and its current facing and velocity if (self.facingDir == 0): self.x = self.x + self.velocity self.y = self.y - self.velocity elif (self.facingDir == 90): self.x = self.x - self.velocity self.y = self.y - self.velocity elif (self.facingDir == 180): self.x = self.x - self.velocity self.y = self.y + self.velocity elif (self.facingDir == 270): self.x = self.x + self.velocity self.y = self.y + self.velocity self.position = self.x, self.y # update (rotate and zoom) the image for the object self.image = pygame.transform.rotozoom(self.origImage, self.facingDir, self.scale) # position the image correctly self.rect = self.image.get_rect() self.rect.center = self.position |
Changing the ship selected
Eventually we might want to have the currently selected ship look
differently (e.g. outline it, highlight it, etc) so the player can
see visually which one is currently "it".
That means we should include a routine in the GameObject class to change/store whether or not the current ship is selected.
This routine would get called by the processEvents routine whenever a player hit the TAB key, and here I'm assuming processEvents would send along a flag specifying whether this ship was now selected or deselected.
# the object selection routine notifies the object # that it has just been selected or deselected def changeSelected(self, selFlag): # if the passed flag is True then store the fact that # this object is now the selected one if selFlag: self.selected = True # otherwise store the fact that this object is no # longer the selected one else: self.selected = False |
Changing the ship facing
We will also need a routine that changes the direction of
the ship when instructed to turn left or right.
This routine would get called by the processEvents routine whenever a player hit the LEFT or RIGHT arrow, and here I'm assuming processEvents would send along a letter (l or r) to indicate which direction to turn.
# the turning routine turns the ship 90 degrees clockwise if # the turning direction is right ('r') or 90 degrees # counterclockwise if the turning direction is left ('l') def changeFacing(self, dir): # if the direction is left then # turn the object 90 degrees counterclockwise if (dir == 'l'): self.facingDir = self.facingDir + 90 if self.facingDir >= 360: self.facingDir = self.facingDir - 360 # otherwise, if the direction is right then # turn the object 90 degrees counterclockwise elif (dir == 'r'): self.facingDir = self.facingDir - 90 if self.facingDir < 0: self.facingDir = self.facingDir + 360 |
Changing the ship velocity
Needless to say, we also need a routine that alters the ship's velocity
when instructed to do so (again by ProcessEvents).
Here I'm assuming processEvents would send along an = or - character specifying whether the ship should be sped up or slowed down.
# the speed change routine increases the object's velocity # if the change is '+', or decreases the velocity # if the change is '-' def changeSpeed(self, change): # if the change is '+' then double the current speed # (or increase to 1 if the speed used to be 0) if (change == '+'): self.velocity = self.velocity * 2 if self.velocity == 0: self.velocity = 1 # otherwise, if the change is '-' then cut the # current speed in half elif (change == '-'): self.velocity = self.velocity / 2 |
Changing the ship size
Finally, we need a routine that alters the ship's size (scaling
or zoom factor)
when instructed to do so (again by ProcessEvents).
Here I'm assuming processEvents would send along an = or - character specifying whether the ship should be scaled up or down.
# the zoom routine zooms in on the object (makes it larger) # if the scale is '+' or zooms out (makes it smaller) # if the scale is '-' def changeScale(self, scale): # zoom in on the object if the scale is '+' if (scale == '+'): self.scale = self.scale * 1.25 # otherwise zoom out if the scale is '-' elif (scale == '-'): self.scale = self.scale / 1.25 |
Event processing routine
Now, the event processing routine needs a major overhaul to
respond to all the different events, calling the right GameObject routine
in each case:
# ===================================================================== # EVENT HANDLING ROUTINE # - processes any pending in-game events, # returns which object is currently selected # (since this can be changed by some events) # the routine expects to be given a list of the game objects # currently available (objList) and which object is # currently selected/controlled by the player (selObj) def processEvents(objList, selObj): # specify which global variables the routine needs access to global keepPlaying # process each pending event for event in pygame.event.get(): # if the user closed the window set keepPlaying to False # to tell the game to quit playing if event.type == pygame.QUIT: keepPlaying = False # check if the user has pressed a key elif event.type == pygame.KEYDOWN: # the escape and q keys quit the game if event.key == pygame.K_ESCAPE: keepPlaying = False elif event.key == pygame.K_q: keepPlaying = False # the left and right arrows turn the currently selected ship # counterclockwise or clockwise, respectively elif event.key == pygame.K_LEFT: print 'turning left with ship ', selObj objList[selObj].changeFacing('l') elif event.key == pygame.K_RIGHT: print 'turning right with ship ', selObj objList[selObj].changeFacing('r') # the up and down arrows cause the currently selected ship # to speed up or slow down, respectively elif event.key == pygame.K_UP: print 'speeding up ship ', selObj objList[selObj].changeSpeed('+') elif event.key == pygame.K_DOWN: print 'slowing down ship ', selObj objList[selObj].changeSpeed('-') # the plus and minus keys zoom in/out on the currently # selected ship elif event.key == pygame.K_EQUALS: print 'zooming in on ship ', selObj objList[selObj].changeScale('+') elif event.key == pygame.K_MINUS: print 'zooming out on ship ', selObj objList[selObj].changeScale('-') # the tab key scrolls through the list of ships, # changing which one is currently selected elif event.key == pygame.K_TAB: objList[selObj].changeSelected(False) selObj = selObj + 1 if (selObj >= len(objList)): selObj = 0 objList[selObj].changeSelected(True) print 'selected ship ', selObj # return the currently selected object number # (may have been updated by one of the events processed) return selObj |
Complete version
Hopefully the game is now fully functional, a complete copy of the code
is given here:
#! /usr/bin/python import sys, pygame # ===================================================================== # GLOBAL VARIABLES # - list all variables that need to be globally accessible # define the size of the pygame display window scrSize = scrWidth, scrHeight = 640,480 gameScreen = None # the main display screen backImage = None # the loaded background image scrRefreshRate = 40 # the pause (in milliseconds) between updates keepPlaying = True # flag to identify if the game should continue # ===================================================================== # SETUP ROUTINE # - initializes the pygame display screen and background image def gameSetup(): # specify the global variables the setup routine needs to access global gameScreen, scrSize, backImage # initialize pygame pygame.init() # initialize the display screen gameScreen = pygame.display.set_mode(scrSize) # load and display the background image backImage = pygame.image.load('backdrop.gif') gameScreen.blit(backImage, (0,0)) # ===================================================================== # GAME OBJECT # controls the basic movable in-game objects (ships in this case) # # Each game object has several properties: # # origImage: the loaded image to represent that object # image: the current display image for the object # (rotated and zoomed appropriately from the original) # position = x,y: the x,y coordinates of the object # facingDir: the direction the object is currently facing # velocity: the horizontal/vertical distance covered # by the object each game step # scale: the scaling (zoom) factor currently used for the object # selected: a true/false value indicating if the object is # currently selected # # Each game object also has several actions that can be applied to it: # # __init__ : the initialization routine for the object # update: the routine applied each step to update the # object's current location and image # changeFacing: a routine to provide a new facing direction # for the object # changeSpeed: a routine to increase or decrease the object's # current velocity # changeScale: a routine to zoom in/out on the object # (scaling the size of its image) class GameObject(pygame.sprite.Sprite): # the constructor (initialization routine) for the # movable game objects def __init__(self, image, x, y, direction, speed): # initialize a pygame sprite for the object pygame.sprite.Sprite.__init__(self) # load an image for the object self.origImage = pygame.image.load(image) self.image = self.origImage # set up the initial position for the object self.position = self.x, self.y = x, y # set up the initial direction for the object self.facingDir = direction # set up the initial scale (zoom) for the object self.scale = 1.0 # set up the initial speed for the object self.velocity = 1 # initially treat the object as unselected self.selected = False # the update routine adjusts the object's current position and # image based on its speed and direction def update(self): # calculate the object's new position based on its old position, # and its current facing and velocity if (self.facingDir == 0): self.x = self.x + self.velocity self.y = self.y - self.velocity elif (self.facingDir == 90): self.x = self.x - self.velocity self.y = self.y - self.velocity elif (self.facingDir == 180): self.x = self.x - self.velocity self.y = self.y + self.velocity elif (self.facingDir == 270): self.x = self.x + self.velocity self.y = self.y + self.velocity self.position = self.x, self.y # update (rotate and zoom) the image for the object self.image = pygame.transform.rotozoom(self.origImage, self.facingDir, self.scale) # position the image correctly self.rect = self.image.get_rect() self.rect.center = self.position # the turning routine turns the ship 90 degrees clockwise if # the turning direction is right ('r') or 90 degrees # counterclockwise if the turning direction is left ('l') def changeFacing(self, dir): # if the direction is left then # turn the object 90 degrees counterclockwise if (dir == 'l'): self.facingDir = self.facingDir + 90 if self.facingDir >= 360: self.facingDir = self.facingDir - 360 # otherwise, if the direction is right then # turn the object 90 degrees counterclockwise elif (dir == 'r'): self.facingDir = self.facingDir - 90 if self.facingDir < 0: self.facingDir = self.facingDir + 360 # the speed change routine increases the object's velocity # if the change is '+', or decreases the velocity # if the change is '-' def changeSpeed(self, change): # if the change is '+' then double the current speed # (or increase to 1 if the speed used to be 0) if (change == '+'): self.velocity = self.velocity * 2 if self.velocity == 0: self.velocity = 1 # otherwise, if the change is '-' then cut the # current speed in half elif (change == '-'): self.velocity = self.velocity / 2 # the zoom routine zooms in on the object (makes it larger) # if the scale is '+' or zooms out (makes it smaller) # if the scale is '-' def changeScale(self, scale): # zoom in on the object if the scale is '+' if (scale == '+'): self.scale = self.scale * 1.25 # otherwise zoom out if the scale is '-' elif (scale == '-'): self.scale = self.scale / 1.25 # the object selection routine notifies the object # that it has just been selected or deselected def changeSelected(self, selFlag): # if the passed flag is True then store the fact that # this object is now the selected one if selFlag: self.selected = True # otherwise store the fact that this object is no # longer the selected one else: self.selected = False # ===================================================================== # EVENT HANDLING ROUTINE # - processes any pending in-game events, # returns which object is currently selected # (since this can be changed by some events) # the routine expects to be given a list of the game objects # currently available (objList) and which object is # currently selected/controlled by the player (selObj) def processEvents(objList, selObj): # specify which global variables the routine needs access to global keepPlaying # process each pending event for event in pygame.event.get(): # if the user closed the window set keepPlaying to False # to tell the game to quit playing if event.type == pygame.QUIT: keepPlaying = False # check if the user has pressed a key elif event.type == pygame.KEYDOWN: # the escape and q keys quit the game if event.key == pygame.K_ESCAPE: keepPlaying = False elif event.key == pygame.K_q: keepPlaying = False # the left and right arrows turn the currently selected ship # counterclockwise or clockwise, respectively elif event.key == pygame.K_LEFT: print 'turning left with ship ', selObj objList[selObj].changeFacing('l') elif event.key == pygame.K_RIGHT: print 'turning right with ship ', selObj objList[selObj].changeFacing('r') # the up and down arrows cause the currently selected ship # to speed up or slow down, respectively elif event.key == pygame.K_UP: print 'speeding up ship ', selObj objList[selObj].changeSpeed('+') elif event.key == pygame.K_DOWN: print 'slowing down ship ', selObj objList[selObj].changeSpeed('-') # the plus and minus keys zoom in/out on the currently # selected ship elif event.key == pygame.K_EQUALS: print 'zooming in on ship ', selObj objList[selObj].changeScale('+') elif event.key == pygame.K_MINUS: print 'zooming out on ship ', selObj objList[selObj].changeScale('-') # the tab key scrolls through the list of ships, # changing which one is currently selected elif event.key == pygame.K_TAB: objList[selObj].changeSelected(False) selObj = selObj + 1 if (selObj >= len(objList)): selObj = 0 objList[selObj].changeSelected(True) print 'selected ship ', selObj # return the currently selected object number # (may have been updated by one of the events processed) return selObj # ===================================================================== # MAIN GAME CONTROL ROUTINE # - sets up the game and runs the main game update loop # until instructed to quit def main(): # identify any global variables the main routine needs to access global gameScreen, backImage, scrRefreshRate, keepPlaying # initialize pygame and the game's display screen gameSetup() # create the list of screen update sections (rectangles) updateSections = None # create an array of objects to add to the display, # giving each of them # an image, xcoord, ycoord, facing, and speed gameObjList = [ GameObject('ship.gif', 80, 60, 270, 2), GameObject('ship.gif', 560, 60, 180, 0), GameObject('ship.gif', 560, 420, 90, 1) ] # indicate which game object is currently 'selected' selectedObj = 0 gameObjList[selectedObj].changeSelected(True) # create a group for the game objects objGroup = pygame.sprite.RenderUpdates(*gameObjList) # run the main game loop keepPlaying = True while keepPlaying: # handle any pending events # (processEvents needs the list of objects that might # be affected, and which object is currently selected) selectedObj = processEvents(gameObjList, selectedObj) # update the display of the object groups objGroup.clear(gameScreen, backImage) # run the updates on each object in the group objGroup.update() # update the display rectangles updateSections = objGroup.draw(gameScreen) # update the buffered display pygame.display.update(updateSections) # switch to the new display image pygame.display.flip() # pause before initiating the next loop cycle pygame.time.delay(scrRefreshRate) # ===================================================================== # INITIATE THE GAME # - calls the main() routine if __name__ == "__main__": main() |