Zoler1337
Zoler1337

Reputation: 116

Collision in PyGame with sprite.spritecollide and collide_rect

I'm practicing with Pygame trying to learn some basic usage of classes, objects etc.

Right now I'm making a basic platformer with the help of http://programarcadegames.com/

I have a sprite Player and sprites Platforms. The platforms can also be MovingPlatforms which are supposed to move the player on collision, however when the player gets moved into other platforms some weird teleports happen and I do not manage to see the issues right now.

Basically the player is not being moved logically (to me at least) when pushed.

Very thankful for any help! Code below of Player sprite and Platform sprites. They should contain any form of collision. Also full code if anyone wants to run it (just need any "background.jpg" image-file)

Player Sprite:

class Player(pygame.sprite.Sprite):

    def __init__(self):
        super().__init__()

        self.player_width = 25 
        self.player_height = 50
        self.image = pygame.Surface([self.player_width,self.player_height])
        self.image.fill([255,0,0])
        self.rect = self.image.get_rect()

        self.change_x = 0
        self.change_y = 0
        self.double_jump = 0

        self.level = None

    def update(self):
        self.calc_gravity()
        
        self.rect.x += self.change_x
        collision_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
        for platform in collision_list:
            if self.change_x > 0:
                self.rect.right = platform.rect.left
            elif self.change_x < 0:
                self.rect.left = platform.rect.right

        self.rect.y += self.change_y
        collision_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
        for platform in collision_list:
            if self.change_y > 0:
                self.rect.bottom = platform.rect.top
                self.double_jump = 0
            elif self.change_y < 0:
                self.rect.top = platform.rect.bottom
            self.change_y = 0

    def calc_gravity(self):
        if self.change_y == 0:
            self.change_y = 2
        else:
            self.change_y += 0.35

        if self.rect.y >= screen_height - self.player_height - 32 and self.change_y >= 0:
            self.change_y = 0
            self.rect.y = screen_height - self.player_height - 32

Platform and MovingPlatform Sprites:

class Platform(pygame.sprite.Sprite):

    def __init__(self, width, height):
        super().__init__()

        self.image = pygame.Surface([width, height])
        self.image.fill([173,255,47])
        self.rect = self.image.get_rect()

class MovingPlatform(Platform):

    change_x = 0
    change_y = 0
    
    boundary_top = 0
    boundary_bottom = 0
    boundary_left = 0
    boundary_right = 0

    def update(self):
        self.rect.x += self.change_x
        hit = pygame.sprite.collide_rect(self, self.player)
        if hit:
            if self.change_x < 0:
                self.player.rect.right = self.rect.left
            elif self.change_x > 0:
                self.player.rect.left = self.rect.right

        self.rect.y += self.change_y
        hit = pygame.sprite.collide_rect(self, self.player)
        if hit:
            if self.change_y < 0:
                self.player.rect.bottom = self.rect.top
            else:
                self.player.rect.top = self.rect.bottom

        if self.rect.bottom >= self.boundary_bottom or self.rect.top <= self.boundary_top:
            self.change_y *= -1

        cur_pos = self.rect.x - self.level.world_shift
        if cur_pos < self.boundary_left or cur_pos > self.boundary_right:
            self.change_x *= -1

Full code:

import pygame

screen_width = 800
screen_height = 600

class Player(pygame.sprite.Sprite):

    def __init__(self):
        super().__init__()

        self.player_width = 25 
        self.player_height = 50
        self.image = pygame.Surface([self.player_width,self.player_height])
        self.image.fill([255,0,0])
        self.rect = self.image.get_rect()

        self.change_x = 0
        self.change_y = 0
        self.double_jump = 0

        self.level = None

    def update(self):
        self.calc_gravity()
        
        self.rect.x += self.change_x
        collision_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
        for platform in collision_list:
            if self.change_x > 0:
                self.rect.right = platform.rect.left
            elif self.change_x < 0:
                self.rect.left = platform.rect.right

        self.rect.y += self.change_y
        collision_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
        for platform in collision_list:
            if self.change_y > 0:
                self.rect.bottom = platform.rect.top
                self.double_jump = 0
            elif self.change_y < 0:
                self.rect.top = platform.rect.bottom
            self.change_y = 0

    def calc_gravity(self):
        if self.change_y == 0:
            self.change_y = 2
        else:
            self.change_y += 0.35

        if self.rect.y >= screen_height - self.player_height - 32 and self.change_y >= 0:
            self.change_y = 0
            self.rect.y = screen_height - self.player_height - 32
            self.double_jump = 0
        
    def jump(self):
        self.rect.y += 2
        collision_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
        self.rect.y -= 2
        
        if len(collision_list) > 0 or self.rect.bottom >= screen_height - 32:
            self.change_y = -10
        elif self.double_jump == 0:
            self.change_y = -10
            self.double_jump = 1

    def go_left(self):
        self.change_x = -5
    def go_right(self):
        self.change_x = 5
    def stop(self):
        self.change_x = 0

class Platform(pygame.sprite.Sprite):

    def __init__(self, width, height):
        super().__init__()

        self.image = pygame.Surface([width, height])
        self.image.fill([173,255,47])
        self.rect = self.image.get_rect()

