3

So I was making a pygame platformer and I got stuck on one thing. I coudn't find a way to make the bottom of my platforms solid. The player could land on the top of it but when it tries to go through the bottom it bounces back down. I tried this but it didnt work:

hits = pg.sprite.spritecollide(player, platforms, False)
if hits:
    if player.pos.y == hits[0].rect.top:
        player.vel.y = 10
    else:
        player.pos.y = hits[0].rect.top + 1
        player.vel.y = 0

Does anyone got a solution for me? Here's the complete program.

skrx
  • 19,980
  • 5
  • 34
  • 48
sanjay
  • 33
  • 5

1 Answers1

1

Here's a short platformer example. Especially the movement is important. You have to move along the x-axis first, check if the player collides with a wall and move it back if a collision occurred. Afterwards do the same with the y-axis. If you don't split the movement into these two parts, your player will jump to the sides, top or bottom of the wall if you press more than one movement key at the same time.

import pygame as pg


pg.init()
WINDOW_WIDTH, WINDOW_HEIGHT = 800, 600
screen = pg.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
GRAY = pg.Color('gray24')
GRAVITY = 800


class Player(pg.sprite.Sprite):

    def __init__(self, pos, blocks):
        super().__init__()
        self.image = pg.Surface((30, 50))
        self.image.fill(pg.Color(0, 110, 170))
        self.rect = self.image.get_rect(topleft=pos)
        self.vel = pg.math.Vector2(0, 0)
        self.pos = pg.math.Vector2(pos)
        self.blocks = blocks
        self.on_ground = False

    def update(self, dt):
        # Move along x-axis.
        self.pos.x += self.vel.x * dt
        self.rect.x = self.pos.x

        collisions = pg.sprite.spritecollide(self, self.blocks, False)
        for block in collisions:  # Horizontal collision occurred.
            if self.vel.x > 0:  # Moving right.
                self.rect.right = block.rect.left  # Reset the rect pos.
            elif self.vel.x < 0:  # Moving left.
                self.rect.left = block.rect.right  # Reset the rect pos.
            self.pos.x = self.rect.x  # Update the actual x-position.

        # Move along y-axis.
        self.pos.y += self.vel.y * dt
        # +1 to check if we're on a platform each frame.
        self.rect.y = self.pos.y + 1
        # Prevent air jumping when falling.
        if self.vel.y > 0:
            self.on_ground = False

        collisions = pg.sprite.spritecollide(self, self.blocks, False)
        for block in collisions:  # Vertical collision occurred.
            if self.vel.y > 0:  # Moving down.
                self.rect.bottom = block.rect.top  # Reset the rect pos.
                self.vel.y = 0  # Stop falling.
                self.on_ground = True
            elif self.vel.y < 0:  # Moving up.
                self.rect.top = block.rect.bottom  # Reset the rect pos.
                self.vel.y = 0  # Stop jumping.
            self.pos.y = self.rect.y  # Update the actual y-position.

        # Stop the player at screen bottom.
        if self.rect.bottom >= WINDOW_HEIGHT:
            self.vel.y = 0
            self.rect.bottom = WINDOW_HEIGHT
            self.pos.y = self.rect.y
            self.on_ground = True
        else:
            self.vel.y += GRAVITY * dt  # Gravity


class Block(pg.sprite.Sprite):

    def __init__(self, rect):
        super().__init__()
        self.image = pg.Surface(rect.size)
        self.image.fill(pg.Color('paleturquoise2'))
        self.rect = rect


def main():
    clock = pg.time.Clock()
    done = False
    dt = 0

    all_sprites = pg.sprite.Group()
    blocks = pg.sprite.Group()
    player = Player((300, 100), blocks)
    all_sprites.add(player)
    rects = ((300, 200, 30, 70), (100, 350, 270, 30),
             (500, 450, 30, 170), (400, 570, 270, 30),
             (500, 150, 70, 170), (535, 310, 270, 70))
    for rect in rects:  # Create the walls/platforms.
        block = Block(pg.Rect(rect))
        all_sprites.add(block)
        blocks.add(block)

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_a:
                    player.vel.x = -220
                elif event.key == pg.K_d:
                    player.vel.x = 220
                elif event.key == pg.K_w:  # Jump
                    if player.on_ground:
                        player.vel.y = -470
                        player.pos.y -= 20
                        player.on_ground = False
            elif event.type == pg.KEYUP:
                if event.key == pg.K_a and player.vel.x < 0:
                    player.vel.x = 0
                elif event.key == pg.K_d and player.vel.x > 0:
                    player.vel.x = 0

        all_sprites.update(dt)

        screen.fill(GRAY)
        all_sprites.draw(screen)

        pg.display.flip()
        dt = clock.tick(60) / 1000


if __name__ == '__main__':
    main()
    pg.quit()

Here's a working version of the code that you've posted in the comments (only with vertical collisions, you need to add horizontal collisions as well). So when the player is jumping and collides with a platform, you have to set the player.rect.top to the platform.rect.bottom and change the vel.y.

import pygame as pg
from pygame.math import Vector2 as vec


pg.init()
WIDTH, HEIGHT = 800, 600
YELLOW = pg.Color('yellow')
GREEN = pg.Color('green')
BLACK = pg.Color('gray11')
screen = pg.display.set_mode((WIDTH,HEIGHT))
clock = pg.time.Clock()
FPS = 60
PLAYER_FRICTION = .95
PLAYER_ACC = .2


