In this Bonus Tutorial of our Pygame Platformer, we will mainly be focusing on adding Coins and Images into our game.
This will be the end result of our game, once we are done with this tutorial.
Player and Platform Interaction
The first thing we want to do is “fix” the Player and Platform interaction. Currently, what happens is that as the Player lands on a moving platform, the Player will not move along with it. This requires to constantly adjust the position of the Player as the platform moves.
So we’ll be making a few adjustments now to fix this slight issue. All we have to do is change/add a few lines in the Platform Class’s move function.
def move(self):
hits = self.rect.colliderect(P1.rect)
if self.moving == True:
self.rect.move_ip(self.speed,0)
if hits:
P1.pos += (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
A quick breakdown of what we did:
def move(self):
hits = self.rect.colliderect(P1.rect)
This line checks for collision with the player.
if self.moving == True:
self.rect.move_ip(self.speed,0)
if hits:
P1.pos += (self.speed, 0)
Normally, we just move the platform if it’s supposed to be a moving platform. However, if it’s also colliding with the player, we change the player position by adding the platform’s speed into P1.pos
. (It won’t work if you add it into the Player rect, as it’s value is being managed by the Player pos variable)
And that’s it. The rest of the code is the same as before. We just implemented a new feature.
Adding Coins
Adding Coins is a new mechanic and a way of making the game more interesting. While we do gain points for jumping on new platforms, Coins add a bonus way of earning more points.
There are two parts to creating Coins. First we make the Coin class, and then later integrate it into our Platformer Game.
This here is the Class for Coin. It’s fairly basic, with an image “Coin.png”, and a rect based off the image. It takes a parameter “pos
” that sets the initial position for the Coin.
The update()
method is responsible for updating the Score by 5 if the Player collides with it, and then it destroys itself.
class Coin(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.image.load("Coin.png")
self.rect = self.image.get_rect()
self.rect.topleft = pos
def update(self):
if self.rect.colliderect(P1.rect):
P1.score += 5
self.kill()
Integrating Coins
Now we need to add Coins into the Game somehow. Or more specifically, we need to manage them using a Sprite group and generate them on top of Stationary platforms.
First thing we do is create a Sprite group for coins.
all_sprites = pygame.sprite.Group()
platforms = pygame.sprite.Group()
coins = pygame.sprite.Group()
As you can see, we’ve just grouped it with all the other groups in the global namespace.
def generateCoin(self):
if (self.speed == 0):
coins.add(Coin((self.rect.centerx, self.rect.centery - 50)))
Next we add a generateCoin()
method for the Platform class. This is responsible for generating the Coin above the Stationary platform, and add it into the Coin group.
The next change needs to be made where the platform is generated. Platforms are generated in two separate areas (Can be further optimized to just one). As a quick revision, the plat_gen()
is used to constantly generate additional platforms as the player moves up.
The second block is used to generate the initial platforms, that appear at the very start.
def plat_gen():
while len(platforms) < 6:
width = random.randrange(50,100)
p = None
C = True
while C:
p = platform()
p.rect.center = (random.randrange(0, WIDTH - width),
random.randrange(-50, 0))
C = check(p, platforms)
p.generateCoin() # <----------------
platforms.add(p)
all_sprites.add(p)
...
...
...
...
for x in range(random.randint(4,5)):
C = True
pl = platform()
while C:
pl = platform()
C = check(pl, platforms)
pl.generateCoin() # <----------------
platforms.add(pl)
all_sprites.add(pl)
There are only two lines of code actually added here, marked by comments. Rest of code is provided for clarity. The two additional lines simply call generateCoin()
if the platform was successfully created.
Final step is to iterate through the Coin group, and call it’s method every frame for rendering and updating.
for coin in coins:
displaysurface.blit(coin.image, coin.rect)
coin.update()
This is done along with the other groups at the bottom of the game loop.
Replacing Surfaces with Actual Images
Now to actually replace the suface objects we created earlier, with actual graphics. We are going for a Winter theme, so the graphics for the Player is a snowmen, the platforms have snow on them, and the background has a starry night kind of look.
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.surf = pygame.image.load("snowman.png")
self.rect = self.surf.get_rect()
Here in the Player class we simply replace our Surface code with a single line, where we load the snowman image.
class platform(pygame.sprite.Sprite):
def __init__(self, width = 0, height = 18):
super().__init__()
if width == 0: width = random.randint(50, 120)
self.image = pygame.image.load("platform.png")
self.surf = pygame.transform.scale(self.image, (width, height))
self.rect = self.surf.get_rect(center = (random.randint(0,WIDTH-10),
random.randint(0, HEIGHT-30)))
Next is the Platform class. Here we need to make a few modifications to the parameters, so that we can use width and height in the transform.scale()
function. What is this, and why do we need it? Basically, we need platforms of many sizes. So we can’t have dozens of images for all combinations right? Hence we simply scale the image down or up, based on the size of the Platform.
background = pygame.image.load("background.png") # <-----------------
..
..
..
..
..
..
..
while True:
...
...
displaysurface.blit(background, (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()
for coin in coins:
displaysurface.blit(coin.image, coin.rect)
coin.update()
There are two changes we need to make for the background. In the global area of the Code, we need to load an image called background.png. In the Gameloop, we then draw this background to the screen.
It is of utmost importance that the first thing that gets drawn, is the background. This is because the sequence of drawing matters, and can cause the background to be drawn “over” all of the other sprites, if not handled correctly.
Code Download + Materials
Here you can download the Pygame Platformer Code + All the materials (graphics) used in it. If you enjoyed this series, leave some feedback in the comments section below! You can also check out our Pygame RPG Series, where we build an entire RPG game.
Download “Pygame Platformer (Bonus)” Platformer.zip – Downloaded 1603 times – 79.43 KBWith this, our tutorial on Coins and Images in our Pygame Platformer is over.
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.
Hi, Thanks for this amazing tutorial. It helped me to understand the process of building a game using python very well. Would love to see more bonus features being added to this game.
Glad you liked it. 🙂
Thank you very much for your tutorial, which is really well made.
I have added some bonus features to my code (multiplayer and determined difficulty), and i was thinking that other readers might be interested. However, i’m a beginner so do not take my additional code as a reference : i might have coded it the wrong way !
I have coded it with python 3.8.
You can control the second player with the keys q, z and d. And if you play alone, you can choose your difficulty with the keys 1, 2, 3, 4 and 5.
I hope it will inspire other readers.
I’m french, sorry for my bad english.
Here is my code:
—–
import sys
import pygame
import random
import time
pygame.init()
vec = pygame.math.Vector2
height = 900
width = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
difficulty = 0
timer = 0
FramePerSecond = pygame.time.Clock()
displaySurface = pygame.display.set_mode((width,height))
pygame.display.set_caption(“PlatformGame”)
class Player(pygame.sprite.Sprite):
def __init__(self, color=(255,0,0), nb=1):
super().__init__()
self.image = pygame.Surface((30,30))
self.image.fill(color)
self.rect = self.image.get_rect()
self.pos = vec(10,850)
self.vel = vec(0,0)
self.acc = vec(0,0)
self.jumping = False
self.score = 0
self.nb = nb
def move(self):
self.acc = vec(0,0.5)
pressed_keys = pygame.key.get_pressed()
if self.nb == 1:
if pressed_keys[pygame.K_LEFT]:
self.acc.x = -ACC
if pressed_keys[pygame.K_RIGHT]:
self.acc.x = ACC
if self.nb == 2:
if pressed_keys[pygame.K_q]:
self.acc.x = -ACC
if pressed_keys[pygame.K_d]:
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
self.score += 2
if self.pos.x < 0:
self.score += 2
self.pos.x = width
self.rect.midbottom = self.pos
def jump(self):
hits = pygame.sprite.spritecollide(self , platforms, False)
if hits:
pygame.mixer.Sound(“PlatformJump.mp3”).play()
self.jumping = True
self.vel.y = -17
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.rect.y < hits[0].rect.bottom:
self.pos.y = hits[0].rect.top + 1
self.vel.y = 0
self.jumping = False
if hits[0].point == True:
hits[0].point = False
self.score += 1
class Platform(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((random.randint(50,100), 20))
self.image.fill((0,255,0))
self.rect = self.image.get_rect(center = (random.randint(0,width-10), random.randint(0,height-30)))
self.moving = True
self.speed = random.randint(-1,1)
self.point = 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.image.get_width():
#self.rect.right = 0
self.speed = self.speed * (-1)
if self.speed < 0 and self.rect.right < self.image.get_width():
#self.rect.left = width
self.speed = self.speed * (-1)
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
return False
def plat_gen():
while len(platforms) < 12 :
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)
P1 = Player()
P2 = Player((255,255,255), 2)
PT1 = Platform()
PT1.image = pygame.Surface((width, 20))
PT1.image.fill((255,0,0))
PT1.rect = PT1.image.get_rect(center = (width/2, height – 10))
PT1.moving = False
PT1.point = False
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P2)
all_sprites.add(P1)
platforms = pygame.sprite.Group()
platforms.add(PT1)
for x in range(random.randint(10,11)):
C = True
pl = Platform()
while C:
pl = Platform()
C = check(pl, platforms)
platforms.add(pl)
all_sprites.add(pl)
while True:
P1.update()
P2.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
P1.jump()
if event.key == pygame.K_z:
P2.jump()
if event.key == pygame.K_1:
P2.kill()
difficulty = 1
if event.key == pygame.K_2:
P2.kill()
difficulty = 2
if event.key == pygame.K_3:
P2.kill()
difficulty = 3
if event.key == pygame.K_4:
P2.kill()
difficulty = 4
if event.key == pygame.K_5:
P2.kill()
difficulty = 5
if event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
P1.cancel_jump()
if event.key == pygame.K_z:
P2.cancel_jump()
if P1.pos.y < P2.pos.y:
highestPlayer = P1
else:
highestPlayer = P2
if difficulty == 0:
if highestPlayer.pos.y <= height/3:
P1.pos.y += abs(highestPlayer.vel.y)
P2.pos.y += abs(highestPlayer.vel.y)
for plat in platforms:
plat.rect.y += abs(highestPlayer.vel.y)
if plat.rect.top > height:
plat.kill()
elif difficulty > 0:
if timer >= FPS * 2:
P1.pos.y += abs(difficulty)
for plat in platforms:
plat.rect.y += abs(difficulty)
if plat.rect.top > height:
plat.kill()
else:
timer += 1
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:
entity.move()
displaySurface.blit(entity.image, entity.rect)
if P1.rect.top > height or P2.rect.top > height:
for entity in all_sprites:
entity.kill()
time.sleep(0.5)
displaySurface.fill((255,0,0))
pygame.display.update()
time.sleep(0.5)
pygame.quit()
sys.exit()
pygame.display.update()
FramePerSecond.tick(FPS)
—–
idot
How do you make a better game over screen, for example with the text saying “Game Over” and then showing your score and then giving you a replay button? That way, the game won’t lag when you fall out of the screen.
import pygame
from pygame.locals import *
import sys
import random
import time
newline = ‘\n’
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(“Platformer”)
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.surf = pygame.image.load(“games/platformer/images/cube.png”)
self.rect = self.surf.get_rect()
self.pos = vec((10, 360))
self.vel = vec(0,0)
self.surf = pygame.image.load(“games/platformer/images/cube.png”)
self.acc = vec(0,0)
self.jumping = False
self.score = 0
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:
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.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)))
self.speed = random.randint(-3, 3)
self.point = True
self.moving = True
def move(self):
hits = self.rect.colliderect(P1.rect)
if self.moving == True:
self.rect.move_ip(self.speed,0)
if hits:
P1.pos += (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
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)
PT1.moving = False
PT1.point = False ##
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()
font = pygame.font.SysFont(‘Verdana’, 20)
text = font.render(f’Game Over!!!! Your score was {P1.score}.’, True, (255,0,0), (0,0,0))
textRect = text.get_rect()
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:
for entity in all_sprites:
entity.kill()
time.sleep(1)
displaysurface.fill((0,0,0))
displaysurface.blit(text, textRect)
pygame.display.update()
time.sleep(10)
pygame.quit()
sys.exit()
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()
pygame.display.update()
FramePerSec.tick(FPS)
How do you make the game have a game over screen and a restart button in Python311