Pong game using Pygame - step 1
Categories: pygame
This is the first article in a series where we will develop a complete game of Pong using Pygame.
In this step, we will simply create a window and display the bat and ball, with no animation.
The code for this article can be found on github, and the resources are here.
Creating a basic game window
Here is the code to create a simple Pygame window:
import pygame as pg
# Setup game
screen_width = 640
screen_height = 480
# Initialise pygame
pg.init()
# Set display size
pg.display.set_mode((screen_width, screen_height))
# Set window title
pg.display.set_caption('Pong')
# Game loop
running = True
while running:
# Check all events in queue
for event in pg.event.get():
# If a quit event occurs, reset running flag
if event.type == pg.QUIT:
running = False
This code doesn't do a great deal - it just displays a blank window - but it is the basic set-up any game will need. We will look at it step by step.
The first thing we need to do is import the pygame
module. We import it as pg
, which means that in our code we can refer to it as pg
. You don't have to do this, but it makes your code slightly more readable:
import pygame as pg
Next, we initialise Pygame, like this:
# Setup game
screen_width = 640
screen_height = 480
# Initialise pygame
pg.init()
# Set display size
pg.display.set_mode((screen_width, screen_height))
# Set window title
pg.display.set_caption('Pong')
There are three steps:
pg.init()
initialises the library.pg.display.set_mode()
sets the screen size to 640 by 480. You can change this if you wish, for example to 800 by 600pg.set_caption()
sets the title bar of the window.
Here is the result:
Sprites
What is a sprite? In game development, a sprite is a 2-dimensional graphical element of a game. It can be a player, a ball, a monster, a bullet, a spaceship, an asteroid, and so on. It can be an active element or it can be a passive element such as a wall, floor, obstacle, etc.
A sprite usually has an image and often has associated behaviours and interactions.
In our game, we have two sprites: a bat and a ball. The bat moves left and right under the control of the user's mouse. The ball moves around automatically and will bounce off the bat or the boundaries of the screen.
In Pygame, a sprite is created as a Python class. The class is based on the Pygame Sprite
class, but each sprite we define can add its own behaviours.
We will implement our sprites in a separate Python file called sprites.py, It is easier to manage a program if you split it into different files each containing a set of related classes or functions. Here is the code for sprites.py:
import pygame as pg
class Ball(pg.sprite.Sprite):
def __init__(self, pos):
super(Ball, self).__init__()
self.image = pg.image.load('ball.png')
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self):
pass
class Bat(pg.sprite.Sprite):
def __init__(self, pos):
super(Bat, self).__init__()
self.image = pg.image.load('bat.png')
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self):
pass
Classes and objects
You will probably be familiar with Python objects. For example:
s1 = "abcd"
s2 = "xyz"
print(type(s1)) # <class 'str'>
print(type(s2)) # <class 'str'>
print(s1.upper()) # "ABCD"
print(s2.upper()) # "XYZ"
In this code, s1
and s2
are both strings. When we print the type
of s1
we get:
<class 'str'>
This tells us that s1
has class str
, in other words, it has type str
. Same for s2
.
Both values are the same type, but of course, they contain different data. s1
contains the text "abcd", but s2
contains the text "xyz".
s1
and s2
are different objects, but they are both the same type (or class) of object.
The string class has several functions associated with it. A function belonging to a class is usually called a method but it is more or less the same thing.
For example, strings have a method upper
that returns a copy of the text, converted to uppercase. So s1.upper()
gives "ABCD" and s2.upper()
gives "XYZ".
To summarise:
- A class defines a type of object. It defines what sort of values the object can hold, and also defines methods that can be called pt act on that data.
- An object is an instance of a class. For example
s1
is an instance of the string class (put more simply, it is a string). An object has its own unique data, but it also has the methods defined by its class.
Objects are a great way of combining data, together with useful functions to operate on that data, into a single convenient item. We will use them quite a lot in game development, and often define our own classes.
Defining the sprites
The Ball
class is defined like this:
class Ball(pg.sprite.Sprite):
def __init__(self, pos):
super(Ball, self).__init__()
self.image = pg.image.load('ball.png')
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self):
pass
This code defines a class called Ball
that represents a ball sprite in our game. We give our sprite two methods:
__init__
is a special method that is used to initialise an object when it is created.update
is called once on each pass through the game loop to update the sprite.
One other thing about this class - it inherits from the Sprite
class (because we pass sprite.Sprite
into the Ball
class declaration). This means that our class will already have some useful behaviours without us having to write any code at all.
The __init__
method is where we set up new Ball
objects. We do several things in this method:
- Call
super.__init__()
. This initialises the parentSprite
class. This is very important, if we don't do this the sprite behaviours won't be initialised properly and might cause weird errors or bugs. - Load the ball.png image and store it in
self.image
. - Find the size of the image using
self.image.get_rect()
, and use that to set the size of the spriteself.rect
. - Take the initial position
pos
(passed in when the ball is constructed) and use it to set the position of the centre of the sprite.
Methods within a class have a special extra parameter self
that represents the object itself. So when we declare self.image
it creates a variable that belongs to the object (called a member variable) that can be accessed in other methods (again as self.image
).
Looking back to the original sprite.py file, you will see that we also define a Bat
class that is currently almost identical to the Ball
class, except that it loads a bat.png as its image.
Creating the bat and ball
Now we will change the main game code to create and use the sprites. Here is the new code:
import pygame as pg
from sprites import Ball, Bat
# Setup game
screen_width = 640
screen_height = 480
# Initialise pygame
pg.init()
# Set display size
screen = pg.display.set_mode((screen_width, screen_height))
# Set window title
pg.display.set_caption('Pong')
# Create sprites
ball_sprite = Ball((100, 200))
bat_sprite = Bat((200, 400))
all_sprites = pg.sprite.RenderPlain()
all_sprites.add(ball_sprite)
all_sprites.add(bat_sprite)
# Game loop
running = True
while running:
# Check all events in queue
for event in pg.event.get():
# If a quit event occurs, reset running flag
if event.type == pg.QUIT:
running = False
screen.fill((0, 0, 0))
all_sprites.draw(screen)
pg.display.flip()
This is very similar to the previous code that displays a blank screen, we have just added 2 sections to create the sprites, and to display them.
Here is the window it creates. The bat and ball are stationary and will never move:
Here is the creation code:
ball_sprite = Ball((100, 200))
bat_sprite = Bat((200, 400))
all_sprites = pg.sprite.RenderPlain()
all_sprites.add(ball_sprite)
all_sprites.add(bat_sprite)
This code creates a ball_sprite
by calling the Ball
function. It passes in the parameter (100, 200)
, a tuple that sets the centre of the ball to a position that is 100 pixels in from the left of the game window, and 200 pixels down from the top.
We create a bat_sprite
in a similar way, at position (200, 400)
.
We then add both sprites to a sprite group.
Sprite groups
A sprite group is an object that holds a collection of sprites. Sprite groups are very useful because they allow us to operate on all the sprites in a group by making a single call.
In our case, we have created a sprite group called all_sprites
that holds the ball and bat sprites. The group is a RenderPlain
object, which is a special type of sprite group that can automatically draw all the sprites.
Having created the group, we then add the ball and bat sprites using the add
method.
Drawing the sprites
To draw the sprites, we just need to add this code to the game loop:
screen.fill((0, 0, 0))
all_sprites.draw(screen)
pg.display.flip()
This code is called each time the game loop runs, which is typically many times a second.
We draw using the screen
object we created earlier. Here is what the code does:
- First we clear the screen to black by filling it with the RGB colour value
(0, 0, 0)
. - Next we draw the sprites to the screen using the
all_sprites.draw
method. Sinceall_sprites
contains our ball and bat, both those items will be drawn. - Finally, we call
display.flip()
to make display the screen, as described below.
Double buffering
Why do we need to call the flip()
method to display the sprites?
The reason is that the screen
object is not the actual screen that the user sees. screen
is a hidden memory buffer that stores the pixels' colours but doesn't display them.
There is a good reason for this. In most games, the sprites are moving around. We need to first remove the old sprites (by filling the screen with black pixels) and then redraw the sprites in their new position.
If we did this on the real screen, the whole screen would flicker each time through the game loop, It would look terrible, and probably give the players a headache.
To avoid this, Pygame, like most game engines, uses double buffering. The screen
object is a hidden memory buffer. We can clear it and redraw it without having any visible effect on the computer screen.
When we have completely finished all our drawing operations, we call the display.flip()
method that quickly copies the memory from the hidden buffer to the real screen memory.
File structure
Since this is a simple project, all our files live in the same folder. It contains:
- The main module main.py.
- The sprites module sprites.py
- The ball image ball.png.
- The bat image bat.png.
Summary
We have covered quite a lot of ground here. It might seem like a lot of trouble, just to display a fixed, unmoving image of a ball and bat.
But in fact, we have done more than that. We have laid the groundwork for the next steps of our game, which will come together quite quickly in the next few stages.
See also
Join the PythonInformer Newsletter
Sign up using this form to receive an email when new content is added:
Popular tags
2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest