In this section we’ll finally complete our Pygame Platformer game series.
If you’ve been following our Platformer Tutorial Series uptil now, you’ll know that we’ve reached a point where our game is almost complete. There are just a few small things that need to be added to really complete the concept of a successful and complete game in Pygame.
What are these few things that need to be added to our Pygame Platformer?
- Game-Over Screen
- Score System
- Moving Platforms

Part 4 – Code
The completed code from the previous section:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 64 65 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 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 140 141 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 | import pygame from pygame. locals import * import sys import random pygame.init() vec = pygame.math.Vector2 #2 for two dimensional HEIGHT = 450 WIDTH = 400 ACC = 0.5 FRIC = - 0.12 FPS = 60 FramePerSec = pygame.time.Clock() displaysurface = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption( "Game" ) class Player(pygame.sprite.Sprite): def __init__( self ): super ().__init__() #self.image = pygame.image.load("character.png") self .surf = pygame.Surface(( 30 , 30 )) self .surf.fill(( 255 , 255 , 0 )) self .rect = self .surf.get_rect() self .pos = vec(( 10 , 360 )) self .vel = vec( 0 , 0 ) self .acc = vec( 0 , 0 ) self .jumping = False def move( self ): self .acc = vec( 0 , 0.5 ) pressed_keys = pygame.key.get_pressed() if pressed_keys[K_LEFT]: self .acc.x = - ACC if pressed_keys[K_RIGHT]: self .acc.x = ACC self .acc.x + = self .vel.x * FRIC self .vel + = self .acc self .pos + = self .vel + 0.5 * self .acc if self .pos.x > WIDTH: self .pos.x = 0 if self .pos.x < 0 : self .pos.x = WIDTH self .rect.midbottom = self .pos def jump( self ): hits = pygame.sprite.spritecollide( self , platforms, False ) if hits and not self .jumping: self .jumping = True self .vel.y = - 15 def cancel_jump( self ): if self .jumping: if self .vel.y < - 3 : self .vel.y = - 3 def update( self ): hits = pygame.sprite.spritecollide( self ,platforms, False ) if self .vel.y > 0 : if hits: if self .pos.y < hits[ 0 ].rect.bottom: self .pos.y = hits[ 0 ].rect.top + 1 self .vel.y = 0 self .jumping = False class platform(pygame.sprite.Sprite): def __init__( self ): super ().__init__() self .surf = pygame.Surface((random.randint( 50 , 100 ), 12 )) self .surf.fill(( 0 , 255 , 0 )) self .rect = self .surf.get_rect(center = (random.randint( 0 ,WIDTH - 10 ), random.randint( 0 , HEIGHT - 30 ))) def move( self ): pass def check(platform, groupies): if pygame.sprite.spritecollideany(platform,groupies): return True else : for entity in groupies: if entity = = platform: continue if ( abs (platform.rect.top - entity.rect.bottom) < 40 ) and ( abs (platform.rect.bottom - entity.rect.top) < 40 ): return True C = False def plat_gen(): while len (platforms) < 6 : width = random.randrange( 50 , 100 ) p = platform() C = True while C: p = platform() p.rect.center = (random.randrange( 0 , WIDTH - width), random.randrange( - 50 , 0 )) C = check(p, platforms) platforms.add(p) all_sprites.add(p) PT1 = platform() P1 = Player() PT1.surf = pygame.Surface((WIDTH, 20 )) PT1.surf.fill(( 255 , 0 , 0 )) PT1.rect = PT1.surf.get_rect(center = (WIDTH / 2 , HEIGHT - 10 )) all_sprites = pygame.sprite.Group() all_sprites.add(PT1) all_sprites.add(P1) platforms = pygame.sprite.Group() platforms.add(PT1) for x in range (random.randint( 4 , 5 )): C = True pl = platform() while C: pl = platform() C = check(pl, platforms) platforms.add(pl) all_sprites.add(pl) while True : P1.update() for event in pygame.event.get(): if event. type = = QUIT: pygame.quit() sys.exit() if event. type = = pygame.KEYDOWN: if event.key = = pygame.K_SPACE: P1.jump() if event. type = = pygame.KEYUP: if event.key = = pygame.K_SPACE: P1.cancel_jump() if P1.rect.top < = HEIGHT / 3 : P1.pos.y + = abs (P1.vel.y) for plat in platforms: plat.rect.y + = abs (P1.vel.y) if plat.rect.top > = HEIGHT: plat.kill() plat_gen() displaysurface.fill(( 0 , 0 , 0 )) for entity in all_sprites: displaysurface.blit(entity.surf, entity.rect) entity.move() pygame.display.update() FramePerSec.tick(FPS) |
Implementing a Game Over Screen
The following code is inserted into the Game loop (directly below the keys and events handling).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import time ... ... while True : ... ... ... if P1.rect.top > HEIGHT: for entity in all_sprites: entity.kill() time.sleep( 1 ) displaysurface.fill(( 255 , 0 , 0 )) pygame.display.update() time.sleep( 1 ) pygame.quit() sys.exit() |
The code is actually very simple. Once the top part of the Player has gone below the screen, a for loop activates which immediately kills each Sprite individually and then fills the screen with red, representing a game over screen. After a second of waiting, the game turns off.
You can make this system better obviously, this just shows you how to activate the “Game over” signal, giving the game an actual end. Previously the player would keep falling once he fell.
Be sure to import the time library!
High Score
A simple addition, but a much needed one. Without a scoring system, there’s no sense of progression or achievement that the player feels while playing.
The code for this is going to be rather long due to the various number of small changes that must be made in several places. In order for you to keep track of the changes, we have marked any new lines/changes with a double hash (##).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 | class Player(pygame.sprite.Sprite): def __init__( self ): super ().__init__() ... ... self .jumping = False self .score = 0 ## ... ... def update( self ): hits = pygame.sprite.spritecollide( self ,platforms, False ) if self .vel.y > 0 : if hits: if self .pos.y < hits[ 0 ].rect.bottom: if hits[ 0 ].point = = True : ## hits[ 0 ].point = False ## self .score + = 1 ## self .pos.y = hits[ 0 ].rect.top + 1 self .vel.y = 0 self .jumping = False class platform(pygame.sprite.Sprite): def __init__( self ): super ().__init__() ... ... self .moving = True self .point = True ## ... ... PT1.moving = False PT1.point = False ## ... ... while True : ... ... ... displaysurface.fill(( 0 , 0 , 0 )) f = pygame.font.SysFont( "Verdana" , 20 ) ## g = f.render( str (P1.score), True , ( 123 , 255 , 0 )) ## displaysurface.blit(g, (WIDTH / 2 , 10 )) ## for entity in all_sprites: ... |
We’ll discuss the changes below in the sequence that they were made.
- First we added a new attribute “score” to our Player. This is to keep track of the high score. We also initialize this from zero.
- (Read Change 3 first, then read this) We check the point attribute of the platform we just landed on. If it’s set to
True
we plus one into our score and turn the point value toFalse
. This prevents the player from gaining points from jumping onto the same platform over and over. - We create the “point” attribute for the Platform which determines whether the platform will give the player a point if he lands on it or not. If True, then yes, If False, then no.
- We disable the point feature on the Base platform.
- We create a font for our High Score display in pygame and then render it. If you don’t know how to handle fonts in pygame, read our fonts with pygame tutorial.
Moving Platforms in Pygame
Our game isn’t very challenging in it’s current state. As a final boost to the game’s difficulty, we’re going to introduce the concept of the platforms moving around randomly. This will make it harder for the player to land on one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class platform(pygame.sprite.Sprite): def __init__( self ): ... ... self .speed = random.randint( - 1 , 1 ) self .moving = True def move( self ): if self .moving = = True : self .rect.move_ip( self .speed, 0 ) if self .speed > 0 and self .rect.left > WIDTH: self .rect.right = 0 if self .speed < 0 and self .rect.right < 0 : self .rect.left = WIDTH ... ... ... PT1.moving = False |
The self.moving
line is there purely for the PT1 platform that forms the floor of the level. It’s not supposed to be able to move, hence we introduced the concept only platforms with moving
set to True will be able move.
1 | self .speed = random.randint( - 1 , 1 ) |
The above line is the mastermind behind making the platforms move in such a unique way. The above line creates three possibilities value for the speed, -1, 0 and 1. Because of this there is a 33% chance that the platform will move left, a 33% chance it will move right and 33% chance it won’t move at all. That’s a fairly unique distribution for just a single line in my opinion.
1 2 3 4 5 6 7 | def move( self ): if self .moving = = True : self .rect.move_ip( self .speed, 0 ) if self .speed > 0 and self .rect.left > WIDTH: self .rect.right = 0 if self .speed < 0 and self .rect.right < 0 : self .rect.left = WIDTH |
The move function has two parts to it. The first is the move_ip
function which moves the platform using the self.speed
parameter. The second is a warping feature. What this does is that when a platform goes out of the screen from the right or the left, it will appear from the other side. Without this part, the platforms would fly off the screen.
Below is a small video displaying the random movement in our Platformer game. (Forgive that little glitch in the video, it’s a problem with the video recording software).
Code Download
The code for the completed Pygame Platformer Game, available by clicking the download button below.
Update: We released some new Bonus Content that really enhances the Look of the game, and makes it more enjoyable to Play. Check it out now!
This marks the end of the Python Pygame Platformer Game series. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the article content can be asked in the comments section below.
good game
Really good tutorial but I am having problems with the game over screen.
i get a time.sleep error. any help would be kindly accepted.
Could you copy paste the error over here? As well as the code block where the error is occuring?
time.sleep(1)
NameError: name ‘time’ is not defined
while True:
P1.update()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
P1.jump()
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
P1.cancel_jump()
if P1.rect.top <= HEIGHT / 3:
P1.pos.y += abs(P1.vel.y)
for plat in platforms:
plat.rect.y += abs(P1.vel.y)
if plat.rect.top >= HEIGHT:
plat.kill()
plat_gen()
displaysurface.fill((0,0,0))
f = pygame.font.SysFont(“Verdana”, 20)
g = f.render(str(P1.score), True, (123,255,0))
displaysurface.blit(g, (WIDTH/2, 10))
for entity in all_sprites:
displaysurface.blit(entity.surf, entity.rect)
entity.move()
if P1.rect.top > HEIGHT:
for entity in all_sprites:
entity.kill()
time.sleep(1) ##
displaysurface.fill((255,0,0))
pygame.display.update()
time.sleep(1)
pygame.quit()
sys.exit()
## = where I get the error
You need to import the time library. I forgot to include the import statement in the code. My bad.
Probably didn’t import time at the top
Is there any chance of posting the whole code for the game altogether.
Done! It’s available here with this download link as well as on the page.
https://coderslegacy.com/download/7400/
Hi, I was wondering if you would have any suggestions on how to implement the player moving with the moving platforms?
I have previously tried this (below) in the platform class, it doesn’t cause any errors but also doesn’t work.
def move(self):
hits = pygame.sprite.spritecollide(P1, platforms, False)
if self.moving == True:
self.rect.move_ip(self.speed,0)
if hits:
P1.rect.move_ip(self.speed, 0)
if self.speed > 0 and self.rect.left > WIDTH:
self.rect.right = 0
if self.speed < 0 and self.rect.right < 0:
self.rect.left = WIDTH
The reason why it doesn’t work is because of the self.pos variable in the player. Which keeps resetting the self.rect value. In short, you need to change the values of the pos variable, like so : P1.pos += (self.speed, 0)
Thanks. I tried this using the hits condition, and had a few issues. Firstly, it appears to apply the movement randomly rather than relating to the platform being collided with. Also, despite putting this inside moving == True, the player is moving at the start of the game. I also tried changing it to check which direction the platforms were going after checking hits and this did not fix the issue either.
Within 24 hours, I’ll have a 7th part added into this series. It will have the feature you are trying to add + 1-2 more. Please check in again after a while
Have you tried adding the player- and platform- speed? Like that:
I appreciate the tutorial, though there are a lot of gaps where code was added but not included in the tutorial. Wouldn’t be the easiest for a complete beginner. Also, keeping everything in one file is a lot, even for a small game like this.
I did SOME refactoring as I went through, mainly separate files for classes. I still have the random freezing issue someone noted before. My code is linked below and I included a copy of the original.
https://github.com/emanual4real/pygame-platformer.git
i’m getting this error:
No module named ‘pygame.locals’; ‘pygame’ is not a package.
what should i do, i’ve already installed pygame