pygame platformer collision causes weird spacing issue

Viewed 135

I am working on a platformer and have a weird issue with the collision. So far, I have a player and some platforms and the collision does work but the horizontal collision leaves some space between the player and the level and I don't understand why.

Here is the code for the player:

import pygame 
from pygame.math import Vector2

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((32,64))
        self.image.fill('red')
        self.rect = self.image.get_rect(center = (350,250))
        self.direction = Vector2(0,0)
        self.speed = 8
        self.gravity = 0.8
        self.jump_speed = -16
        self.on_ground = False

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

        if keys[pygame.K_RIGHT]: self.direction.x = 1
        elif keys[pygame.K_LEFT]: self.direction.x = -1
        else: self.direction.x = 0

        if keys[pygame.K_SPACE] and self.on_ground:
            self.jump()
            self.on_ground = False

    def jump(self):
        self.direction.y = self.jump_speed

    def apply_gravity(self):
        self.direction.y += self.gravity
    
    def apply_movement(self):
        # horizontal movement
        self.rect.x += self.direction.x * self.speed

        # vertical movement 
        self.rect.y += self.direction.y

    def update(self):
        # movement methods 
        self.get_input()
        self.apply_gravity()
        self.apply_movement()

and here the code for the level

import pygame, sys
from player import Player


basic = [
'0000000000000',
'0000000000000',
'0010011000010',
'0111000010110',
'0111000110111',
'1111111111111',
'1111111111111',]

class Level:
    def __init__(self):
        self.player = pygame.sprite.GroupSingle(Player())
        self.tiles = pygame.sprite.Group()
        self.setup_level()

    def setup_level(self):
        for row_index, row in enumerate(basic):
            for col_index,cell in enumerate(row):
                if cell == '1':
                    x = col_index * 100
                    y = row_index * 100
                    self.tiles.add(Tile((x,y)))

    def player_collision(self):
        player = self.player.sprite
        player_size = player.rect.size
        
        # vertical collision check 
        future_y = player.rect.top + player.direction.y
        future_player_y = pygame.Rect((player.rect.left,future_y),player_size)

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(future_player_y):
                if player.direction.y >= 0:
                    player.on_ground = True
                    player.rect.bottom = sprite.rect.top
                    player.direction.y = 0
                else:
                    player.rect.top = sprite.rect.bottom
                    player.direction.y = 0

        # horizontal collision check
        future_x = self.player.sprite.rect.left + (self.player.sprite.direction.x * self.player.sprite.speed)
        future_player_x = pygame.Rect((future_x,self.player.sprite.rect.top),player_size)

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(future_player_x):
                if player.direction.x < 0:
                    player.rect.left = sprite.rect.right
                    player.rect.right += player.speed
                elif player.direction.x > 0:
                    player.rect.right = sprite.rect.left
                    player.rect.right -= player.speed

    def run(self):
        self.tiles.draw(screen)

        self.player.update()
        self.player_collision()
        self.player.draw(screen)

        

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


if __name__ == '__main__':
    pygame.init()
    screen = pygame.display.set_mode((1000,640))
    clock = pygame.time.Clock()
    debug_font = pygame.font.Font(None,40)

    level = Level()

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

        level.run()



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

I am using future_player_y because I had some earlier problems with animation frames and detecting whether the player was on the ground or not. I applied the same logic to the horizontal axis but I guess it wouldn't be strictly needed there.

The result looks like this and the collisions do work (kinda) but the red rectangle cannot move any closer to the tile to its left. enter image description here

Just for clarity, here is a zoomed in look at the area I am talking about: enter image description here

1 Answers

You need to separate the movement in the x and y direction.

Remove the apply_movement method:

class Player(pygame.sprite.Sprite):
    # [...]

    def update(self):
        # movement methods 
        self.get_input()
        self.apply_gravity()
        # self.apply_movement() <--- DELETE

Apply the movement separately before the collision test:

class Level:
    # [...]

    def player_collision(self):
        player = self.player.sprite
        
        # vertical collision check 
        player.rect.y += player.direction.y
        
        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.y >= 0:
                    player.on_ground = True
                    player.rect.bottom = sprite.rect.top
                    player.direction.y = 0
                else:
                    player.rect.top = sprite.rect.bottom
                    player.direction.y = 0

        # horizontal collision check
        player.rect.x += player.direction.x * player.speed
        
        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.x < 0:
                    player.rect.left = sprite.rect.right
                elif player.direction.x > 0:
                    player.rect.right = sprite.rect.left

Related