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
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
.