0

I am creating a simple 2D platformer in Pygame. In this project I have been trying to use classes much more for good practice. My current problem is that I am trying to make the screen center on the player, however I cannot find a way to do this with the way my code is currently set up.

In addition, I plan to later add a way to make the screen follow the player with a small amount of lag, following faster as it gets further away. However, making the camera center on the player is my main concern.

When the player is moving right, I have all the "PlatformRects" move left at the same speed. I have tried different speeds, setting the player to zero speed and just moving the "PlatformRects" almost works (related comment in code in the "update" func of the "Player" class). I had also tried making the "PlatformRects" movement depend on the position of the player, but I couldn't get this to work either.

I would appreciate any help. Here's my code (I trimmed out a lot of irrelevant code, like animating and such):

import pygame, sys, random
#---Classes----------------------------------------#
class PlatformRect(pygame.sprite.Sprite):
    def __init__(self,x_pos,y_pos,size_x,size_y,path,players):
        super().__init__()
        self.x_pos = x_pos
        self.y_pos = y_pos
        self.size_x = size_x
        self.size_y = size_y
        self.players = players
        self.can_jump1 = None

        if path == "null":
            self.image = pygame.Surface((self.size_x,self.size_y))
            self.rect = self.image.get_rect(topleft = (self.x_pos,self.y_pos))
        else:
            self.image = pygame.transform.scale(pygame.image.load(path).convert_alpha(),(size_x,size_y))
            self.rect = self.image.get_rect(topleft = (self.x_pos,self.y_pos))

    def collision_check(self):
        if pygame.sprite.spritecollide(self,self.players,False): 
            collision_paddle = pygame.sprite.spritecollide(self,self.players,False)[0].rect
            if player.movement_y > 0 and abs(self.rect.top - collision_paddle.bottom) < 10: #load of checks to ensure the correct side is being collided 
                collision_paddle.bottom = self.rect.top 
                player.movement_y = 0
                self.can_jump1 = True
            elif player.movement_y < 0 and abs(self.rect.bottom - collision_paddle.top) < 10 and not abs(self.rect.right - collision_paddle.left) < 10 and not abs(self.rect.left - collision_paddle.right) < 10:
                collision_paddle.top = self.rect.bottom
                player.movement_y *= -0.2
                print("moving bottom")
            elif player.movement_x < 0 and abs(self.rect.right - collision_paddle.left) < 10 and not abs(self.rect.bottom - collision_paddle.top) < 10:
                collision_paddle.left = self.rect.right
                print("moving left")
            elif player.movement_x > 0 and abs(self.rect.left - collision_paddle.right) < 10 and not abs(self.rect.bottom - collision_paddle.top) < 10:
                collision_paddle.right = self.rect.left
                print("moving right")
        else:
            self.can_jump1 = self.rect.top == self.players.sprites()[0].rect.bottom  

    def can_jump_check(self):
        return self.can_jump1

    def update(self):
        self.collision_check()
        self.rect.centerx -= player.movement_x #trying to get rects to move the same distance as the player, if player moves 2 pixels right, rect moves 2 pixels left?
        self.rect.centery -= 0


class Player(pygame.sprite.Sprite):
    def __init__(self,x_pos,y_pos,size_x,size_y,speed_x,acceleration_y): 
        super().__init__()
        self.x_pos = x_pos
        self.y_pos = y_pos
        self.size_x = size_x
        self.size_y = size_y
        self.speed_x = speed_x
        self.shift_pressed = False
        self.acceleration_y = acceleration_y
        self.movement_x = 0 
        self.movement_y = 0
        
        self.image = pygame.Surface((self.size_x,self.size_y))
        self.image.fill("red")
        self.rect = self.image.get_rect(center = (self.x_pos,self.y_pos))

    def screen_constrain(self): 
        if self.rect.bottom >= sresy:
            self.rect.bottom = sresy

    def update(self):
        if abs(self.movement_y) <= 8: 
            self.movement_y += GRAVITY
        self.rect.centery += self.movement_y
        if self.shift_pressed == False:
            self.rect.centerx += self.movement_x #if set to 0, scrolling kindof works, although camera still scrolls even if player is stuck on block
        elif self.shift_pressed == True: 
            self.rect.centerx += 2*self.movement_x
        self.screen_constrain()

class GameManager:
    def __init__(self,player_group,platform_group):
        self.player_group = player_group
        self.platform_group = platform_group
        self.can_jump = True

    def run_game(self):
        #---drawing---#
        self.player_group.draw(screen)
        self.platform_group.draw(screen)

        #---updating---#
        self.player_group.update()
        self.platform_group.update()

    def game_checking(self):
        #---checking---#
        self.can_jump = any(p.can_jump_check() for p in self.platform_group)  
        return self.can_jump
        
#---Setup------------------------------------------#
#---constants-----#
global GRAVITY
GRAVITY = 0.25

#---Gamevariables-----#
can_jump = True
shift_pressed = False
music_playing = False

#---colour---#
bg_colour = (50,50,50)
white_colour = (255,255,255)
black_colour = (0,0,0)

