Search code examples
pythonpygame2dtile

Pygame 2d tile scrolling edges don't load


Im trying to make a 2d game in pygame that kinda works like pokemon. Ive gotten really stuck on a problem now that I dont know how to solve.

When I move my character I scroll the map instead so the player stays centered. I've created an "animation" by offsetting the distance by 2 pixels at a time instead of moving the full tile size so I get smooth movemen. The problem I have is that when I move, the screen doesn't load the new tiles in the edges that i'm moving towards so the edges end up a white space until i've completed the full animation. I'll link my code and hopefully someone can help me :)

TILESIZE = 32
MAP_WIDTH = 25
MAP_HEIGHT = 25

class Player(pygame.sprite.Sprite):
    def __init__(self, color, width, height):
        # Call the parent class (Sprite) constructor
        super().__init__()
        self.name = "Player"
        self.width = width
        self.height = height
        self.image = pygame.Surface([width, height])
        self.image.fill(color)
        self.rect = self.image.get_rect()
        self.rect.x = int(TILESIZE * (MAP_WIDTH / 2)) - TILESIZE / 2
        self.rect.y = int(TILESIZE * (MAP_HEIGHT / 2)) - TILESIZE / 2

class World:
    def __init__(self):
        self.shiftX = 0
        self.shiftY = 0
        self.tile_map = [ [DIRT for w in range(MAP_WIDTH)] for h in range(MAP_HEIGHT)]
        for row in range(MAP_HEIGHT):
            for column in range(MAP_WIDTH):
                try:
                    if real_map[row + self.shiftY][column + self.shiftX] == 0:
                        tile = DIRT
                    elif real_map[row + self.shiftY][column + self.shiftX] == 1:
                        tile = GRASS
                    elif real_map[row + self.shiftY][column + self.shiftX] == 2:
                        tile = WATER
                    else:
                        tile = DIRT
                    self.tile_map[row][column] = tile
                except:
                    self.tile_map[row][column] = WATER

    def shiftWorld(self):

        for row in range(MAP_HEIGHT):
            for column in range(MAP_WIDTH):
                try:
                    if real_map[row + self.shiftY][column + self.shiftX] == 0:
                        tile = DIRT
                    elif real_map[row + self.shiftY][column + self.shiftX] == 1:
                        tile = GRASS
                    elif real_map[row + self.shiftY][column + self.shiftX] == 2:
                        tile = WATER
                    else:
                        tile = DIRT
                    self.tile_map[row][column] = tile
                except:
                    self.tile_map[row][column] = WATER

    def okToMove(self, key):
        if key[K_w]:
            if self.tile_map[int(MAP_WIDTH/2 - 1)][int(MAP_HEIGHT/2)] != 2:
                return True
        elif key[K_s]:
            if self.tile_map[int(MAP_WIDTH/2 + 1)][int(MAP_HEIGHT/2)] != 2:
                return True
        elif key[K_a]:
            if self.tile_map[int(MAP_WIDTH/2)][int(MAP_HEIGHT/2) - 1] != 2:
                return True
        elif key[K_d]:
            if self.tile_map[int(MAP_WIDTH/2)][int(MAP_HEIGHT/2) + 1] != 2:
                return True

