Léo Lepage
Léo Lepage

Reputation: 89

My pygame sprites get missaligned overtime

I am trying to develop a platformer, but when I started implementing gravity and a jump mechanic the sprites of my terrain get misaligned on the y axis and I really don't know what is wrong.

My code consist of tree main classes:

There is also a class named Game but the issue is 100% not coming from there as all this class does for now is initialize the level class.

import pygame, sys

pygame.init()

# Settings
tile_size = 64
fov = 10
screen_width = 1200
screen_height = tile_size * fov

level_data = [
'                       ',
'                       ',
'                  XX   ',
'XX    XXX              ',
'XX                   XX',
'XXXX        XX         ',
'XXXX  P   XX           ',
'XX    X  XXX    XX  X  ',
'      X  XXX    XX  XX ',
'   XXXX  XXXXX  XX  XXX',
'XXXXXXX  XXXXX  XX  XXX']

# Classes
class Game():
    def __init__(self):
        self.level = Level(screen)
        self.status = 'level'

    def run(self):
        if self.status == 'level':
            self.level.run()

class Level:
    def __init__(self,display_surface):
        # Basic setup
        self.setup_level()
        self.display_surface = display_surface

        # Movement
        self.x_shift = 0
        self.y_shift = 0

    def setup_level(self):
        self.tiles = pygame.sprite.Group()
        self.player = pygame.sprite.GroupSingle()
        for row_index,row in enumerate(level_data):
            for col_index,col in enumerate(row):
                x = col_index * tile_size
                y = (row_index * tile_size) - ((len(level_data) - fov) * tile_size)
                if col == 'X':
                    tile = Tile((x,y))
                    self.tiles.add(tile)
                if col == 'P':
                    player = Player((x,y))
                    self.player.add(player)

    def apply_gravity(self):
        player = self.player.sprite
        self.y_shift = player.direction.y

    def run(self):
        # Tiles
        self.tiles.update(self.x_shift,self.y_shift)
        self.tiles.draw(self.display_surface)

        # Player
        self.player.update()
        self.apply_gravity()
        self.player.draw(self.display_surface)

class Tile(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        self.image = pygame.Surface((tile_size,tile_size))
        self.image.fill('grey')
        self.rect = self.image.get_rect(topleft = pos)

    def update(self,x_shift,y_shift):
        self.rect.x += x_shift
        self.rect.y -= y_shift

class Player(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        # Image
        self.image = pygame.Surface((32,64))
        self.image.fill('red')
        self.rect = self.image.get_rect(topleft = pos)

        # Movement
        self.direction = pygame.math.Vector2(0,0)
        self.gravity = 0.8

    def get_inputs(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_SPACE]:
            self.direction.y = -20

    def apply_gravity(self):
        self.direction.y += self.gravity

    def update(self):
        self.get_inputs()
        self.apply_gravity()

# Game setup
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption('Platformer')
clock = pygame.time.Clock()
game = Game()

# Main
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    
    screen.fill('black')
    game.run()

    pygame.display.update()
    clock.tick(60)

Upvotes: 1

Views: 82

Answers (1)

Rabbid76
Rabbid76

Reputation: 210909

This is a very common problem and is related to pygame.Rect. Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.

The coordinates for Rect objects are all integers. [...]

The fraction part of the coordinates gets lost when the new offset is added to the position of the Rect object. If this is done every frame, the position error will accumulate over time.

self.rect.x += x_shift
self.rect.y -= y_shift

If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:

class Tile(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        self.image = pygame.Surface((tile_size,tile_size))
        self.image.fill('grey')
        self.rect = self.image.get_rect(topleft = pos)
        self.x = self.rect.x
        self.y = self.rect.y

    def update(self,x_shift,y_shift):
        self.x += x_shift
        self.y -= y_shift
        self.rect.topleft = round(self.x), round(self.y)

Upvotes: 1

Related Questions