Search code examples
pythonpygame

How can I stop my player from glitching through my platform?


So I'm making a small project and I can't understand why my player keeps going up and down when it collides with my platform even when I want it to be static. The problem is in the code below and I can't find a fix.

def Collision_Manager():
    if player.rect.colliderect(platform.rect):
        if abs(player.rect.bottom >= platform.rect.top):
            player.y = platform.y - player.height

I cant understand why it's not a seamless animation like my border system which I did by doing this.

def player_borders():
    if player.x <= 0:
        player.x = 0
    elif player.x >= 870:
        player.x = 870
    if player.y <= 0:
        player.y = 0
    elif player.y >= 570:
        player.y = 570

Solution

  • player.y and platform.y are floating point coordinates. pygame.Rect objects however can just store integral coordinates.

    The issue is caused when the .rect attribute is updated with the player's position and the coordinates are truncated. e.g.:

    self.rect.topleft = int(self.x), int(self.y)
    

    A possible solution might be to round the coordinates:

    self.rect.topleft = round(self.x), round(self.y)
    

    Or round up the y coordinate:

    self.rect.topleft = round(self.x), int(math.ceil(self.y))
    

    Depending on the control flow of your code, it may also be necessary to update the .rect attribute when a collision is detected:

    if player.rect.colliderect(platform.rect):
        if player.rect.bottom >= platform.rect.top:
            platform.rect.bottom = platform.rect.top
            player.y = platform.rect.y
    

    By the way, abs() is completely useless here:

    if abs(player.rect.bottom >= platform.rect.top):

    if player.rect.bottom >= platform.rect.top:
    

    It doesn't any harm, however, since it's abs(True) or abs(False), which results in 1 or 0.


    Minimal example (see also Platform collision):

    import pygame
    import random
    
    pygame.init()
    window = pygame.display.set_mode((500, 500))
    clock = pygame.time.Clock()
    
    class Player(pygame.sprite.Sprite):
        JUMP_ACCELERATION = -7
        def __init__(self, midbottom_pos):
            super().__init__() 
            self.image = pygame.Surface((30, 30), pygame.SRCALPHA)
            self.image.fill("red") 
            self.rect = self.image.get_rect(midbottom = midbottom_pos)
            self.y = self.rect.bottom
            self.vel_y = 0
            self.acc_y = 0
        def jump(self):
            self.acc_y = Player.JUMP_ACCELERATION
        def update(self, platforms):
            self.vel_y += GRAVITY
            self.y += self.vel_y
            self.rect.bottom = round(self.y + 1)
            colliders = pygame.sprite.spritecollide(self, platforms, False)
            if colliders:
                self.y = colliders[0].rect.top
                self.vel_y = self.acc_y
                self.y += self.vel_y
            elif self.y > window.get_height():
                self.y = 100
            self.acc_y = 0
            self.rect.bottom = round(self.y)
    
    class Platform(pygame.sprite.Sprite):
        PLATFORM_SHIFT = 1.5
        def __init__(self, topleft_pos):
            super().__init__() 
            self.image = pygame.Surface((200, 10), pygame.SRCALPHA)
            self.image.fill("gray") 
            self.rect = self.image.get_rect(topleft = topleft_pos)
            self.x = self.rect.x
        def update(self):
            self.x -= Platform.PLATFORM_SHIFT
            self.rect.x = round(self.x)
            if self.rect.right <= 0:
                self.kill()
    
    GRAVITY = 0.2
    platfrom_group = pygame.sprite.Group(Platform((0, 250)))
    player = Player((50, 100))
    plyer_sprites = pygame.sprite.GroupSingle(player)
    
    run = True
    while run:
        clock.tick(100)
        acc_y = GRAVITY
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            if event.type == pygame.KEYDOWN: 
                if event.key == pygame.K_SPACE:
                    player.jump()
    
        platfrom_group.update()
        while platfrom_group.sprites()[-1].rect.right < window.get_width():
            last_platform_rect = platfrom_group.sprites()[-1].rect
            if last_platform_rect.top - 50 < 150:
                new_y = last_platform_rect.top + 50
            elif last_platform_rect.top + 50 > window.get_height() - 50:
                new_y = last_platform_rect.top - 50
            else:
                new_y = last_platform_rect.top + (50 if random.random() > 0.5 else -50)
            platfrom_group.add(Platform((last_platform_rect.right, new_y)))
            print(len(platfrom_group.sprites()))
        player.update(platfrom_group)
    
        window.fill((0, 0, 64))
        platfrom_group.draw(window)
        plyer_sprites.draw(window)
        pygame.display.flip()
    
    pygame.quit()
    exit()