I'm working on a small platformer game in which you place blocks to make a level, then play it.
I got gravity, jumping, and left and right movement.. but I am not sure how to make the player collide with walls when moving left or right.
The way I want it to work is like this-
if key[K_LEFT]:
if not block to the left:
move to the left
How would I go about doing this (relative to this source):
import pygame,random
from pygame.locals import *
import itertools
pygame.init()
screen=pygame.display.set_mode((640,480))
class Block(object):
sprite = pygame.image.load("texture\\dirt.png").convert_alpha()
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
class Player(object):
sprite = pygame.image.load("texture\\playr.png").convert()
sprite.set_colorkey((0,255,0))
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
blocklist = []
player = []
colliding = False
while True:
screen.fill((25,30,90))
mse = pygame.mouse.get_pos()
key=pygame.key.get_pressed()
if key[K_LEFT]:
p.rect.left-=1
if key[K_RIGHT]:
p.rect.left+=1
if key[K_UP]:
p.rect.top-=10
for event in pygame.event.get():
if event.type == QUIT: exit()
if key[K_LSHIFT]:
if event.type==MOUSEMOTION:
if not any(block.rect.collidepoint(mse) for block in blocklist):
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
else:
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
to_remove = [b for b in blocklist if b.rect.collidepoint(mse)]
for b in to_remove:
blocklist.remove(b)
if not to_remove:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
elif event.button == 3:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
player=[]
player.append(Player(x+16,y+16))
for b in blocklist:
screen.blit(b.sprite, b.rect)
for p in player:
if any(p.rect.colliderect(block) for block in blocklist):
#collide
pass
else:
p.rect.top += 1
screen.blit(p.sprite, p.rect)
pygame.display.flip()
A common approch is to seperate the horizontal and vertical collision handling into two seperate steps.
If you do this and also track the velocity of your player, it's easy to know on which side a collision happened.
First of all, let's give the player some attributes to keep track of his velocity:
class Player(object):
...
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
# indicates that we are standing on the ground
# and thus are "allowed" to jump
self.on_ground = True
self.xvel = 0
self.yvel = 0
self.jump_speed = 10
self.move_speed = 8
Now we need a method to actually check for a collision. As already said, to make things easy, we use our xvel
and yvel
to know if we collided with our left or right side etc. This goes into the Player
class:
def collide(self, xvel, yvel, blocks):
# all blocks that we collide with
for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:
# if xvel is > 0, we know our right side bumped
# into the left side of a block etc.
if xvel > 0: self.rect.right = block.rect.left
if xvel < 0: self.rect.left = block.rect.right
# if yvel > 0, we are falling, so if a collision happpens
# we know we hit the ground (remember, we seperated checking for
# horizontal and vertical collision, so if yvel != 0, xvel is 0)
if yvel > 0:
self.rect.bottom = block.rect.top
self.on_ground = True
self.yvel = 0
# if yvel < 0 and a collision occurs, we bumped our head
# on a block above us
if yvel < 0: self.rect.top = block.rect.bottom
Next, we move our movement handling to the Player
class. So let's create on object that keeps track of the input. Here, I use a namedtuple
, because why not.
from collections import namedtuple
...
max_gravity = 100
Move = namedtuple('Move', ['up', 'left', 'right'])
while True:
screen.fill((25,30,90))
mse = pygame.mouse.get_pos()
key = pygame.key.get_pressed()
for event in pygame.event.get():
...
move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])
for p in player:
p.update(move, blocklist)
screen.blit(p.sprite, p.rect)
We pass the blocklist
to the update
method of the Player
so we can check for collision. Using the move
object, we now know where the player should move, so let's implement Player.update
:
def update(self, move, blocks):
# check if we can jump
if move.up and self.on_ground:
self.yvel -= self.jump_speed
# simple left/right movement
if move.left: self.xvel = -self.move_speed
if move.right: self.xvel = self.move_speed
# if in the air, fall down
if not self.on_ground:
self.yvel += 0.3
# but not too fast
if self.yvel > max_gravity: self.yvel = max_gravity
# if no left/right movement, x speed is 0, of course
if not (move.left or move.right):
self.xvel = 0
# move horizontal, and check for horizontal collisions
self.rect.left += self.xvel
self.collide(self.xvel, 0, blocks)
# move vertically, and check for vertical collisions
self.rect.top += self.yvel
self.on_ground = False;
self.collide(0, self.yvel, blocks)
The only thing left is to use a Clock
to limit the framerate to let the game run at a constant speed. That's it.
Here's the complete code:
import pygame,random
from pygame.locals import *
from collections import namedtuple
pygame.init()
clock=pygame.time.Clock()
screen=pygame.display.set_mode((640,480))
max_gravity = 100
class Block(object):
sprite = pygame.image.load("dirt.png").convert_alpha()
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
class Player(object):
sprite = pygame.image.load("dirt.png").convert()
sprite.set_colorkey((0,255,0))
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
# indicates that we are standing on the ground
# and thus are "allowed" to jump
self.on_ground = True
self.xvel = 0
self.yvel = 0
self.jump_speed = 10
self.move_speed = 8
def update(self, move, blocks):
# check if we can jump
if move.up and self.on_ground:
self.yvel -= self.jump_speed
# simple left/right movement
if move.left: self.xvel = -self.move_speed
if move.right: self.xvel = self.move_speed
# if in the air, fall down
if not self.on_ground:
self.yvel += 0.3
# but not too fast
if self.yvel > max_gravity: self.yvel = max_gravity
# if no left/right movement, x speed is 0, of course
if not (move.left or move.right):
self.xvel = 0
# move horizontal, and check for horizontal collisions
self.rect.left += self.xvel
self.collide(self.xvel, 0, blocks)
# move vertically, and check for vertical collisions
self.rect.top += self.yvel
self.on_ground = False;
self.collide(0, self.yvel, blocks)
def collide(self, xvel, yvel, blocks):
# all blocks that we collide with
for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:
# if xvel is > 0, we know our right side bumped
# into the left side of a block etc.
if xvel > 0: self.rect.right = block.rect.left
if xvel < 0: self.rect.left = block.rect.right
# if yvel > 0, we are falling, so if a collision happpens
# we know we hit the ground (remember, we seperated checking for
# horizontal and vertical collision, so if yvel != 0, xvel is 0)
if yvel > 0:
self.rect.bottom = block.rect.top
self.on_ground = True
self.yvel = 0
# if yvel < 0 and a collision occurs, we bumped our head
# on a block above us
if yvel < 0: self.rect.top = block.rect.bottom
blocklist = []
player = []
colliding = False
Move = namedtuple('Move', ['up', 'left', 'right'])
while True:
screen.fill((25,30,90))
mse = pygame.mouse.get_pos()
key = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == QUIT: exit()
if key[K_LSHIFT]:
if event.type==MOUSEMOTION:
if not any(block.rect.collidepoint(mse) for block in blocklist):
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
else:
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
to_remove = [b for b in blocklist if b.rect.collidepoint(mse)]
for b in to_remove:
blocklist.remove(b)
if not to_remove:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
elif event.button == 3:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
player=[]
player.append(Player(x+16,y+16))
move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])
for b in blocklist:
screen.blit(b.sprite, b.rect)
for p in player:
p.update(move, blocklist)
screen.blit(p.sprite, p.rect)
clock.tick(60)
pygame.display.flip()
Note that I changed the image names so I just need a single image file for testing this. Also, I don't know why you keep the player in a list, but here's a nice animation of our game in action:
Since you're using pygame, you can use pygame's rect.colliderect()
to see if the player's sprite is colliding with a block. Then make a function that returns the side a certain rect is on relative to the other rect:
def rect_side(rect1, rect2): # Returns side of rect1 relative to rect2.
if rect1.x > rect2.x:
return "right"
else:
return "left"
# If rect1.x == rect2.x the function will return "left".
Than if rect_side()
returns "right"
you restrict movement to the left and vice-versa.
PS: If you want the same but including vertical movement you can compare rect1.y
to rect2.y
and deal with outputs "up"
and "down"
. You can make a tuple that represents horizontal and vertical directions.