PyGame Pong

A simple version of the classic game!

Introduction

Pygame PongIn this section we will use some of the concepts looked at in the previous sections to build a basic version of the classic game Pong in PyGame. The intention of this version is to keep everything as simple as possible so you may more easily understand everything that is going on. This version looks at recreating the game with more functionality and making better use of good programming patterns to help make it more easily extensible. (Start with this tutorial however as it goes into more detail on the actual processing.)

Getting Set Up

Pygame template windowBefore we begin, let's create a new file (call it pong.py) and copy in the template code from the previous section.

Once you've saved your file with the template code in it, run the file to make sure you've copied it in ok.

Working through the rest of this section you will progressively add code to this file to build up the game.

Before we begin though, let's change the name of the game on line 15 from My Game! to Pong!.

We also want to make the window a little bigger so we have more room to play the game in. Change the WINDOW_WIDTH to 800 and the WINDOW_HEIGHT to 600 on lines 11 and 12 (or any other size that you prefer).

Development Strategy

When building up a game like this it helps if you have the right strategy. What we are after is a methodical approach that allows us to progressively build the game up, testing constantly as we go. That way, if you make a mistake and break things, you will find out earlier and it will be quicker and easier to fix.

What you want to be doing is constantly asking, "what is the next easiest step I could take?" and then implement that, test it and repeat. If you take this approach then building the game becomes a set of simple steps rather than one big scary problem. Don't worry if you don't know what all the steps will be yet. It's like solving a puzzle, the more pieces you get into place, the easier the later ones become.

When making a graphical program using a library such as PyGame, sometimes the errors you get can be a little cryptic. If this happens then Googling the error can often give you hints as to what might be wrong.

Drawing the Game Elements

