Search code examples
pythonpygamesmooth-scrollingvertical-scrolling

Having trouble with seamless scrolling when images regenerate at top of screen


So I can't figure out why I can't get the images to stay connect and seamless when the regenerate at the top of the display screen. I have them both going back to top (rect.bottom = 0) once the rect.top reaches bottom (600). depending on what my speedy is they do funny things like start apart and then slide together at top of screen or just end up with a consistent space in between them.

Any help would be appreciated or can I put the image in same class and have it repeat 6 times down the screen and move them all so I don't have to do 6 different Road classes.

import pygame
from pygame.locals import *
import sys
import os

W, H = 800, 600
HW, HH = W / 2, H / 2
AREA = W * H
os.environ['SDL_VIDEO_WINDOW_POS'] = "50,50"
FPS = 30
GREEN = (0, 200, 0)

def events():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

class Road1(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        bkgrnd = pygame.image.load("Images/Road.png").convert_alpha()
        self.image = pygame.transform.scale(bkgrnd, (300, 100))
        self.rect = self.image.get_rect()
        self.rect.x = 100
        self.rect.top = 0
        self.speedy = 0

    def update(self):
        self.rect.top += self.speedy
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            self.speedy += 0.25
        if keys[pygame.K_s]:
            self.speedy -= 0.25
        if self.speedy <= 0:
            self.speedy = 0
        if self.speedy >= 20:
            self.speedy = 20
        if self.rect.top >= 600:
            self.rect.x = 100
            self.rect.bottom = 0

class Road2(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        bkgrnd = pygame.image.load("Images/Road.png").convert_alpha()
        self.image = pygame.transform.scale(bkgrnd, (300, 100))
        self.rect = self.image.get_rect()
        self.rect.x = 100
        self.rect.top = 100
        self.speedy = 0

    def update(self):
        self.rect.top += self.speedy
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            self.speedy += 0.25
        if keys[pygame.K_s]:
            self.speedy -= 0.25
        if self.speedy <= 0:
            self.speedy = 0
        if self.speedy >= 20:
            self.speedy = 20
        if self.rect.top >= 600:
            self.rect.x = 100
            self.rect.bottom = 0

pygame.init()
CLOCK = pygame.time.Clock()
DS = pygame.display.set_mode((W, H))
pygame.display.set_caption("Scrolling")

all_sprites = pygame.sprite.Group()
road = pygame.sprite.Group()
rd1 = Road1()
all_sprites.add(rd1)
road.add(rd1)
rd2 = Road2()
all_sprites.add(rd2)
road.add(rd2)

while True:
    CLOCK.tick(FPS)
    events()
    all_sprites.update()
    DS.fill(GREEN)
    all_sprites.draw(DS)
    pygame.display.flip()

Solution

  • If the "street" is out of the window at the bottom, the it's top position is not exactly 600, it is a bit greater than 600. To keep the alignment of the streets to each other, you have to change the the top of the street by the height of the window:

    class Road1(pygame.sprite.Sprite):
        # [...]
    
        def update(self):
            #[...]
            if self.rect.top >= 600:
                self.rect.x = 100
                self.rect.top = self.rect.top - 600
    
    class Road2(pygame.sprite.Sprite):
        # [...]
    
        def update(self):
            #[...]
            if self.rect.top >= 600:
                self.rect.x = 100
                self.rect.top = self.rect.top - 600
    

    Further more, it is absolutely a bad design to generate a separate class for each part of the street. Classes have attributes and constructors of classes have parameters. Use them! THe parts of the streets differ only in its initial position. This can be a parameter to the constructor of a class Road:

    class Road(pygame.sprite.Sprite):
        def __init__(self, top):
            pygame.sprite.Sprite.__init__(self)
            bkgrnd = pygame.image.load("Images/Road.png").convert_alpha()
            self.image = pygame.transform.scale(bkgrnd, (300, 100))
            self.rect = self.image.get_rect()
            self.rect.x = 100
            self.rect.top = top # parameter top
            self.speedy = 0
    
        def update(self):
            self.rect.top += self.speedy
            keys = pygame.key.get_pressed()
            if keys[pygame.K_w]:
                self.speedy += 0.25
            if keys[pygame.K_s]:
                self.speedy -= 0.25
            if self.speedy <= 0:
                self.speedy = 0
            if self.speedy >= 20:
                self.speedy = 20
            if self.rect.top >= 600:
                self.rect.x = 100
                self.rect.top = self.rect.top - 600
    
    # [...]
    rd1 = Road(0)
    # [...]
    rd2 = Road(100)
    # [...]
    

    For an endless road you'll need 7 elements, where the 1st element is placed at -100. If an element has to be flipped to the begin, its new position is top = top-700.
    Since self.speedy is a floating point I recommend to add an attribute self.top, which is synchronized to the integral attribute self.rect.top. e.g.:

    class Road(pygame.sprite.Sprite):
        def __init__(self, top):
            pygame.sprite.Sprite.__init__(self)
            bkgrnd = pygame.image.load("Images/Road.png").convert_alpha()
            self.image = pygame.transform.scale(bkgrnd, (300, 100))
            self.rect = self.image.get_rect(topleft = (100, top))
            self.top = top
            self.speedy = 0
    
        def update(self):
            self.top += self.speedy
            if self.top > 600:
                self.top = self.top - 700
            self.rect.top = self.top
    
            keys = pygame.key.get_pressed()
            if keys[pygame.K_w]:
                self.speedy = min(20, self.speedy + 0.25)
            if keys[pygame.K_s]:
                self.speedy = max(0, self.speedy + 0.25)
    
    all_sprites = pygame.sprite.Group()
    road = pygame.sprite.Group()
    for i in range(7):
        rd = Road((i-1) * 100)
        all_sprites.add(rd)
        road.add(rd)