Welcome to the Starting Small series. The aim of this series is to take a programming language that you hopefully know a bit about (enough to feel comfortable using) or that you want to try out and show you how to use it to make games. The language that this tutorial focuses on is Python using Pygame.
Unfortunately, if you’re reading this article because you want to start developing something spectacular in Pygame, I have to disappoint you. Though, hopefully, by the end of the article you will be able to use Pygame and understand the basics of it. We are going to make something step by step; that is, the “hello world” of game development: Pong. If you are still here and want to follow the tutorial it would be a good idea to get Python and Pygame now.
Python and Pygame are quite are both lightweight and easy to rapidly develop in. The only major drawback is that Python requires an interpreter to run; there are ways to compile projects into exe files, but I won’t touch on that any further in this article.
The best way to learn something is to try it for yourself, so I encourage you as a reader to try out the code for yourself (not just copying and pasting it) and experimenting with it to see what you can do.
To begin with we will need to import the modules we are going to use. We also need to import everything from “pygame.locals” for use in event handling. Immediately after importing “random” and “pygame”, we initialise the “pygame” engine, and give our random module a seed so that we get decent pseudo- random numbers.
import pygame, random from pygame.locals import * pygame.init() random.seed()
The next step is to create our game window. For the purpose of this tutorial we will use a 400 by 400 resolution; there’s no need to make it massive. We create our window and keep a reference to it using the following, which handles the window creation for you:
screen = pygame.display.set_mode((400, 400))
The next step is to create a game loop for this new window we have to draw on.
run = True while run: #game logic
The next addition should be indented to be in line with the “#game logic” placeholder comment. This new code handles refreshing of the window’s contents and filling it with a background colour. Once we’re done drawing we then can “flip” the backbuffer to the screen
If you run your game now, you will be unable to close it without closing python completely because we are within an infinite loop and we aren’t checking for input. To stop this infinite loop we will check if the user clicks on the close button and stop the game loop. This code will actually allow our game to handle all user events, so that at a later stage we can check for other game-related input.
for event in pygame.event.get(): if event.type == QUIT: run = False
Finally, right at the end of your code you need to add the line (outside of your loop) which will result in allowing you to run your game and exit it.
Your final code should look like this. I have inserted some comments for you to refer to in the next section.
import pygame, random from pygame.locals import * pygame.init() random.seed() screen = pygame.display.set_mode((400, 400)) #---Place 1 run = True while run: #game logic #---Place 2 screen.fill((0,0,0)) #---Place 3 pygame.display.flip() for event in pygame.event.get(): if event.type == QUIT: run = False pygame.quit()
The next step is to load your objects. For this example we will stick to sprites, which means we also need to get their bounding rectangles. This is the space that the sprite will occupy on the screen and it has two properties that interest us: its “top” and “left” properties. The top property is the distance, in pixels, the object will be drawn away from the top edge of the window, and the left property is the distance from the left side of the window. Note that Pygame also handles other objects you might want to use, such as sounds.
To load our sprites we use the following:
sprite_[name] = pygame.image.load("[name of file]")
You will want to give each a descriptive name, like “sprite_paddle” or “sprite_ball”. Replace [name of file] with the file name to the image, either fully qualified, or relative to the game’s root folder. “Mysprite.png”, “data/mysprite.bmp” or “c:/mysprite.jpg”, for example.
Then, to get the rectangle, we use the following bit of code. This rectangle will give Pygame the info it needs to draw your sprites where you want them and to the correct proportions.
rect_[name]1 = sprite_[name].get_rect()
Now, you may think my naming of objects is weird and makes no sense, but here is the explanation: Firstly, each type of object should be distinguishable by its name, so a sprite variable naturally starts with “sprite_” and rectangles with “rect_”. This makes it easier to read and understand the code. Secondly, by making the name of an object the same as the name for its rectangle can be naturally paired with their sprites. Having a rectangle variable name ending with a number is a simple way to represent multiple instances of the same sprite.
Before you continue, make yourself 2 sprites of the following dimensions (or 3 if you want to do separate sprites for the each of the paddles), then load them into your game so that they can be used. The loading code goes in at the comment marked “Place 1”.
- 1x ball – 25 by 25
- 1x (or 2x) paddle – 100 by 25
The next step we will handle is the drawing of the sprites. This code would go into Place 3, between clearing the screen, and flipping the backbuffer. Blitting a sprite is fairly easy:
Now, place your sprites where you want them by changing the top and left properties (left,top), I have placed my ball at (187.5,187.5) and my paddles at (150,0) and (150,375). This gives you 2 paddles, one at the top, one at the bottom, and a ball in the middle of the window. If you run your game now it should look like this image below.
You are now ready to work on your game logic, to make your game actually work. Your game logic should go in at Place 2, and, at the very least should be handling player input and updating of things like the positions of your objects if they are moving.
Input handling from the keyboard in Pygame is fairly easy: you simply check specific keys against the list of all keys currently pressed down.
keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: #left is pressed if keys[pygame.K_a]: #a is pressed if keys[pygame.K_ENTER]: #enter is pressed
Now, for Pong, we need to move our paddles left and right. We’ll do this by changing the left property of the rectangles we are using for them. For now, only make the increase or decrease of the left property by 1. Another thing to keep in mind is that we don’t want the paddle to leave the left side of the screen, so we only move the paddle left if the left property is already larger than 0. The same applies to the right side of the screen, but we will need to check to make sure the left property is smaller that 300. This is because our sprite is 100 pixels long, so we can only move its left edge a maximum of 100 pixels away from the right side of the window.
The following is an example of how to do the checks and make the paddle move, you would just need to repeat it for each key that you wish to use.
if keys[pygame.K_LEFT]: if (rect_paddle1.left > 0): rect_paddle1.left -= 1
If you cannot decide what keys to use for each paddle, make the one paddle use the left and right arrows, and the other use “a” and “d” (K_a and K_d). Finish the paddle movement code so that both paddles can move left and right independently.
You should now have a game where you have two paddles and a ball drawn on the screen. You should be able to move each paddle using a separate set of keys, and exit the game with the quit button with no problems.
Now we are going to make our ball move. To do this, we will be making two variables to control our balls movement, “hspeed” or horizontal speed, and “vspeed” or vertical. This way, all we have to do is add “hspeed” to the left property of the ball to move it from side to side, and when the ball hits the side of the window, all we have to do is invert the sign of “hspeed”. The same is done for the “vspeed” with the ball’s top property, but instead of changing the direction of the ball, we place it back in the centre of the window as a reset of sorts if it reaches the top or bottom of the screen.
First, let’s declare hspeed and vspeed at Place 1. We give them the starting value of 0, and we keep looping till it is randomly assigned a non-zero integer between -1 and 1, making sure that we have a direction to move. Just below that we’ll make a variable to slow down the updating of the ball’s movement
hspeed = 0 vspeed = 0 while hspeed == 0: hspeed = random.randint(-1,1) hspeed = hspeed while vspeed == 0: vspeed = random.randint(-1,1) vpseed = vspeed slower = 15
We then move to Place 2 – which is where we will update the balls movement – and add in the following code:
slower -= 1 if slower == 0: slower = 15 #ball movement updating code will go here
I’ve used 15 as the number of repeats we want between each time the ball moves, if you find that this is too slow for you feel free to decrease it.
The movement for the ball is slightly more complicated then it is for a paddle because you have more things to do. If we are at either horizontal edge of the window we will have to reverse the hspeed. With vertical edges we won’t reverse the direction, but rather move the ball to the middle again.
if hspeed < 0: if (rect_ball.left + hspeed > 0): rect_ball.left += hspeed else: hspeed = -hspeed else: if (rect_ball.left + hspeed < 375): rect_ball.left += hspeed else: hspeed = -hspeed #vertical movement for ball if vspeed < 0: if (rect_ball.top + vspeed > 0): rect_ball.top += vspeed else: rect_ball.left = 200 - 12.5 rect_ball.top = 200 - 12.5 else: if (rect_ball.top + vspeed < 375): rect_ball.top += vspeed else: rect_ball.left = 200 - 12.5 rect_ball.top = 200 - 12.5
The biggest drawback you will have with using Pygame when you start out will be its lack of built in collision detection. I will explain very briefly how the collision detection works and give you the code for this game. Be sure to read our series on collision detection for games for an expansion of this concept.
When we check if the ball collides with a paddle we check two things: is the top left corner of the paddle inside the ball, or is the top left corner of the ball inside the paddle. If you are feeling brave you can write yourself a function that takes your 2 rectangles and returns whether or not they are colliding or not. After we have found a collision, we’ll check if the ball is heading towards the paddle. If the “vspeed” is negative, the ball is moving up, so if the ball’s top property is above the halfway line and it has collided, I need to make the value of “vspeed” its opposite. This ensures that when the ball hits the paddle it will change direction and not get stuck on the paddle, which will happen if its “vspeed” changes constantly every frame.
The following code handles the collision with paddles and doesn’t get put inside the slower if-statement, but goes into the main game loop
#ball collision with paddle1 coll = False if ((rect_ball.left >= rect_paddle1.left) and \ (rect_ball.left <= rect_paddle1.left + rect_paddle1.width) and \ (rect_ball.top >= rect_paddle1.top) and \ (rect_ball.top <= rect_paddle1.top + rect_paddle1.height)): coll = True elif ((rect_paddle1.left >= rect_ball.left) and \ (rect_paddle1.left <= rect_ball.left + rect_ball.width) and \ (rect_paddle1.top >= rect_ball.top) and \ (rect_paddle1.top <= rect_ball.top + rect_ball.height)): coll = True #ball collision with paddle2 if ((rect_ball.left >= rect_paddle2.left) and \ (rect_ball.left <= rect_paddle2.left + rect_paddle2.width) and \ (rect_ball.top + rect_ball.height >= rect_paddle2.top) and \ (rect_ball.top + rect_ball.height <= rect_paddle2.top + rect_paddle2.height)): coll = True elif ((rect_paddle2.left >= rect_ball.left) and \ (rect_paddle2.left <= rect_ball.left + rect_ball.width) and \ (rect_paddle2.top >= rect_ball.top) and \ (rect_paddle2.top <= rect_ball.top + rect_ball.height)): coll = True if coll: if ((rect_ball.top < 200) and (vspeed < 0)): vspeed = -vspeed elif ((rect_ball.top > 200) and (vspeed > 0)): vspeed = -vspeed
If all has gone well and you haven’t run into any problems, you have now completed Pong in Python using Pygame.
Things to try yourself:
- Try loading a sound or two and playing them (for instance when the ball hits a paddle or wall)
sound_[name] = pygame.mixer.sound("[file name]") sound_[name].play()
- Try changing the caption of the window
- Try make the one paddle be controlled by the computer rather than another human playerHint:To do this you need to use if statements that check if the balls left property is to the left or right of the middle of the one paddle, and then move towards it if the balls vspeed makes it head towards that paddle. This should make it seem like you’re playing against a human player.
You can get the full source for the game in this article from here if you have problems.