A good place to start is to get your game elements drawn on the screen when you only have a few simple elements like Pong has. The elements will be static and won't do anything but we can then progressively make them active. The easiest one is the center line so we will start with that.

  • # Colours
  • BACKGROUND = (255, 255, 255)
  • ELEMENTCOLOUR = (100, 100, 100)
  •     WINDOW.fill(BACKGROUND)
  •     pygame.draw.line(WINDOW, ELEMENTCOLOUR, (WINDOW_WIDTH // 2, 0), (WINDOW_WIDTH // 2, WINDOW_HEIGHT), 2)
  •     pygame.display.update()

Pygame Pong center lineIf you save and test the program you should now have a single line going down the middle of the screen.

You will also notice that we have used the double slash ( // ) instead of the normal single slash ( / ) for the division. The double slash operator works the same but returns the result as an integer (whole number). This is important here (and in many other calculations we will do) as when we are providing coordinates they need to be whole numbers and not fractions.

We could have just hard coded the values for the colour of the line and the coordinates of the line directly into the draw command which would have made things simpler. By setting up variables at the top of our code and using calculations to work out the coordinates for the center of the window instead we make it much easier to tweak these settings later on. If you decide you want to change the colour, you don't have to go searching through the code for every place it is used. Similarly, if you decide you want to change the size of the window, you won't need to update values all over your code to accommodate the new dimensions. This is good programmign practice and it is a little bit more work up front but it makes your easier to understand and much easier to tweak to get just the right game play.

Now let's add the ball and the paddles. Just after "pygame.display.set_caption('Pong!') and just before def main () : add the following lines. The PADDLEINSET is going to be how far in from the edge of the window the paddles are :

  • pygame.display.set_caption('Pong!')
  •  
  • # Game Element Variables
  • PADDLEINSET = 20
  • PADDLEWIDTH = 10
  • PADDLEHEIGHT = 60
  • BALLSIZE = 10

It is common convention to name variables that shouldn't change within the program in all UPPERCASE. This way they stand out. These variables should all be listed at the top of the script so they are easy to find.

Because the variables indicating where the paddles and the ball are on the screen will change we will set these up within the main function (and they will be named not in all uppercase).

  •   looping = True
  •  
  •   leftPaddleY = 50
  •   rightPaddleY = 50
  •   ballX = WINDOW_WIDTH // 2
  •   ballY = WINDOW_HEIGHT // 2

Next we will create Rect objects for the paddles :

  •     # Processing
  •  
  •     leftPaddleRect = pygame.Rect(PADDLEINSET, leftPaddleY, PADDLEWIDTH, PADDLEHEIGHT)
  •     rightPaddleRect = pygame.Rect(WINDOW_WIDTH - PADDLEINSET - PADDLEWIDTH, rightPaddleY, PADDLEWIDTH, PADDLEHEIGHT)

Pygame Pong paddle mathematicsThe maths here may seem a little confusing but remember that when defining a rectangle in Pygame we are specifying the coordinates of the top left corner.

(In the diagram to the right, the red circles indicate the points we are going to calculate the coordinates of.)

And finally, draw the elements :

  •     WINDOW.fill(BACKGROUND)
  •     pygame.draw.line(WINDOW, ELEMENTCOLOUR, ((WINDOW_WIDTH //2) - 1, 0), ((WINDOW_WIDTH // 2) - 1, WINDOW_HEIGHT), 2)
  •     pygame.draw.rect(WINDOW, ELEMENTCOLOUR, leftPaddleRect)
  •     pygame.draw.rect(WINDOW, ELEMENTCOLOUR, rightPaddleRect)
  •     pygame.draw.circle(WINDOW, ELEMENTCOLOUR, (int(ballX), int(ballY)), BALLSIZE)
  •     pygame.display.update()

Pygame Pong all elementsIf you save and test your program, you should now have all the game elements shown on the screen.

Make the Paddles Move

The next easiest step is to make the paddles move. Because they only move up and down we only need to adjust the Y values.

  •         pygame.quit()
  •         sys.exit()
  •  
  •     pressed = pygame.key.get_pressed()
  •     if (pressed[K_w] :
  •       leftPaddleY -= 2
  •     elif (pressed[K_s] :
  •       leftPaddleY += 2
  •     if (pressed[K_UP] :
  •       leftPaddleY -= 2
  •     elif (pressed[K_DOWN] :
  •       leftPaddleY += 2

If you save and test your program, you should now be able to move the paddles using the w, s, UP and DOWN keys.

We have hard coded the values for how much to move the paddles here (which you will remember up above I said was bad practice. See how if we wanted to change the speed of the paddle movement we would have to change all 4 values. A better way to do this would be to have a PADDLESPEED variable set up the top of the code with the others and then to refer to it instead. You may want to have a go at modifying the program to do just this.

You will notice that at the moment the paddles aren't constrained. That is, they can move up and down indefinitely and go off the screen. We can fix that by putting in a few if statements to work out if the paddles have reached the top or bottom edges and not let them go past those.

  •     # Processing
  •  
  •     if leftPaddleY < 0 :
  •       leftPaddleY = 0
  •     if leftPaddleY > WINDOW_HEIGHT - PADDLEHEIGHT :
  •       leftPaddleY = WINDOW_HEIGHT - PADDLEHEIGHT
  •     if rightPaddleY < 0 :
  •       rightPaddleY = 0
  •     if rightPaddleY > WINDOW_HEIGHT - PADDLEHEIGHT :
  •       rightPaddleY = WINDOW_HEIGHT - PADDLEHEIGHT
  •  
  •     leftPaddleRect = pygame.Rect(PADDLEINSET, leftPaddleY, PADDLEWIDTH, PADDLEHEIGHT)

Pygame Pong paddle edgesWhat we are doing here is detecting if the paddle coordinates go beyond the top or bottom of the screen and making them equal to the top or the bottom of the screen if that is the case. We could have tested for if the coordinates are equal to the top or bottom of the screen but this is not as safe. If you change the speed of the paddles movement then the coordinates may never exactly equal the edge of the screen (one step they will be just before the edge and the next step just past the edge).

What we have referred to here is what is called defensive programming. This is where you set your code up so that it will more robustly handle any unexpected events. Doing this can save you a lot of time trying to account for weird behaviour later on.

Make the Ball Move

The last bit is to get the ball to move.

First off we will add two variables to track the momentum of the ball on the X and Y axis. Add these to your variables just inside the main function :

  •   looping = True
  •  
  •   leftPaddleY = 50
  •   rightPaddleY = 50
  •   ballX = WINDOW_WIDTH // 2
  •   ballY = WINDOW_HEIGHT // 2
  •   ballXMomentum = 1
  •   ballYMomentum = 1
  •  

Pygame Pong ball scenariosNext we need to think about the different situations the ball can get into and how it should react to them. There are three events that require something to happen :

  • The ball has hit the top or bottom of the court - in this case the ball bounces off.
  • The ball has hit either side edge of the court - the player has not hit the ball with their paddle and so they lose.
  • The ball has hit a paddle - the ball bounces back in the other direction.

Now we may progressively add code to handle each of these scenarios. There is a bit of mathematics and logic involved in detecting these. We could have just hard coded the values but using the calculations allows us to easily tweak values later such as the size of the paddles, window, ball etc without needing to go back and recalculate all of these values. It is a bit more work up front but it will save you time and effort later on.

We will continue with our methodical approach, building and testing each scenario before moving onto the next one. We will also code them in order from easiest to implement to hardest to implement.

The easiest events to detect are the top or bottom of the court. All we need to do is check if the variable ballY is less than the radius of the ball (the variable BALLSIZE) or if it is greater than WINDOW_HEIGHT (representing the bottom of the court) - the ball radius (BALLSIZE).

  •     if rightPaddleY > WINDOW_HEIGHT - PADDLEHEIGHT :
  •       rightPaddleY = WINDOW_HEIGHT - PADDLEHEIGHT
  •  
  •     if ballY < BALLSIZE : # ball has hit the top
  •       ballYMomentum = 1
  •     if ballY > WINDOW_HEIGHT - BALLSIZE : # ball has hit the bottom
  •       ballYMomentum = -1
  •  
  •     leftPaddleRect = pygame.Rect(PADDLEINSET, leftPaddleY, PADDLEWIDTH, PADDLEHEIGHT)

The ball doesn't move at the moment. Let's fix that by updating the balls X and Y coordinates by adding the respective Momentums.

  •     leftPaddleRect = pygame.Rect(PADDLEINSET, leftPaddleY, PADDLEWIDTH, PADDLEHEIGHT)
  •     rightPaddleRect = pygame.Rect(WINDOW_WIDTH - PADDLEINSET - PADDLEWIDTH, rightPaddleY, PADDLEWIDTH, PADDLEHEIGHT)
  •  
  •     ballX = ballX + ballXMomentum
  •     ballY = ballY + ballYMomentum
  •  
  •     WINDOW.fill(BACKGROUND)
  •     pygame.draw.line(WINDOW, ELEMENTCOLOUR, ((WINDOW_WIDTH //2) - 1, 0), ((WINDOW_WIDTH // 2) - 1, WINDOW_HEIGHT), 2)

If you save and run the game now the ball should do down and to the right bouncing off the bottom of the window.

Can you change the starting values for some of the variables to test the ball starting in the other three directions (right and up, left and up, left and down) to make sure it bounces off the top and bottom correctly in all directions?

Next easiest is if the ball has gone past either of the paddles and hit the side edges. It is a similar process however we are working with ballX instead of ballY. We will also reset the ball to the center of the court if either of these events occur.

  •     if ballY > WINDOW_HEIGHT - BALLSIZE : # ball has hit the bottom
  •       ballYMomentum = -1
  •  
  •     if ballX <= BALLSIZE : # Left player loses
  •       ballX = WINDOW_WIDTH // 2
  •       ballY = WINDOW_HEIGHT // 2
  •       ballYMomentum = 1
  •       ballXMomentum = 1
  •     if ballX >= WINDOW_WIDTH - BALLSIZE : # Right player loses
  •       ballX = WINDOW_WIDTH // 2
  •       ballY = WINDOW_HEIGHT // 2
  •       ballYMomentum = 1
  •       ballXMomentum = -1
  •  
  •     leftPaddleRect = pygame.Rect(PADDLEINSET, leftPaddleY, PADDLEWIDTH, PADDLEHEIGHT)

If you save and run the game now, you should notice that the ball will travel to the edge of the screen then reset to the middle of the court heading in the opposite direction, over and over. Now all that is left is to detect if the paddles have hit the ball and bounce the ball accordingly.

  •       ballYMomentum = 1
  •       ballXMomentum = -1
  •  
  •     if ballX <= PADDLEINSET + PADDLEWIDTH and ballX > PADDLEINSET : # work out if left paddle has hit the ball
  •       if leftPaddleY < ballY and leftPaddleY + PADDLEHEIGHT > ballY :
  •         ballXMomentum = 1
  •     if ballX >= WINDOW_WIDTH - PADDLEINSET - PADDLEWIDTH and ballX < WINDOW_WIDTH - PADDLEINSET : # work out if right paddle has hit the ball
  •       if rightPaddleY < ballY and rightPaddleY + PADDLEHEIGHT > ballY :
  •         ballXMomentum = -1
  •  
  •     leftPaddleRect = pygame.Rect(PADDLEINSET, leftPaddleY, PADDLEWIDTH, PADDLEHEIGHT)

Pygame Pong ball and paddle mathematicsThe code above might seem a little daunting but what we are essentially doing is first checking if the ball has an X coordinate between the front and back of the paddle and if it does then we check to see if the ball is between the top and bottom of the paddle. If it is both of these then it must be hitting the paddle.

This is a bit of a hack as we are calculating if the center of the ball is touching the paddle and not the edge of the ball. This greatly simplifies the equations however and when you are playing the game you don't really notice so it is ok.

We could have also done this using PyGames built in rectangle collision detection method however doing it this way allows us to see what is going on better. It also makes it easier to improve the game later on by adjusting the angle of bounce based upon where on the paddle the ball was hit.

The game is now finished. At the momemnt it is a fairly basic implementation of Pong in PyGame. Here is a more involved version of Pong using functions and having different screens.

Activities

You now have a basic version of Pong, ripe for tinkering with. Let's see how you go improving the game.

Have a go at the following :

1. Can you adjust the colours and add more detail to the court?

2. Can you increase the speed and angle of the ball to make it more challenging?

3. Can you add a scoring mechanism?

4. Can you make is to that if you press 'p' on the keyboard it will pause the game?

5. Can you add sound effects when the paddles hit the ball or the ball bounces off the edge of the window?