Pygame – creating a scrolling background



This article explains how to create a scrolling background in Pygame.

Scrolling backgrounds, while not suitable in all types of games can add a very realistic touch to the game they are implemented in. Common examples of games which use scrolling backgrounds are typically side scroller games like flappy bird, space invaders and car racing games.

Today, in this article we’ll be using one of our previous games, from our Pygame Tutorial. It’s a simple side scroller, where you have to dodge the incoming cars. If you want to learn more about the code and how the game works, follow the link to the tutorial.


The concepts behind scrolling backgrounds

Now obviously there are many different methods that can be used to create the scrolling background effect in Pygame. The method we’ll be following is what I consider to be one of the best. It easily bypasses many common issues that arise, while also having the ability to scroll vertically as well as horizontally (with a few tweaks).

The common principle behind background scrolling is simply to move the background in the direction you want it to move. However, the problem is that moving the background 10 pixels in any direction will result in a gap of 10 pixels in that very spot.

The solution that our approach takes is to create two copies of the background, with no overlap. This way, if the first background has been moved half way down, the other background which will be right behind it and will cover that 50% gap.

If this doesn’t make sense, take a good long look at the code we’ll be showing below. If you examine each line of the code carefully, you’ll understand how background scrolling works.

A horizontal scrolling version of our code is available at the very end of this article.


Our Code

Another advantage of our approach is that it’s class based. 90% of the work is all done before the game loop even begins. If you want to skip the explanation and simply implement the scrolling background feature, copy over the code for the Background class, create an object and call the two methods shown below in your game loop. You’ll also have to change the file path of the image to be used.

background_object.update()
background_object.render()

For the actual explanation, keep reading.

Below is simply the code for the Background class. The full source code for the game can be found at the bottom of this article. We’ll begin by explaining each method of the Background class.

class Background():
      def __init__(self):
            self.bgimage = pygame.image.load('AnimatedStreet.png')
            self.rectBGimg = self.bgimage.get_rect()

            self.bgY1 = 0
            self.bgX1 = 0

            self.bgY2 = self.rectBGimg.height
            self.bgX2 = 0

            self.moving_speed = 5
        
      def update(self):
        self.bgY1 -= self.moving_speed
        self.bgY2 -= self.moving_speed
        if self.bgY1 <= -self.rectBGimg.height:
            self.bgY1 = self.rectBGimg.height
        if self.bgY2 <= -self.rectBGimg.height:
            self.bgY2 = self.rectBGimg.height
            
      def render(self):
         DISPLAYSURF.blit(self.bgimage, (self.bgX1, self.bgY1))
         DISPLAYSURF.blit(self.bgimage, (self.bgX2, self.bgY2))

Explanation

def __init__(self):
      self.bgimage = pygame.image.load('AnimatedStreet.png')
      self.rectBGimg = self.bgimage.get_rect()

      self.bgY1 = 0
      self.bgX1 = 0

      self.bgY2 = self.rectBGimg.height
      self.bgX2 = 0

      self.moving_speed = 5

This is the initializing function for the Background class. First we load an image and then create a rect object based of it.

Next we define 2 sets of points. The first set starts at the origin point, the (top left corner) and the second initializes at the bottom of the screen, just out of sight.

Finally we define the moving speed of the background. The faster you want the background the move, the higher the value should be. 5 is a good average though.

def update(self):
      self.bgY1 -= self.moving_speed
      self.bgY2 -= self.moving_speed
      if self.bgY1 <= -self.rectBGimg.height:
            self.bgY1 = self.rectBGimg.height
      if self.bgY2 <= -self.rectBGimg.height:
            self.bgY2 = self.rectBGimg.height

The update() method is what handles all the movement of the background. Every time it is called, it decrements self.bgY1 and self.bgY2, thus changing the co-ordinates to which the background is drawn.

The if statements are there to make sure that the values of the two variables do not exceed the height of the screen itself. If this occurs, it will reset it back to it’s original position at the top.

def render(self):
      DISPLAYSURF.blit(self.bgimage, (self.bgX1, self.bgY1))
      DISPLAYSURF.blit(self.bgimage, (self.bgX2, self.bgY2))

The render() method is the method used to finally draw the background to the screen. Normally, we would only have one blit() function here. However, this time we draw two backgrounds using both pairs of co-ordinates we defined earlier.

Here’s a short video showing the scrolling background effect in our game.

If you want to see how the above code will be implemented in the actual program, you can look the source code for the whole project below. A section on a horizontal version of the Background class is also present at the end of this article.


The Full Game + Code

The code for the whole game with the scrolling background implemented is shown below. If you want the source code and the images used in the game, follow the link to the main tutorial.

#Imports
import pygame, sys
from pygame.locals import *
import random, time

#Initializing 
pygame.init()

#Setting up FPS 
FPS = 60
FramePerSec = pygame.time.Clock()

#Creating colors
BLUE  = (0, 0, 255)
RED   = (255, 0, 0)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

#Other Variables for use in the program
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
SPEED = 5
SCORE = 0

#Setting up Fonts
font = pygame.font.SysFont("Verdana", 60)
font_small = pygame.font.SysFont("Verdana", 20)
game_over = font.render("Game Over", True, BLACK)

#Create a white screen 
DISPLAYSURF = pygame.display.set_mode((400,600))
DISPLAYSURF.fill(WHITE)
pygame.display.set_caption("Game")


