pygame platformer – How can I make the bottom solid?

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.

Leave a Comment