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!
The 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()