One of the things I love about developing games is that once you have a basic version of the game working, you have full control over adding any enhancements that you can imagine! The exercise from the last day was to enhance our pyPong v1.0 game by adding additional features. Today I’m going to walk you through the code to make these enhancements:
-
- Change the Ball sprite from a red circle to an image file of a ball.
- Change the Player sprite from a black rectangle to an appropriate image.
- Use a custom font for the ScoreKeeper sprite.
- Add a background image.
- Every time a Ball bounces off a Player, increase the Ball speed by 1.
- Play background music while the game is running.
- Play a sound effect when the Ball bounces off a Player.
- Play a sound effect when a Player scores a point.
- Change control of Player 2 to a second joystick or the mouse.
- When the game ends, display a “Game Over” message in the window.
There are many ways to accomplish the same goals, so I’m not saying that the way I have implemented the enhancements is the only way to do it. You may have done it differently. If so, compare our approaches to understand the differences.
Pulling Together Resources
We’ve learned how to incorporate sound and images already, but you’ll note that I avoided doing this in the first version of the game. The reason for this is so that I could focus on the basic game mechanics rather than aesthetics for my first version. There is no point wasting time creating pretty graphics if you end up unable to get the basic game code working!
Now it’s time to jazz up the game, so as a first step I pulled together the images (ball.gif, background.gif, player1.gif, player2.gif, and gameover.gif), font (spaceage.ttf), background music (music.ogg), and sound effects (bounce.wav and score.wav). Pulling all of this together took a considerable amount of time. If you plan to distribute your game, be sure that any resources you use are not copyrighted.
Let’s look at the Sprite class revisions first, then the main game loop.
Ball Sprite Revisions
In the __init__() method of the original code, I had created a very plain circle to represent the Ball sprite.
1 2 3 4 5 6 7 |
# Set the image and rect attributes for the Ball self.image = pygame.Surface((20, 20)) self.image.fill((0, 0, 0)) self.image.set_colorkey((0,0,0)) pygame.draw.circle(self.image, (255, 0, 0), (10, 10), 10, 0) self.rect = self.image.get_rect() self.rect.center = (screen.get_width()/2,screen.get_height()/2) |
To replace this with an actual image of a ball actually takes less code:
1 2 3 4 5 |
# Load the ball image, and center the ball rect on the screen. self.image = pygame.image.load("ball.gif") self.image = self.image.convert() self.rect = self.image.get_rect() self.rect.center = (screen.get_width()/2,screen.get_height()/2) |
One other change I made was to add a new method called speedUp(). The purpose of this method is to increase the speed of the Ball sprite by 1 pixel. I’m going to call this method in my main game loop code every time the Ball collides with one of the Player sprites. This will increase the speed/difficulty of the game.
1 2 3 4 5 6 7 8 |
def speedUp(self): '''This method causes the ball to increase in x speed by 1 pixel''' # If the ball is moving right (i.e., dx is positive), then add 1. if self.dx > 0: self.dx += 1 # If the ball is moving left (i.e., dx is negative), then sub 1. else: self.dx -= 1 |
Player Sprite Revisions
In the original __init__() method, I used a simple black rectangle to represent each player:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Define the image attributes for a black rectangle. self.image = pygame.Surface((10, 100)) self.image = self.image.convert() self.image.fill((0, 0, 0)) self.rect = self.image.get_rect() # If we are initializing a sprite for player 1, # position it 10 pixels from screen left. if playerNum == 1: self.rect.left = 10 # Otherwise, position it 10 pixels from the right of the screen. else: self.rect.right = screen.get_width()-10 |
The code below loads a different image to represent the Player sprite (depending on whether it is player 1 or 2). The rest of the code in the Player class remains the same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# If we are initializing a sprite for player 1, load the (40x100) # player 1 image, and position it 10 pixels from screen left. if playerNum == 1: self.image = pygame.image.load("player1.gif") self.image = self.image.convert() self.rect = self.image.get_rect() self.rect.left = 10 # Otherwise, load the (40x100) player 2 image, and position it # 10 pixels from the right of the screen. else: self.image = pygame.image.load("player2.gif") self.image = self.image.convert() self.rect = self.image.get_rect() self.rect.right = screen.get_width()-10 # Center the player vertically in the window. self.rect.top = screen.get_height()/2 + 50 self.window = screen self.dy = 0 |
Again, the code for this enhancement is not much longer than the original, but will really improve the attractiveness of the game.
ScoreKeeper Sprite Revisions
To use a custom font, I simply changed the following line in the original __init__() method:
1 |
self.font = pygame.font.SysFont("Arial", 30) |
to:
1 |
self.font = pygame.font.Font("spaceage.ttf", 30) |
Main Game Loop Revisions
We’ve now taken care of enhancements 1, 2, and 3 on our list and implemented a method to help us with enhancement 5. The rest of the enhancements will happen in the main game loop code.
Let’s start with revising the IDEA sections:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# I - IMPORT AND INITIALIZE import pygame, pySprites pygame.init() pygame.mixer.init() def main(): '''This function defines the 'mainline logic' for our pyPong game.''' # D - DISPLAY screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("pyPong! v1.0") # ENTITIES # Background Image of pyPong Court background = pygame.image.load("background.gif") background = background.convert() screen.blit(background, (0, 0)) # Initialize Joystick objects. joysticks = [] for joystick_no in range(pygame.joystick.get_count()): stick = pygame.joystick.Joystick(joystick_no) stick.init() joysticks.append(stick) # Sprites for: ScoreKeeper label, End Zones, Ball, and Players score_keeper = pySprites.ScoreKeeper() ball = pySprites.Ball(screen) player1 = pySprites.Player(screen, 1) player1Endzone = pySprites.EndZone(screen,0) player2 = pySprites.Player(screen, 2) player2Endzone = pySprites.EndZone(screen,639) allSprites = pygame.sprite.Group(score_keeper, player1Endzone, \ player2Endzone, ball, player1, player2) # "Game Over" Image to Display After Game Loop Terminates gameover = pygame.image.load("gameover.gif") gameover = gameover.convert() # Background Music and Sound Effects pygame.mixer.music.load("music.ogg") pygame.mixer.music.set_volume(0.3) pygame.mixer.music.play(-1) boing = pygame.mixer.Sound("bounce.wav") boing.set_volume(0.5) yeahbaby = pygame.mixer.Sound("score.wav") yeahbaby.set_volume(0.9) # ACTION |
Since we’re going to add sound to the game, on line 4 we add code to initialize the pygame sound mixer module.
In the Entities (IDEA) section I am loading a background image instead of having a plain white background. I did not need to change any of the Sprite instantiation code. After this, I’ve created a gameover surface from an image containing the words “Game Over”. I’ll show this surface when the game loop terminates.
Finally, I’m loading background music with a volume level of 30% and having it play in a continuous loop. I’m also loading two sound effects so that I can play them as needed during the game action.
Ok, let’s look at the ALTER code revisions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# ASSIGN clock = pygame.time.Clock() keepGoing = True # Hide the mouse pointer pygame.mouse.set_visible(False) # LOOP while keepGoing: # TIME clock.tick(30) # EVENT HANDLING: Player 1 uses joystick, Player 2 uses arrow keys for event in pygame.event.get(): if event.type == pygame.QUIT: keepGoing = False elif event.type == pygame.JOYHATMOTION: player1.changeDirection(event.value) elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: player2.changeDirection((0, 1)) if event.key == pygame.K_DOWN: player2.changeDirection((0, -1)) # Check if player 1 scores (i.e., ball hits player 2 endzone) if ball.rect.colliderect(player2Endzone): score_keeper.player1Scored() yeahbaby.play() ball.changeDirection() # Check if player 2 scores (i.e., ball hits player 1 endzone) if ball.rect.colliderect(player1Endzone): score_keeper.player2Scored() yeahbaby.play() ball.changeDirection() # Check for game over (if a player gets 3 points) if score_keeper.winner(): pygame.mixer.music.fadeout(2000) keepGoing = False # Check if ball hits Player 1 or 2 # If so, change direction, and speed up the ball a little if ball.rect.colliderect(player1.rect) or\ ball.rect.colliderect(player2.rect): boing.play() ball.speedUp() ball.changeDirection() # REFRESH SCREEN allSprites.clear(screen, background) allSprites.update() allSprites.draw(screen) pygame.display.flip() # Display "Game Over" graphic and unhide the mouse pointer screen.blit(gameover, (150, 100)) pygame.display.flip() pygame.mouse.set_visible(True) # Close the game window after 3 seconds, so we can hear music fade-out pygame.time.delay(3000) pygame.quit() # Call the main function main() |
The main changes here occur in the collision detection if statements. I’ve added a line of code to each to play an appropriate sound effect if the ball is bounced off a player or if a player scores a point. Also, in the case where the Ball sprite collides with one of the Player sprites, I call my new speedUp() method in the Ball class to increase the speed of action. If the game has been won I fade out the background music before the game loop terminates.
Once the game loop terminates, I blit the “Game Over” graphic onto the screen.
Believe it or not, those simple changes cover enhancements 4, 5, 6, 7, 8, and 10! I’ll leave enhancement 9 as an exercise for you and a partner. Our pyPong v2.0 game looks (and sounds) much better. Yeah, baby!
Congratulations! At this point, you’ve learned all of the fundamental skills needed to develop your own games. You are now in an excellent position to continue learning additional features of Pygame on your own. The best way to improve your skills is to practice by building progressively more challenging games. What you create is limited only by your imagination, good luck!