#---res---#
resx = 900 
resy = 700
resx_moved = resx - 50
resy_moved = resy - 50
sresx = 850 #base surface size, to be scaled up to resx_moved and resy_moved
sresy = 650

#---game map---# 
game_map = [[0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],
            [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
            [0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
            [0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1],
            [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]

#---start window-----#
pygame.init()
clock = pygame.time.Clock()

screendisplay = pygame.display.set_mode((resx,resy))
screendisplay.fill(bg_colour)
pygame.display.set_caption("PlatformerGame") 

screen = pygame.Surface((sresx,sresy))
screen.fill(white_colour)

#---startgame-----#
player = Player(425,325,50,50,2,30)
player_group = pygame.sprite.GroupSingle() 
player_group.add(player) 

platform_list = []
y_gm = 0
for row in game_map:
    x_gm = 0
    for tile in row:
        if tile == 1:
            platform_list.append(PlatformRect(x_gm * 50, y_gm * 50, 50, 50, "null",player_group)) #sets platform at given positions and size
        x_gm += 1
    y_gm += 1
platform_group = pygame.sprite.Group() 
platform_group.add(platform_list) 

game_manager = GameManager(player_group,platform_group)

#---Loop--------------------------------------------#
while True:
    #---events-----#
    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_ESCAPE: #emergency
                pygame.quit()
                sys.exit()

            if event.key == pygame.K_SPACE: 
                if can_jump == True:
                    player.movement_y = 0
                    player.movement_y -= player.acceleration_y * GRAVITY 

            if event.key == pygame.K_LSHIFT: #sprinting
                player.shift_pressed = True

            if event.key == pygame.K_a: 
                player.movement_x -= player.speed_x

            if event.key == pygame.K_d: 
                player.movement_x += player.speed_x

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_SPACE: 
                if player.movement_y < 0: 
                    player.movement_y = 0

            if event.key == pygame.K_a: 
                player.movement_x += player.speed_x

            if event.key == pygame.K_d: 
                player.movement_x -= player.speed_x

            if event.key == pygame.K_LSHIFT: 
                player.shift_pressed = False

    #---background---#
    surf = pygame.transform.scale(screen,(resx_moved,resy_moved)) #changes resolution
    screendisplay.blit(surf,(25,25))
    screen.fill(white_colour) 

    #---running---#
    game_manager.run_game()
    if not game_manager.game_checking() == True and player.rect.bottom < sresy: #checking if can_jump is true
        can_jump = False
    else:
        can_jump = True

    #---updating-----#
    pygame.display.update()
    clock.tick(120)

I will of course clarify anything about my code if it is not clear.

seanie5011
  • 15
  • 4
  • You should provide a [mre] on what You have tried and what failed. But essentially what You would need is to similarly as moving just move the background `blit` position in the opposite of player moving. – Matiiss May 13 '21 at 22:18
  • 3
    don't move objects but keep their original position - and when you draw objects then substract `offset` to their positions base on player position - `blit(pos-offset)`. `offset` can be `player_position - screen.center`. And substract it from all objects - even player - and then player will be displayed in `player_position - offset` which gives `player_position - (player_position - screen.center)` which finally gives `screen.center` so player will be displayed in the center of screen. – furas May 13 '21 at 22:54

1 Answers1

1

You should move the camera like this:

# at the beginning: set camera
camera = pygame.math.Vector2((0, 0))

# in the main loop: adjust the camera position to center the player
camera.x = player.x_pos - resx / 2
camera.y = player.y_pos - resy / 2

# in each sprite class: move according to the camera instead of changing the sprite position
pos_on_the_screen = (self.x_pos - camera.x, self.y_pos - camera.y)
  • The camera follows the player, and places itself so that the player is in the center of the screen
  • The position of each sprite never changes, this is a very bad idea to constantly change the position of the sprites.
  • Each sprite is drawn on the screen depending of the camera

To reduce lag, you should display each sprite only if it is visible:

screen_rect = pygame.Rect((0, 0), (resx, resy))
sprite_rect = pygame.Rect((self.x_pos - camera.x, self.y_pos - camera.y), sprite_image.get_size())
if screen_rect.colliderect(sprite_rect):
    # render if visible

Here is a screenshot of a moving background in a game I made, using the same method:

Here I move the camera more smoothly. Basically I use this method:

speed = 1
distance_x = player.x_pos - camera.x
distance_y = player.y_pos - camera.y
camera.x = (camera.x * speed + distance_x) / (speed + 1)
camera.y = (camera.y * speed + distance_y) / (speed + 1)

And I change speed according to the FPS.

D_00
  • 1,440
  • 2
  • 13
  • 32
  • Just to clarify when you say "pos_on_the_screen", do you mean to change the rect position? I can't seem to implement it correctly. If you mean to make it so that it draws the apparent position of the rect (so the rect pos - camera pos), how would I implement this with the sprite groups? I cant find anything in the documentation that says how to change where the sprite groups are drawn to without changing the pos of the rect itself. – seanie5011 May 14 '21 at 19:09
  • I believe I have sorted my issue! I never add a line of code to alter the players position by the camera vector. I solved the sprites issue by removing the draw part of my GameManager instead using a for loop to blit each sprite. – seanie5011 May 14 '21 at 22:59