Pong game using Pygame - step 3
Categories: pygame
This is the second article in a series where we will develop a complete game of Pong using Pygame.
In the previous steps:
- step 1, we created a window and displayed the bat and ball, with no animation.
- step 2, we animated the ball, making it bounce off the 4 edges of the screen, but with no interaction with the bat.
In this step we will animate the bat, making it move in response to the mouse. We will also make the ball bounce off the bat. If the player misses the ball, the game will end.
This step is much shorter than the last two steps because we have already done a lot of the groundwork.
The code for this article can be found on github, and the resources are here.
Code so far
This step only affects the behaviour of the bat and the ball. This means that main.py doesn't need to change at all. We won't copy the code here, refer to step 2 if you want to see it.
We will only need to make changes to sprites.py. Here is the sprite code we developed in step 2:
import pygame as pg
class Ball(pg.sprite.Sprite):
def __init__(self, pos, game):
super(Ball, self).__init__()
self.game = game
self.image = pg.image.load('ball.png')
self.rect = self.image.get_rect()
self.rect.center = pos
self.speed = 0.2
self.direction = pg.Vector2(1, -1)
def update(self, delta_time):
self.rect.center += self.direction * self.speed * delta_time
if self.rect.colliderect(self.game.left):
self.direction[0] = 1
if self.rect.colliderect(self.game.right):
self.direction[0] = -1
if self.rect.colliderect(self.game.top):
self.direction[1] = 1
if self.rect.colliderect(self.game.bottom):
self.direction[1] = -1
class Bat(pg.sprite.Sprite):
def __init__(self, pos, game):
super(Bat, self).__init__()
self.game = game
self.image = pg.image.load('bat.png')
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self, delta_time):
pass
Moving the bat
We saw in step 2 how to move the ball (the code above). We did that by updating the ball's position in the update
method of the Ball
sprite. Here is how it worked:
- In the initialisation in main.py we created a
Ball
sprite and added it to theall_sprites
group. - In the game loop, also in main.py, we called
all_sprites.update()
. all_sprites.update()
calls theupdate()
method on every sprite in the group. This includes theBall
sprite.- The
Ball
spriteupdate()
method updates the position of the ball and changes its direction if it collides with the edge of the game screen.
The existing code also adds the Bat
sprite to the all_sprites
group. This means that the update()
method of the Bat
sprite is also called on each pass through the game loop. But as you can see from the code above, the Bat
sprite has an empty update()
method so it does nothing.
We want the bat to follow the mouse in the horizontal direction. To do this we first need to find the mouse position. Pygame has a function to do that:
pg.mouse.get_pos()
This returns the (x, y) position of the mouse, in pixels, using the main window coordinates. Inside the Bat.update()
method, we could do this:
def update(self, delta_time):
self.rect.center = pg.mouse.get_pos()
The bat will now follow the mouse, left, right, up or down. Wherever the mouse goes, the centre of the bat will be right underneath it.
But this isn't quite what we want. We want the bat to follow the mouse in the x direction only. get_pos()
returns an (x, y) tuple, so we can get just the x value of the position using:
pg.mouse.get_pos()[0]
We also need a y value for the bat position, which we can obtain from the current bat position self.rect.center[1]
(taking just the y component of the position).
We combine these two parts to create a full (x, y) tuple of the required position of the bat:
def update(self, delta_time):
self.rect.center = (pg.mouse.get_pos()[0], self.rect.center[1])
Here is the final code of the Bat
sprite:
class Bat(pg.sprite.Sprite):
def __init__(self, pos, game):
super(Bat, self).__init__()
self.game = game
self.image = pg.image.load('bat.png')
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self, delta_time):
self.rect.center = (pg.mouse.get_pos()[0], self.rect.center[1])
Updating the ball behaviour
We need to add two new behaviours to the Ball
sprite:
- Make it bounce when we hit the bat.
- End the game when the player misses the ball.
The update
method already performs checks to see if the ball has hit any edge of the screen, and bounce it in the opposite direction when it does. To bounce the ball off the bat, we just add one extra check in the ball update
method:
if self.rect.colliderect(self.game.bat_sprite.rect):
self.direction[1] = -1
Here we check if the ball (the self
object) has collided with the bat sprite. Since the ball always approaches the bat in the downwards direction, when it hits we always want to make the ball travel upwards, which requires the ball y direction to be set to -1.
Next, we want to end the game when the player misses the ball. The easiest way to detect that is to detect when the ball hits the bottom of the screen. That can only happen when the player misses the ball.
The previous version of the game (step 2) already had a defined behaviour for the ball hitting the bottom of the screen. It would cause the ball to bounce upwards. In the ball update
method we had:
if self.rect.colliderect(self.game.bottom):
self.direction[1] = -1
Now instead of changing the ball direction, we simply need to set the game.running
flag to false. If you recall from step 2, the main game loop is a while loop with game.running
as its condition, so setting it false will terminate the game immediately. We don't need to change the direction of the ball because the game is ending anyway. So our new action is:
if self.rect.colliderect(self.game.bottom):
self.game.running = False
The complete code for the ball and bat sprites is shown in the final code below.
The final code
Here is the final sprite.py code for this step:
import pygame as pg
class Ball(pg.sprite.Sprite):
def __init__(self, pos, game):
super(Ball, self).__init__()
self.game = game
self.image = pg.image.load('ball.png')
self.rect = self.image.get_rect()
self.rect.center = pos
self.speed = 0.2
self.direction = pg.Vector2(1, -1)
def update(self, delta_time):
self.rect.center += self.direction * self.speed * delta_time
if self.rect.colliderect(self.game.left):
self.direction[0] = 1
if self.rect.colliderect(self.game.right):
self.direction[0] = -1
if self.rect.colliderect(self.game.top):
self.direction[1] = 1
if self.rect.colliderect(self.game.bottom):
self.game.running = False
if self.rect.colliderect(self.game.bat_sprite.rect):
self.direction[1] = -1
class Bat(pg.sprite.Sprite):
def __init__(self, pos, game):
super(Bat, self).__init__()
self.game = game
self.image = pg.image.load('bat.png')
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self, delta_time):
self.rect.center = (pg.mouse.get_pos()[0], self.rect.center[1])
Summary
So, in this section we have:
- Modified the
Bat.update()
method to cause the bat to move in response to the mouse. - Modified the
Ball.update()
method to cause the ball to bounce off the bat. - Modified the
Ball.update()
method to make the game end if the player misses the ball.
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