# Pygame wall collision is acting very odd

I've been playing around with python for a while, but I only did basic functional programming. Lately, I've been teaching myself about classes. I've been using pygame (instead of tk, because let's face it, it's much more fun)

So I have a simple script, with a player class, a wall class, and a game class. When the player hits the wall, it stops in it's place like it should, but only if it's moving in one direction, that is - x>0 and only x. if y is >0 too, I'm getting really odd behaviors as the player just flies out of the screen :( I shall post the script below, I realize it's not as orderly as an object-oriented game should be, please take into consideration that I am new to OO.

import pygame
pygame.init()

BLACK = 0, 0, 0
RED = 255,0,0
GREEN = 0,255,0
BLUE = 0,0,255
WHITE = 255,255,255

class Player(pygame.sprite.Sprite):

def __init__(self, color, width, height, x, y, speed):
super(pygame.sprite.Sprite, self).__init__()

self.move_x = 0
self.move_y = 0
self.speed = speed
self.width = width
self.height = height
self.image = pygame.Surface([self.width, self.height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y

def move(self):
self.rect.x += self.move_x
self.rect.y += self.move_y

class Wall(pygame.sprite.Sprite):

def __init__(self, width, height, x, y):
super(pygame.sprite.Sprite, self).__init__()

self.width = width
self.height = height
self.image = pygame.Surface([self.width, self.height])
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y

def collision(self, player):

if self.rect.colliderect(player.rect):

if player.move_x > 0:
# moving right
player.rect.right = self.rect.left

elif player.move_x < 0:
#moving left
player.rect.left = self.rect.right

elif player.move_y < 0:
# moving up
player.rect.top = self.rect.bottom

elif player.move_y >0:
#moving down
player.rect.bottom = self.rect.top

class Game():

def __init__( self, x, y, fps ):

#initialize
self.x = x
self.y = y

self.screen = pygame.display.set_mode( ( x,y ) )
self.running = True

self.clock = pygame.time.Clock()
self.fps = fps
self.fpsFont = pygame.font.SysFont('mono', 22)

self.player = Player(BLUE, 50, 50, 400, 400, 5)
self.wall = wall = Wall(1220,40,0,0)

def on_event(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False

# Get keydowns and adjust player speed
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.player.move_x += self.player.speed
if event.key == pygame.K_LEFT:
self.player.move_x -= self.player.speed
if event.key == pygame.K_UP:
self.player.move_y -= self.player.speed
if event.key == pygame.K_DOWN:
self.player.move_y += self.player.speed

# Get keyups and resets vector
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
self.player.move_x = 0
if event.key == pygame.K_LEFT:
self.player.move_x = 0
if event.key == pygame.K_UP:
self.player.move_y = 0
if event.key == pygame.K_DOWN:
self.player.move_y = 0

def draw_rects(self):
# draw all the images to the screen
self.screen.blit(self.player.image, (self.player.rect.x, self.player.rect.y))
self.screen.blit(self.wall.image, (self.wall.rect.x, self.wall.rect.y))

def draw_fps(self):
#Displays an fps counter
text = "FPS: " + str(round(self.clock.get_fps()))
self.renderedFPS = self.fpsFont.render(text, True, BLACK)
self.screen.blit(self.renderedFPS, (0 , self.y - self.y / 20))

def mainloop(self):
#Game loop
while self.running == True:

# Event loop
self.on_event()

# Game Logic
self.player.move()
self.wall.collision(self.player)

# Draw everything
self.screen.fill( WHITE )
self.draw_fps()
self.draw_rects()
pygame.display.flip()

# Limit FPS to 60
self.clock.tick(self.fps)

if __name__ == "__main__":

game = Game(1220, 720, 60)
game.mainloop()


You might need to look into axis separation. When it comes to collision detection in 2d with rects, you need to split up your movement and collision detection into separate x- and y-axis.

In other words, you should try moving your player on the x-axis first, then check for collision on that axis. If there is a collision, adjust on the x-axis only.

After that, do the same for the y-axis. Move the player on the y-axis and then check and correct movement against the rect on the y-axis.

This prevents the object from jumping around and it makes it easier to detect the position as you're only dealing with one axis at a time.

So add separate functions for moving your character on the x- and y-axis.

Do the same for the collision function and I think this could improve your collision detection. Look up axis separation if you need further information. I believe that is what it is called.

I had similar issues way back when I was first starting out with game programming in pygame. Axis separation as I have described it has become an essential tool for player collision detection, even in 3D.

The problem happens because when you are pressing both the up key and either the left or right key, lets use right in this case, your player.move_y is negative, and that moved the player.rect.top to the wall.rect.bottom as you want, but the player.move_x is also possative, or negative if moving left, so the function not only moves your player to the bottom of the wall but it also teleports him/her/it to the left of the wall as well because the player.rect.left = wall.rect.left argument is also being called.

To fix this issue, you may be able to put something like this bit of code before your first if statement after if "self.rect.colliderect(player.rect):" in Wall.collisions() function:

if player.move_x > 0 and player.move_y < 0:
# moving up and right
player.rect.top = self.rect.bottom
return
elif player.move_x < 0 and player.move_y < 0:
# moving up and left
player.rect.top = self.rect.bottom
return


The return is meant to return the function before it has a chance to hit the rest of the statement and cause the bug if you are moving up right/up left.

This bit of code only fixes it for the bottom of the wall, but from this, I'm sure you can work out how to get the rest of the wall covered if you need it.

I hope this helps. :)

• Thank you for the answer, it does work when going up and left/right. However, it doesn't work for down and left/right nor when hitting the sides of a rectangle.. In which case, the sprite will jump to the top or bottom of the rectangle. – Netanel Sep 16 '15 at 23:34