def start_game():
    pygame.init()
    clock = pygame.time.Clock()
    #HÄR KAN VI MÅLA UPP MER
    #SCREEN = pygame.display.set_mode((MAP_WIDTH*TILESIZE, MAP_HEIGHT*TILESIZE))
    world = World()
    SCREEN = pygame.display.set_mode((TILESIZE * (MAP_WIDTH-2), TILESIZE * (MAP_HEIGHT-4)))
    running = True
    player = Player(BLACK, 32, 32)
    sprites = pygame.sprite.Group()
    sprites.add(player)
    movement = 0
    offsetY = 0
    offsetX = 0
    animation_north = False
    animation_south = False
    animation_west = False
    animation_east = False

    while running:
        for event in pygame.event.get():
            if event.type==QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                #Get keyinput and do whatever needs to be done
                key = pygame.key.get_pressed()
                if key[K_ESCAPE]:
                    pygame.quit()
                    sys.exit()

                if animation_east or animation_north or animation_south or animation_west:
                    pass
                else:
                    if key[K_w]:
                        okToMove = world.okToMove(key)
                        if okToMove == True:
                            animation_north = True
                        else:
                            pass
                    elif key[K_a]:
                        okToMove = world.okToMove(key)
                        if okToMove == True:
                            animation_west = True

                    elif key[K_s]:
                        okToMove = world.okToMove(key)
                        if okToMove == True:
                            animation_south = True

                    elif key[K_d]:
                        okToMove = world.okToMove(key)
                        if okToMove == True:
                            animation_east = True




        if animation_north == True:
            if movement == 32:
                movement = 0
                world.shiftY -= 1
                world.shiftWorld()
                offsetY = 0
                animation_north = False

            else:
                offsetY += 4
                movement += 4

        if animation_south == True:
            if movement == 32:
                movement = 0
                world.shiftY += 1
                world.shiftWorld()
                offsetY = 0
                animation_south = False
                intY = 0
            else:
                offsetY -= 4
                movement += 4

        if animation_west == True:
            if movement == 32:
                movement = 0
                world.shiftX -= 1
                world.shiftWorld()
                offsetX = 0
                animation_west = False
            else:
                offsetX += 4
                movement += 4

        if animation_east == True:
            if movement == 32:
                world.shiftX += 1
                world.shiftWorld()
                movement = 0
                offsetX = 0
                animation_east = False
            else:
                offsetX -= 4
                movement += 4


        SCREEN.fill(WHITE)

        for row in range(MAP_HEIGHT):
            for column in range(MAP_WIDTH):
                SCREEN.blit(textures[world.tile_map[row][column]], (column*TILESIZE + offsetX, row*TILESIZE + offsetY))


        sprites.draw(SCREEN)
        pygame.display.update()
        pygame.display.flip()
        clock.tick(60)

start_game()

Solution

  • I am writing a similar game and I will share my logic with you. my blocks are 32x32 so each block is 32 pixesl.

    enter image description here

    The outer border is the sprites screen and the inner square is the monitor. You always have one sprite extra on all sides of the screen. now if you count the pixel movement on any side of the screen it's easy for you to keep track of when you need to draw the next row or column of sprites but not that they are always DRAWN OFF SCREEN. if my pixel movement is -8 (left movement) I draw a column of sprites on the right side just on the border os the screen but OUTSIDE the visible area. Same goes for the other side.

    Here is some code from my program. This is the sprite adding code.

        def add_sprites(self):
    
        """sprites are added to the group which appear on screen right. the column number is the value in ColDrawn. We selct columns from the list according to this value. Once the end of the column list is reached we start again from the first one. We cycle thru the list depending on the NumCycle[0] value."""
    
        if self.ColDrawn < self.Columns_in_Dungeon - 1:
            self.ColDrawn += 1
    
        else:  # all the columns drawn so increment the flag
            self.ColDrawn = 0
            self.NumCycle[1] += 1
    
        if self.NumCycle[1] >= self.NumCycle[0]:  # if the flag is equal to the number of cycles the screen is scrolled then set numcycle[2] to True
            self.NumCycle[2] = True
    
        else:  # screen can be scrolled
            spritecol = self.all_wall_sprite_columns_list[self.ColDrawn]
            self.wallspritegroup.add(spritecol)  # add column of sprites to the sprite group
        return
    

    and here is the sprite removing code.

        def remove_sprites(self):
    
        """sprites are removed from the group as they exit from screen left."""
    
        for sprt in self.wallspritegroup:  # remove_basic sprites that move_basic off screen on left
    
            if sprt.rect.x <= -48:
                sprt.rect.x = self.screenw # reset the x position and
                sprt.kill()
                #spritegrp.remove_basic(sprt)  # remove_basic the sprite from the sprite group
    
        return
    

    The code is quite easy to follow as I have commented them. Hope this helps.