class Enemy(pygame.sprite.Sprite):
      def __init__(self):
        super().__init__() 
        self.image = pygame.image.load("Enemy.png")
        self.surf = pygame.Surface((42, 70))
        self.rect = self.surf.get_rect(center = (random.randint(40,SCREEN_WIDTH-40)
                                                 , 0))

      def move(self):
        global SCORE
        self.rect.move_ip(0,SPEED)
        if (self.rect.top > 600):
            SCORE += 1
            self.rect.top = 0
            self.rect.center = (random.randint(40, SCREEN_WIDTH - 40), 0)


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.image = pygame.image.load("Player.png")
        self.surf = pygame.Surface((40, 75))
        self.rect = self.surf.get_rect(center = (160, 520))
       
    def move(self):
        pressed_keys = pygame.key.get_pressed()
       #if pressed_keys[K_UP]:
            #self.rect.move_ip(0, -5)
       #if pressed_keys[K_DOWN]:
            #self.rect.move_ip(0,5)
        
        if self.rect.left > 0:
              if pressed_keys[K_LEFT]:
                  self.rect.move_ip(-5, 0)
        if self.rect.right < SCREEN_WIDTH:        
              if pressed_keys[K_RIGHT]:
                  self.rect.move_ip(5, 0)
                  
class Background():
      def __init__(self):
            self.bgimage = pygame.image.load('AnimatedStreet.png')
            self.rectBGimg = self.bgimage.get_rect()

            self.bgY1 = 0
            self.bgX1 = 0

            self.bgY2 = self.rectBGimg.height
            self.bgX2 = 0

            self.movingUpSpeed = 5
        
      def update(self):
        self.bgY1 -= self.movingUpSpeed
        self.bgY2 -= self.movingUpSpeed
        if self.bgY1 <= -self.rectBGimg.height:
            self.bgY1 = self.rectBGimg.height
        if self.bgY2 <= -self.rectBGimg.height:
            self.bgY2 = self.rectBGimg.height
            
      def render(self):
         DISPLAYSURF.blit(self.bgimage, (self.bgX1, self.bgY1))
         DISPLAYSURF.blit(self.bgimage, (self.bgX2, self.bgY2))
        
#Setting up Sprites        
P1 = Player()
E1 = Enemy()

back_ground = Background()

#Creating Sprites Groups
enemies = pygame.sprite.Group()
enemies.add(E1)
all_sprites = pygame.sprite.Group()
all_sprites.add(P1)
all_sprites.add(E1)

#Adding a new User event 
INC_SPEED = pygame.USEREVENT + 1
pygame.time.set_timer(INC_SPEED, 1000)

#Game Loop
while True:
      
    #Cycles through all occurring events   
    for event in pygame.event.get():
        if event.type == INC_SPEED:
              SPEED += 0.5      
        if event.type == QUIT:
            pygame.quit()
            sys.exit()


    back_ground.update()
    back_ground.render()

    #DISPLAYSURF.blit(background, (0,0))
    scores = font_small.render(str(SCORE), True, BLACK)
    DISPLAYSURF.blit(scores, (10,10))

    #Moves and Re-draws all Sprites
    for entity in all_sprites:
        DISPLAYSURF.blit(entity.image, entity.rect)
        entity.move()

    #To be run if collision occurs between Player and Enemy
    if pygame.sprite.spritecollideany(P1, enemies):
          pygame.mixer.Sound('crash.wav').play()
          time.sleep(0.8)
                   
          DISPLAYSURF.fill(RED)
          DISPLAYSURF.blit(game_over, (30,250))
          
          pygame.display.update()
          for entity in all_sprites:
                entity.kill() 
          time.sleep(1.5)
          pygame.quit()
          sys.exit()        
        
    pygame.display.update()
    FramePerSec.tick(FPS)

Horizontal Version

Some people may require background scrolling that occurs from left to right (or right to left) rather than up and down. As such, we’ve adapted our code to work as a horizontal scroller. Just remember to be using appropriate images . Without any editing, the above street image would look very out of place if scrolled horizontally.

class Background():
      def __init__(self):
            self.bgimage = pygame.image.load('AnimatedStreet.png')
            self.rectBGimg = self.bgimage.get_rect()

            self.bgY1 = 0
            self.bgX1 = 0

            self.bgY2 = 0
            self.bgX2 = self.rectBGimg.width

            self.moving_speed = 5
        
      def update(self):
        self.bgX1 -= self.moving_speed
        self.bgX2 -= self.moving_speed
        if self.bgX1 <= -self.rectBGimg.width:
            self.bgX1 = self.rectBGimg.width
        if self.bgX2 <= -self.rectBGimg.width:
            self.bgX2 = self.rectBGimg.width
            
      def render(self):
         DISPLAYSURF.blit(self.bgimage, (self.bgX1, self.bgY1))
         DISPLAYSURF.blit(self.bgimage, (self.bgX2, self.bgY2))

To summarize, we changed all the height related aspects to the width. You can observe this change by comparing the update functions of both classes. This is where the most change occurred.

Another difference is where we spawned the second background. Unlike the first example, we created the two backgrounds side by side, rather than one up and one down.


This marks the end of the Pygame Scrolling Background article. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the article content can be asked in the comments section below.

Leave a Reply

Your email address will not be published. Required fields are marked *