class MovingPlatform(Platform):

    change_x = 0
    change_y = 0
    
    boundary_top = 0
    boundary_bottom = 0
    boundary_left = 0
    boundary_right = 0

    def update(self):
        self.rect.x += self.change_x
        hit = pygame.sprite.collide_rect(self, self.player)
        if hit:
            if self.change_x < 0:
                self.player.rect.right = self.rect.left
            elif self.change_x > 0:
                self.player.rect.left = self.rect.right

        self.rect.y += self.change_y
        hit = pygame.sprite.collide_rect(self, self.player)
        if hit:
            if self.change_y < 0:
                self.player.rect.bottom = self.rect.top
            else:
                self.player.rect.top = self.rect.bottom

        if self.rect.bottom >= self.boundary_bottom or self.rect.top <= self.boundary_top:
            self.change_y *= -1

        cur_pos = self.rect.x - self.level.world_shift
        if cur_pos < self.boundary_left or cur_pos > self.boundary_right:
            self.change_x *= -1

class Level():

    def __init__(self, player):
        self.platform_list = pygame.sprite.Group()
        self.player = player
        self.world_shift = 0
    
    def update(self):
        self.platform_list.update()
    
    def draw(self, screen, background):
        screen.blit(background, [0,0])
        self.platform_list.draw(screen)
    
    def shift_world(self, shift_x):
        self.world_shift += shift_x

        for platform in self.platform_list:
            platform.rect.x += shift_x
        
class Level_01(Level):

    def __init__(self, player):
        Level.__init__(self, player)
        self.level_limit = -140
        level = [[200, 25, 500, 500],
                 [100, 25, 100, 300]
                 ]

        for platform in level:
            block = Platform(platform[0], platform[1])
            block.rect.x = platform[2]
            block.rect.y = platform[3]
            block.player = self.player
            self.platform_list.add(block)
        
        block = MovingPlatform(150, 25)
        block.rect.x = 200
        block.rect.y = 300
        block.boundary_top = 0
        block.boundary_bottom = screen_height - 30
        block.change_y = 1 
        block.player = self.player
        block.level = self
        self.platform_list.add(block)
        
        block = MovingPlatform(150, 25)
        block.rect.x = 100
        block.rect.y = 350
        block.boundary_left = 0
        block.boundary_right = 600
        block.change_x = 2
        block.player = self.player
        block.level = self
        self.platform_list.add(block)
           
class Level_02(Level):

    def __init__(self, player):
        Level.__init__(self, player)
        self.level_limit = -1000
        level = [[200, 25, 500, 500],
                 [100, 25, 100, 100]
                 ]
        
        for platform in level:
            block = Platform(platform[0], platform[1])
            block.rect.x = platform[2]
            block.rect.y = platform[3]
            block.player = self.player
            self.platform_list.add(block)

def main():
    screen = pygame.display.set_mode([screen_width,screen_height])
    clock = pygame.time.Clock()
    done = False
    background = pygame.image.load("background.jpg")
    background = pygame.transform.scale(background, [800,600])
    
    camera_left = 250
    camera_right = 550

    active_sprite_list = pygame.sprite.Group()
    player = Player()

    level_list =[]
    level_list.append(Level_01(player))
    level_list.append(Level_02(player))
    current_level_no = 0
    current_level = level_list[current_level_no]

    player.level = current_level
    player.rect.x = 100
    player.rect.y = screen_height - player.player_height - 25
    active_sprite_list.add(player)

    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            
            #---Key presses---
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_a:
                    player.go_left()
                elif event.key == pygame.K_d:
                    player.go_right()
                elif event.key == pygame.K_SPACE:
                    player.jump()
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_a and player.change_x < 0:
                    player.stop()
                elif event.key == pygame.K_d and player.change_x > 0:
                    player.stop()
        
        #---Move camera---
        if player.rect.left <= camera_left:
            diff = camera_left - player.rect.left
            player.rect.left = camera_left
            current_level.shift_world(diff)
            
        if player.rect.right >= camera_right:
            diff = player.rect.right - camera_right
            player.rect.right = camera_right
            current_level.shift_world(-diff)
        
        #---Change level---
        current_position = player.rect.x + current_level.world_shift
        if current_position < current_level.level_limit:
            player.rect.x = 120
            if current_level_no < len(level_list)-1:
                current_level_no += 1
                current_level = level_list[current_level_no]
                player.level = current_level

        #---Update sprites---
        current_level.update()
        active_sprite_list.update()
        
        #---Draw sprites---
        current_level.draw(screen, background)
        active_sprite_list.draw(screen)
        
        clock.tick(60)
        pygame.display.flip()
    pygame.quit()

main()

Upvotes: 1

Views: 2260

Answers (1)

Rabbid76
Rabbid76

Reputation: 210928

The problem is related to MovingPlatform.update:

class MovingPlatform(Platform):
  # [...]

  def update(self):
       self.rect.x += self.change_x
       hit = pygame.sprite.collide_rect(self, self.player)
       if hit:
           if self.change_x < 0:
               self.player.rect.right = self.rect.left
           elif self.change_x > 0:
               self.player.rect.left = self.rect.right

       # [...]

When the player stands on a platform moving up and down and hits another platform, the player is moved on either side of that platform.

Before asking how to solve the problem, one must ask what is supposed to happen in this particular situation.

One possible solution is to simply remove the code above:

Alternatively, you can add an additional condition that checks if the player is to the left or right of the platform, but not in the middle:

class MovingPlatform(Platform):
    # [...]

    def update(self):
        self.rect.x += self.change_x
        hit = pygame.sprite.collide_rect(self, self.player)
        if hit:
            if self.change_x < 0 and self.player.rect.left < self.rect.left:
                self.player.rect.right = self.rect.left
            elif self.change_x > 0 and self.player.rect.right > self.rect.right:
                self.player.rect.left = self.rect.right

        # [...]

However, this will not solve the problem if the player is squeezed by 2 platforms.

Upvotes: 1

Related Questions