class Player(pg.sprite.Sprite):

    def __init__(self):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((30, 40))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect(center=(WIDTH/2, HEIGHT-30))
        self.pos = vec(WIDTH/2, HEIGHT/2)
        self.vel = vec(0,0)
        self.acc = vec(0,0)

    def jump(self):
        self.rect.y += 1
        hits = pg.sprite.spritecollide(self, platforms, False)
        self.rect.y -= 1
        if hits:
            self.vel.y = -13

    def update(self):
        self.acc = vec(0, 0.5)
        keys = pg.key.get_pressed()
        if keys[pg.K_a]:
            self.acc.x = -PLAYER_ACC
        if keys[pg.K_d]:
            self.acc.x = PLAYER_ACC

        # apply friction
        self.vel.x *= PLAYER_FRICTION
        self.vel += self.acc
        self.pos += self.vel
        # wrap around the sides of the screen
        if self.pos.x > WIDTH:
            self.pos.x = 0
        if self.pos.x < 0:
            self.pos.x = WIDTH

        self.rect.midbottom = self.pos


class Platform(pg.sprite.Sprite):
    def __init__(self, x, y, w, h):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((w, h))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect(topleft=(x, y))


all_sprites = pg.sprite.Group()
platforms = pg.sprite.Group()

player = Player()
all_sprites.add(player)
# spawns and adds platforms to group
p1 = Platform(0, HEIGHT - 40, WIDTH, 40)
p2 = Platform(WIDTH / 2 - 50, HEIGHT - 300, 100, 20)
p3 = Platform(WIDTH / 2 - 100, HEIGHT - 150, 200, 20)
all_sprites.add(p1, p2, p3)
platforms.add(p1, p2, p3)

running = True
while running:
    clock.tick(FPS)
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_SPACE:
                player.jump()

    all_sprites.update()
    # Check if we hit a wall/platform.
    hits = pg.sprite.spritecollide(player, platforms, False)
    for platform in hits:  # Iterate over the collided platforms.
        if player.vel.y > 0:  # We're falling.
            player.rect.bottom = platform.rect.top
            player.vel.y = 0
        elif player.vel.y < 0:  # We're jumping.
            player.rect.top = platform.rect.bottom
            player.vel.y = 3

        player.pos.y = player.rect.bottom

    #Draw / render
    screen.fill(BLACK)
    all_sprites.draw(screen)
    pg.display.flip()


pg.quit()

BTW, in the jump method you have to change self.rect.y not self.rect.x.

skrx
  • 19,980
  • 5
  • 34
  • 48
  • thanks but i want it to bounce when it collides on the bottom of a platform. anyway to do that. you are doing the bottom and top of the entire window not a single platform. – sanjay Jan 03 '18 at 01:21
  • You didn't mention that in your question. What exactly are you trying to achieve? How should the effect look like? – skrx Jan 03 '18 at 01:36
  • i want the effect to look like that the bottom of the platform is solid. specifically when the top of the player sprite hits the bottom of a platform the player sprite will shoot back down. This way the player wont be able to go through the platform. the only way to land is on the top. – sanjay Jan 03 '18 at 01:40
  • hopefully that explains it well – sanjay Jan 03 '18 at 01:41
  • If the sprite should move very fast downwards if it jumps against the bottom of a platform, you can set the velocity to a high value like `400` instead of `0` in this line: `self.vel.y = 0 # Stop jumping`. – skrx Jan 03 '18 at 06:19
  • ok i got that but what code would i have to write to make the collision the code i listed at the very top didnt work for me it just went thorugh the platform didnt even detect collision. – sanjay Jan 03 '18 at 07:40
  • I'm not sure what you mean and why you still have problems. Have you tried to implement the code in my answer in your project? – skrx Jan 03 '18 at 07:55
  • Please add your code (a [minimal, runnable example](https://stackoverflow.com/help/mcve)) to the question. – skrx Jan 03 '18 at 08:53
  • if player.vel.y > 0: hits = pg.sprite.spritecollide(player, platforms, False) if hits: if player.rect.top == hits[0].rect.bottom: player.vel.y = 10 else: player.pos.y = hits[0].rect.top + 1 player.vel.y = 0 – sanjay Jan 04 '18 at 03:15
  • that is the code i used to detect the player collison with the platform. my problem s if the player sprite jumps to the bottom of the platform it will just go through it. i dont want the player to go through the bottom of the platform. the only way that he has to land is from the top. – sanjay Jan 04 '18 at 03:16
  • Try to reduce your program to the minimum first, but make sure that it can still be run. It often takes a lot of time to read and analyze the code of someone else. If you don't know how to minimize it, just post everything. – skrx Jan 04 '18 at 03:22
  • i coundnt paste entire program into chat box so i used pastebin instead – sanjay Jan 04 '18 at 05:36
  • when the player tries to jump to the bottom of the platform is goes through it i dont want that to happen. if the player hits the bottom of the platform i want it to go back down. – sanjay Jan 04 '18 at 05:42
  • I just check every frame if the player collides with the platforms. If he collides and moves upwards, then he collides with the bottom of the platform, so I set his `rect.top` to the `platform.rect.bottom`. If he moves downwards, I do the same but set his `rect.bottom` to the platform's `rect.top`. And the `pos` has to be updated as well. – skrx Jan 04 '18 at 07:40
  • for platform in hits: # Iterate over the collided platforms. if player.vel.y > 0: # We're falling. player.rect.bottom = platform.rect.top player.vel.y = 0 elif player.vel.y < 0: # We're jumping. player.rect.top = platform.rect.bottom player.vel.y = 3 how does this all work? – sanjay Jan 04 '18 at 07:42