Pygame Platformer: Bonus Content (Coins and Images)

In this Bonus Tutorial of our Pygame Platformer, we will mainly be focusing on adding Coins and Images into our game.

Pygame Platformer - Coins and Graphics

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 KB

With 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.

7 thoughts on “Pygame Platformer: Bonus Content (Coins and Images)”

  1. 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.

    Reply
  2. 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)
    —–

    Reply
  3. 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.

    Reply
    • 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)

      Reply

Leave a Comment