Pygame Tutorial 3: Pong Step 1
Written by Collin Green â€” Version 1.0.1 â€” 2010-12-26
In this tutorial we will subclass the pygame sprite class to make our paddles and ball, then control them using the keyboard. At the end we will have two paddles that move and a ball that does absolutely nothing.
First things first. We are going to start with the same code we used in Tutorial 2, then modify it to work for us. You may want to copy it over, but be aware of the changes, particularly the additional imports, the background, the new sprite code, and the new rendering code. As usual we begin with the import code, but this time we want a couple extra libraries. We won’t need all these for step 1, but we are going to import them now so we don’t have to come back to this code block later. We will use the os module for our path manipulation when we load our images, then math and random for picking a random serving angle for the ball.
12 13 14 15 16 17 18 19
try: import sys, os, math, random import pygame from pygame.locals import * except ImportError, err: print "%s Failed to Load Module: %s" % (__file__, err) sys.exit(1)
The Sprite Classes – Paddle
This is our first interesting piece of code in these tutorials. In pygame, pretty much everything you see moving around the screen and interacting is a sprite or a subclass of a sprite. This base class provides all the useful behind-the-scenes functionality for rendering, joining and leaving render groups, etc. You can look at the pygame.sprite module docs for more info. From our point of view, a sprite has two important attributes: image and rect. Image is a pygame.Surface object, which is the base class for everything that can get rendered in pygame. Rect is a pygame.Rect, which represents a rectangle (who knew?) and has a handful of useful functions associated with it as well. Basically when pygame goes to render your sprite, it will take the surface in yoursprite.image and render it in the position and size defined by yoursprite.rect.
The first sprite class we are going to make will be our paddle. The paddles are pretty simple — little rectangles that can only move along the Y axis. There are lots of ways we could do this, but we are going to use velocity for the paddles and move them based on that. This means, instead of a keypress moving the paddles directly, it will change the velocity of the paddle. Every frame we will move the paddle based on its velocity, even if it is zero. This gives us two benefits – we can test the velocity of the paddle at any time, which will be very useful later when adding spin to the ball, and we can have the ‘expected’ behavior of the paddle not moving when both the up and down keys are pressed, instead of just giving preference to one of them. Be aware though, the way we are doing this depends on the frame rate, so we will be limiting that later on in our run method. (In a more complicated program you can amend your movement code to scale with the time between frames, so users will varying frame rates will always have a consistent experience.)The other thing we will do is add a move function to the paddles that will automatically keep them inside the screen. This will make moving the paddles extremely easy, and easy is good.
You’ll need to download the tutorial zip to get the images or just find/create your own images for the paddles and later on the ball. Place them in a folder below the script named images.
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
class Paddle(pygame.sprite.Sprite): """A paddle sprite. Subclasses the pygame sprite class. Handles its own position so it will not go off the screen.""" def __init__(self, xy): # initialize the pygame sprite part pygame.sprite.Sprite.__init__(self) # set image and rect self.image = pygame.image.load(os.path.join('images','pong_paddle.gif')) self.rect = self.image.get_rect() # set position self.rect.centerx, self.rect.centery = xy # the movement speed of our paddle self.movementspeed = 5 # the current velocity of the paddle -- can only move in Y direction self.velocity = 0 def up(self): """Increases the vertical velocity""" self.velocity -= self.movementspeed def down(self): """Decreases the vertical velocity""" self.velocity += self.movementspeed def move(self, dy): """Move the paddle in the y direction. Don't go out the top or bottom""" if self.rect.bottom + dy > 400: self.rect.bottom = 400 elif self.rect.top + dy < 0: self.rect.top = 0 else: self.rect.y += dy def update(self): """Called to update the sprite. Do this every frame. Handles moving the sprite by its velocity""" self.move(self.velocity)
The Game Class
This is our Game class from tutorial 2 with some extras added in for this game. Notably, I changed the window size to 800 x 400, added the KEYUP event, and all the sprite stuff starting at line 92. The first piece of new code is for the background. I’m creating a pygame surface filling it with white, drawing a black line down the middle, and blitting (drawing) it onto the main window. You may want to have a look at the surface or draw documentation if you don’t understand what is happening. Next, I created a RenderUpdates sprite group. This is a pygame list you add your sprites to that is needed for the kind of ‘dirty update’ rendering we will use in the main loop. Lastly, we create two Paddles, self.leftpaddle and self.rightpaddle, give them their initial coordinates on either side of the screen, and add them to the render group.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
class Game(object): """Our game object! This is a fairly simple object that handles the initialization of pygame and sets up our game to run.""" def __init__(self): """Called when the the Game object is initialized. Initializes pygame and sets up our pygame window and other pygame tools that we will need for more complicated tutorials.""" # load and set up pygame pygame.init() # create our window self.window = pygame.display.set_mode((800, 400)) # clock for ticking self.clock = pygame.time.Clock() # set the window title pygame.display.set_caption("Pygame Tutorial 3 - Pong") # tell pygame to only pay attention to certain events # we want to know if the user hits the X on the window, and we # want keys so we can close the window with the esc key pygame.event.set_allowed([QUIT, KEYDOWN, KEYUP]) # make background -- all white, with black line down the middle self.background = pygame.Surface((800,400)) self.background.fill((255,255,255)) # draw the line vertically down the center pygame.draw.line(self.background, (0,0,0), (400,0), (400,400), 2) self.window.blit(self.background, (0,0)) # flip the display so the background is on there pygame.display.flip() # a sprite rendering group for our ball and paddles self.sprites = pygame.sprite.RenderUpdates() # create our paddles and add to sprite group self.leftpaddle = Paddle((50,200)) self.sprites.add(self.leftpaddle) self.rightpaddle = Paddle((750,200)) self.sprites.add(self.rightpaddle)
The Event Loop – Run()
This is the same as the run method in tutorial 2, but with our new rendering code put in. First we will run each sprite’s update method, then use the rendergroup to draw them. Notice in lines 134 the sprite group’s draw method returns a list of areas that have changed, which we then pass on to the update method in line 137. This means only those areas will be redrawn, which is far more efficient than re-rendering the entire window if you have a lot going on. It will have almost no effect for us, but it is good practice to start with because when you do a real project you’ll be much better off using it. As mentioned above in the movement discussion, we need a consistent frame rate for the paddles to move correctly, so we are going to cap our frame rate using the pygame clock. The ‘right’ way to do it is to pass the time since the last frame to the update methods and scale the movement from there, but since our game is so simple almost any computer can run this over 60 fps, so capping it there should work for us and keep the tutorial more simple.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
def run(self): """Runs the game. Contains the game loop that computes and renders each frame.""" print 'Starting Event Loop' running = True # run until something tells us to stop while running: # tick pygame clock # you can limit the fps by passing the desired frames per seccond to tick() self.clock.tick(60) # handle pygame events -- if user closes game, stop running running = self.handleEvents() # update the title bar with our frames per second pygame.display.set_caption('Pygame Tutorial 3 - Pong %d fps' % self.clock.get_fps()) # update our sprites for sprite in self.sprites: sprite.update() # render our sprites self.sprites.clear(self.window, self.background) # clears the window where the sprites currently are, using the background dirty = self.sprites.draw(self.window) # calculates the 'dirty' rectangles that need to be redrawn # blit the dirty areas of the screen pygame.display.update(dirty) # updates just the 'dirty' areas print 'Quitting. Thanks for playing'
HandleEvents – Processing User Input
Just like the others, this starts with the tutorial 2 handleEvents method, then we add some additional code. At the top we have the quit code if the user hits the X or presses the escape key. After that, however, is the interesting stuff. We are listening for both keydown and keyup events, and we need to handle the accordingly. We want the paddle to move as long as the user is holding the button, and if they press both the up and down keys at the same time, the paddle to not move at all. In the keydown block, we call the paddles’ up methods for W and the up arrow, and the down methods for S and the down arrow. On keyup, we simple do the opposite to undo the change we made to the velocity. Have a look at the code below, then glance back up at the Paddle class to see how it fits together. If you download the source you can also see a commented out piece of code that does the same thing in a different way (using buffered input instead).
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
def handleEvents(self): """Poll for PyGame events and behave accordingly. Return false to stop the event loop and end the game.""" # poll for pygame events for event in pygame.event.get(): if event.type == QUIT: return False # handle user input elif event.type == KEYDOWN: # if the user presses escape, quit the event loop. if event.key == K_ESCAPE: return False # paddle control if event.key == K_w: self.leftpaddle.up() if event.key == K_s: self.leftpaddle.down() if event.key == K_UP: self.rightpaddle.up() if event.key == K_DOWN: self.rightpaddle.down() elif event.type == KEYUP: # paddle control if event.key == K_w: self.leftpaddle.down() if event.key == K_s: self.leftpaddle.up() if event.key == K_UP: self.rightpaddle.down() if event.key == K_DOWN: self.rightpaddle.up() return True
Running the Script
The last thing is the standard tutorial ending that creates the game object and calls run when you actually run the script.
209 210 211 212
# create a game and run it if __name__ == '__main__': game = Game() game.run()
Result and Download
Our table and our paddles:
The paddle image:
Pygame Tutorial 3 - Pong (1443)