1
votes

I was trying to write a platformer in Pygame, but for the past few weeks I've been stuck on the collision detection between the hero and the platform. Here are my classes:

class Platform(object):
    def __init__(self, colour, rect):
        self.colour = colour
        self.rect = rect

    def draw(self, screen):
        pygame.draw.rect(screen, self.colour, self.rect)


class Character(Block):
    def __init__(self, colour, rect, speed):
        super().__init__(colour, rect)
        (self.dx, self.dy) = speed
        self.is_falling = True

    def update(self, platforms):
        self.is_falling = True
        for platform in platforms:
            if self.is_on(platform):
                self.rect.bottom = platform.rect.top
                self.dy = 0
                self.is_falling = False

        if self.is_falling:
            self.gravity()
        self.rect.x += self.dx
        self.rect.y += self.dy

    def is_on(self, platform):
        return (pygame.Rect(self.rect.x, self.rect.y + self.dy,
                            self.rect.width, self.rect.height)
                .colliderect(platform.rect) and self.dy > 0)

    def left(self):
        self.dx = -5

    def right(self):
        self.dx = 5

    def stop_x(self):
        self.dx = 0

    def jump(self):
        if self.dy == 0:
            self.dy = -25

    def gravity(self):
        self.dy += 5

and here are my functions:

def key_down(event, character):
    if event.key == pygame.K_LEFT:
        character.left()
    elif event.key == pygame.K_RIGHT:
        character.right()
    elif event.key == pygame.K_UP:
        character.jump()

def key_up(event, character):
    if event.key in (pygame.K_LEFT, pygame.K_RIGHT):
        character.stop_x()

def main():
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    pygame.display.set_caption("PyPlatformer")
    hero = Character((255, 255, 0), pygame.Rect(100, 0, 20, 20), (0, 0))
    platform1 = Block((0, 255, 255), pygame.Rect(100, 100, 100, 10))
    platform2 = Block((0, 255, 255), pygame.Rect(150, 150, 100, 10))
    platforms = (platform1, platform2)
    clock = pygame.time.Clock()
    while True:
        clock.tick(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                key_down(event, hero)
            elif event.type == pygame.KEYUP:
                key_up(event, hero)

        screen.fill((0, 0, 0))
        hero.update(platforms)
        [platform.draw(screen) for platform in platforms]
        hero.draw(screen)
        pygame.display.update()

However, when a Character is sitting on a Block, the collision detection fails, and the character falls through the block. However, when it is moving and inside the block, the detection succeeds and the character goes back to sitting on the block. This cycle then repeats and the character looks like it is bouncing on the block.

What is causing this bug? How do I fix it?

1
add many print with different values, slow down game to 2 FPS and observe printed text .furas

1 Answers

0
votes

This version works for me:

def is_on(self, platform):
    return (pygame.Rect(self.rect.x, self.rect.y + self.dy+1, # +1
                        self.rect.width, self.rect.height)
            .colliderect(platform.rect)) 

I add +1 and remove dy > 0


By The Way: I found other problem.

If you put platforms too close (see 128 in platform2)

platform1 = Block((0, 255, 255), pygame.Rect(100, 100, 100, 10))
platform2 = Block((0, 255, 255), pygame.Rect(150, 128, 100, 10)) # 128

player standing on platform2 can touch platform1 and he is moved on platform1

It can move me realy high :)

platforms = []
platforms.append(Block((0, 255, 255), pygame.Rect(100, 400, 100, 10)))
platforms.append(Block((0, 255, 255), pygame.Rect(150, 428, 100, 10)))
platforms.append(Block((0, 255, 255), pygame.Rect(250, 28, 10, 400)))