Skip to content
Advertisement

How to handle vector based movement and x axis tile map collisions in pygame

I have a problem with my pygame script in which the way I check for collisions doesn’t seem to work with the x axis and presents some very frustrating and downright stupid errors. The way it works is I first check for movement in the axis of choice, here is an example in the y axis. I then get a list of tiles I collide with and check to see if there are any collisions and the tile’s collision index is for the top of itself:

if self.velocity.y > 0:
            collide_rect = pygame.sprite.spritecollide(self, self.tile_group, False)
            if collide_rect and collide_rect[0].top_collide == True:
                self.position.y = collide_rect[0].rect.top + 1
                self.velocity.y = 0

The last two lines set the position of the player, set at the bottom left of it’s rectangle, to the top of the tile + 1 pixel to avoid jittering. I then set the y velocity to 0. But here is the code I have for the x axis:

if self.velocity.x > 0:
            collide_rect = pygame.sprite.spritecollide(self, self.tile_group, False)
            if collide_rect and collide_rect[0].left_collide == True:
                self.rect.right = collide_rect[0].rect.left

If I run this code and collide to the left of a tile, I stop for a moment and go through the tile. If I also set the x velocity to zero, the same thing happens. If I set the x velocity to -5, the player jitters inside of the tile and in the ground as well. And worst of all, if the tile has top and side collision, the player glitches out on top of it due to how the player is technically inside the tile to avoid the horrible jittering the collision causes otherwise, and ends up being shot to the left most area of the tile. This is most frustrating as I just want to make a platformer game in pygame, like Mario or Sonic, with basic collision that makes sense.

I’ve searched google and stack overflow for answers to my problem, but the most I found was a tutorial for collision in the y axis, which works for me just fine. The second best was saying basically just search up proper game design docs, which is the most unhelpful advice I have heard since that’s why I’m here. (BTW, I use a 2D vector system for my movement) The expected result I wish to have is the player staying in place when running up against a wall without clipping into the ground or wall visibly. Here is the link to the file I’m using to create this.

Answer

Similar questions have been asked many times, and the answer is still the same. You need to separate the motion and collision detection along the X-axis and Y-axis. First, perform the fall and jump and limit the vertical position according to the obstacles. Then move the player horizontally and limit the horizontal position of the player according to the obstacles.
If you do not separate the directions, the direction you treat first always “wins”. e.g. if you fall (even minimally) and move to the left at the same time, the player is always automatically levitated onto each object, since the vertical collision control also detects a collision and lifts the player to the next level.

Minimal example based on your code:

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

    def update(self):
        self.acceleration = vector(0, self.VERTICAL_ACCELERATION)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_a]:
            self.acceleration.x -= self.HORIZONTAL_ACCELERATION
        if keys[pygame.K_d]:
            self.acceleration.x += self.HORIZONTAL_ACCELERATION

        self.acceleration.x -= self.velocity.x * self.HORIZONTAL_FRICTION
        self.velocity += self.acceleration
        
       # Motion and collision detection along the y-axis

        self.position.y += self.velocity.y + .5 * self.acceleration.y
        self.rect.bottomleft = self.position

        collide_rect = pygame.sprite.spritecollide(self, self.tile_group, False)
        if collide_rect:
            if self.velocity.y > 0:
                self.velocity.y = 0
                self.position.y = collide_rect[0].rect.top
                self.rect.bottom = self.position.y
            elif self.velocity.y < 0:
                self.velocity.y = 0
                self.rect.top = collide_rect[0].rect.bottom
                self.rect.bottom = self.position.y

        # Motion and collision detection along the x-axis            

        self.position.x += self.velocity.x + .5 * self.acceleration.x
        self.rect.bottomleft = self.position
        
        collide_rect = pygame.sprite.spritecollide(self, self.tile_group, False)
        if collide_rect:
            if self.velocity.x > 0:     
                self.rect.right = collide_rect[0].rect.left
                self.position.x = self.rect.x
                self.velocity.x = 0
            elif self.velocity.x < 0:
                self.rect.left = collide_rect[0].rect.right
                self.position.x = self.rect.x
                self.velocity.x = 0

    def jump(self):
        self.rect.y += 1
        if pygame.sprite.spritecollide(self, self.tile_group, False):
            print("jump")
            self.velocity.y -= 10
        self.rect.y -= 1
Advertisement