I know many questions have already been asked on this same topic, but I can't figure out from those how to solve my problem (possibly due to a different approach to achieve the same goal: scrolling my camera keeping the main character of my game centered in the screen).
I am developing my first 2D platform game with python and pygame.
I am having difficulties in making a camera which follows the player each times he moves, keeping him in the center of the screen.
What is happening is that the camera keeps following the main character as he passes over the center of the screen, but, as he keeps moving in one direction (say leftward), he suddenly keeps closer to the border towards he is moving (i.e. the camera "is too slow" in following him...). I know it has probably to do with the camera shift not taking the hero horizontal and vertical speed, but I don't know how to solve this problem at the moment.
I have two separate classes for the main character and the camera. I am about to attach a bit of my code (it seems a lot but it's "only" the relevant part of code to understand the problem, and there's plenty of spaces and comments...). I hope someone could give a good hint!
Hero class is actually a child of
Character class, so here are both:
class Character(pygame.sprite.Sprite): """class with generic character's attrs and methods (intended to be inherited from players and enemies in the game)""" def __init__(self, x, y, life, img): super().__init__() # Call the parent class (Sprite) constructor self.img = img self.image = pygame.image.load(os.path.join(SPRITE_FOLDER, img)).convert_alpha() self.rect = self.image.get_rect() self.x = x self.y = y self.rect.x, self.rect.y = self.x, self.y self.hspeed = 5 self.vspeed = 0 self.life = life def gravity_force(self, gravity = .35): if self.vspeed == 0: self.vspeed = 1 else: self.vspeed += gravity def collision(self, block_list = pygame.sprite.Group()): """hanlde collisions between hero and environment. 'collision_list' should contain a list of sprite against which collision is detected for the main character""" self.gravity_force() self.rect.top += self.vspeed # let the hero falling # See if the character Sprite has collided with anything in the Group block_list # (returns a Sprite_list) collision_list = pygame.sprite.spritecollide(self, block_list, False) () # check for collisions in each direction for colliding in collision_list: if self.vspeed > 0: # if is falling... self.rect.bottom = colliding.rect.top # ... if hits a block, place it on it... self.vspeed = 0 # ... and stop vertical speed class Hero(Character): """main Hero class""" # If you want to extend the parent's __init__() method, you should do: # def __init__(self, x, y, life, img, *args): # super().__init__(x, y, life, img) # REDEFINE super() methods and add additional attributes def movement(self, event = None): """handle character's interaction with the level and keyboard inputs""" if event: if event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: self.rect.left += self.hspeed # move topleft rectangle 'hspeed' pixels left if event.key == pygame.K_LEFT: self.rect.left -= self.hspeed # move topleft rectangle 'hspeed' pixels right # if event.key == pygame.K_UP: # self.rect.top += 5
And here is the
Camera class (a rectangle with size equal to the screen, whose center coordinates are moved to match the Hero's ones each frame):
class Camera(): """Surface containing the Hero at its center and all objects nearby it""" def __init__(self, screen): """create a camera Rect whose size is equal to screen size""" self.rect = screen.get_rect() self.x = self.rect.x self.y = self.rect.y self.dx = 0 # initialize camera x shift self.dy = 0 # initialize camera y shift def follow_target(self, target): """move the camera so that target Rect is always centered""" self.rect.clamp_ip(target.rect) # center camera to target # do not accept negative coords if self.rect.x < 0: self.rect.x = 0 if self.rect.y < 0: self.rect.y = 0 # calculate dx, dy based on previous and current camera position self.dx = self.x - self.rect.x self.dy = self.y - self.rect.y # set current camera pos permanently self.x = self.rect.x self.y = self.rect.y def apply_shift(self, target): """move target Rect according to camera pos""" target.rect.move_ip(self.dx, self.dy) # move target position of a given offset
And finally the parts in the parts of my
main() function of the game relevant to my issue:
screen.blit(level.background, (0, 0)) # draw background camera = Camera(screen) # creates camera while True: # main game loop .... # draw blackground on moving objects (hero, platforms, enemies) # (to delete just them on each frame instead of redrawing the entire screen, # as this will increase game performance) screen.blit(level.background, hero.rect, hero.rect) # delete hero for block in level.block_group: screen.blit(level.background, block.rect, block.rect) # delete other objects # check for hero moves nad collision hero.movement(event) hero.collision(level.block_group) camera.follow_target(hero) # centers the hero to the camera Rect # update blocks pos according to hero pos for block in level.block_group: camera.apply_shift(block) screen.blit(block.image, block.rect) # draw hero screen.blit(hero.image, hero.rect) pygame.display.update()