Collision detection doesn't work in pygame
Question:
I am trying to make a top down game and I need functional walls for it to work, i have tried it multiple times but when i hit two directions at once the player phases through the wall. the system i have now is supposed to cancel out any velocity the player has by subtracting/adding it to the current (x, y) coordinates but it doesn’t work, i have also tried making it so there is supposed to be no reaction when you press the button but that breaks the code too.
import pygame
import random
#sets up the screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("collision test")
#defines the size and coords for the player
wall_height, wall_width = 200, 20
wall_x, wall_y = 400, 300
last_key = 0
x = 400
y = 300
width = 40
height = 60
vel = 5
#where the main code is run
run = True
while run:
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#movement code
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > vel:
last_key = "left"
x -= vel
if keys[pygame.K_RIGHT] and x < 800 - width:
last_key = "right"
x += vel
if keys[pygame.K_UP] and y > vel:
last_key = "up"
y -= vel
if keys[pygame.K_DOWN] and y < 600 - height - vel:
last_key = "down"
y += vel
#draws the player
screen.fill((0,0,0))
wall = pygame.draw.rect(screen, (244, 247, 30), (wall_x, wall_y, wall_width, wall_height))
player = pygame.draw.rect(screen, (255, 255, 255), (x, y, width, height))
#collision code
if player.colliderect(wall) and last_key == "left":
x += vel
elif player.colliderect(wall) and last_key == "right":
x -= vel
elif player.colliderect(wall) and last_key == "up":
y += vel
elif player.colliderect(wall) and last_key == "down":
y -= vel
pygame.display.update()
pygame.quit()
Answers:
so you are basically throwing velocity in the opposite direction if the player collides with the wall which isnt a bad thought
I would recommend either putting a rect (rectangle) around the wall and if you collide with that rect then your velocity is == 0
alternatively you can import math and through a math equation for finding distance between two object like, then when you are within a certain range you set vel ==0
distance = math.sqrt((math.pow(x - wall_x, 2)) + (math.pow(y - wally, 2)))
if distance < 20:
vel = 0
naturally the distance of 20 was just a guess that could be tweaked
Here is a solution, it uses built in collision detection methods and it is easier to "create/build" the world, just change sth in the matrix (explanation is in code comments (without comments this is around only 80 lines of code)):
# some useless comments too in the sense that they explain what code does
# import pygame
import pygame
# initialise pygame, set display and clock
pygame.init()
screen = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()
# constant for tile size, this size is because screen is 600x600 pixels,
# there are 12 tiles per column per row so each tile gets displayed
TILE = 50
# world matrix, this is where you create the world layout of tiles,
# if you want to have thinner walls, increase the row and grid count
# and decrease the tile size
world = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
# tile list which will contain all the valid tiles (rectangles)
# you could append also the number and then change the number
# in the world matrix to apply some different textures
tile_list = []
for row_num, row in enumerate(world):
for col_num, col in enumerate(row):
# if the current tile is 1 in the matrix then calculate
# its position and append the rectangular area to the tile_list
if col == 1:
x, y = col_num * TILE, row_num * TILE
rect = pygame.Rect(x, y, TILE, TILE)
tile_list.append(rect)
# player stuff, like player's rectangle to allow for
# easier collision detection
player_rect = pygame.Rect(60, 60, 30, 30)
vel = 3
fps = 60
while True:
clock.tick(fps)
screen.fill((0, 0, 0))
# get pressed keys
keys = pygame.key.get_pressed()
# delta x and delta y values that allow to move
# player in either direction, they are like
# a helper variable since
# they don't directly affect player movement thus
# allowing for additional checks like collision detection
dx, dy = 0, 0
# the basic key stuff, the reason to reduce instead of set the
# value is in case both keys are pressed so that the player stays in place
# basically makes both keys equivalent
if keys[pygame.K_UP]:
dy -= vel
if keys[pygame.K_DOWN]:
dy += vel
if keys[pygame.K_RIGHT]:
dx += vel
if keys[pygame.K_LEFT]:
dx -= vel
# this is where collision detection starts
# first I create two projections of where the player might be
# depending on what keys they pressed, `.move()` returns a new
# rectangle allowing to easily apply `.colliderect() method
x_projection = player_rect.move(dx, 0)
y_projection = player_rect.move(0, dy)
# now for each placed tile check against collision
for tile in tile_list:
# also draw the rectangle here too
pygame.draw.rect(screen, (255, 255, 0), tile)
# can add the border
# pygame.draw.rect(screen, (255, 255, 255), tile, width=1)
# `pygame.Rect` has a built-in method for checking the collision
if x_projection.colliderect(tile):
# depending on player movement calculate delta x
# so that the player can touch the wall and there
# wouldn't be any gaps between player and wall
# the same logic for checking
# y collision
if dx > 0:
dx = tile.left - player_rect.right
elif dx < 0:
dx = tile.right - player_rect.left
elif y_projection.colliderect(tile):
if dy > 0:
dy = tile.top - player_rect.bottom
elif dy < 0:
dy = tile.bottom - player_rect.top
# move the player's rectangle in place meaning
# it moves the actual rectangle instead of returning
# an new one as `.move` would do
player_rect.move_ip(dx, dy)
# draw the player cube on screen
pygame.draw.rect(screen, (255, 0, 0), player_rect)
# can also draw border
# pygame.draw.rect(screen, (0, 0, 255), player_rect, width=1)
# event checking
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
# remember to update the display
pygame.display.update()
Sources:
If you have any questions, be sure to ask them!
I’ll give you an complete answer with minimal changes to your code.
Change the order. Do the collision detection before the drawing. Create pygame.Rect
objects:
wall = pygame.Rect(wall_x, wall_y, wall_width, wall_height)
player = pygame.Rect(x, y, width, height)
Restrict the position of the player to the boundaries of the wall:
if player.colliderect(wall) and last_key == "left":
x += vel
x = wall.right
elif player.colliderect(wall) and last_key == "right":
x -= vel
x = wall.left - width
elif player.colliderect(wall) and last_key == "up":
y += vel
y = wall.bottom
elif player.colliderect(wall) and last_key == "down":
y -= vel
y = wall.top - height
Complete example:
import pygame
#sets up the screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("collision test")
#defines the size and coords for the player
wall_height, wall_width = 200, 20
wall_x, wall_y = 400, 300
last_key = 0
x = 400
y = 240
width = 40
height = 60
vel = 5
#where the main code is run
run = True
while run:
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#movement code
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > vel:
last_key = "left"
x -= vel
if keys[pygame.K_RIGHT] and x < 800 - width:
last_key = "right"
x += vel
if keys[pygame.K_UP] and y > vel:
last_key = "up"
y -= vel
if keys[pygame.K_DOWN] and y < 600 - height - vel:
last_key = "down"
y += vel
wall = pygame.Rect(wall_x, wall_y, wall_width, wall_height)
player = pygame.Rect(x, y, width, height)
#collision code
if player.colliderect(wall) and last_key == "left":
x += vel
x = wall.right
elif player.colliderect(wall) and last_key == "right":
x -= vel
x = wall.left - width
elif player.colliderect(wall) and last_key == "up":
y += vel
y = wall.bottom
elif player.colliderect(wall) and last_key == "down":
y -= vel
y = wall.top - height
#draws the player
screen.fill((0,0,0))
pygame.draw.rect(screen, (244, 247, 30), (wall_x, wall_y, wall_width, wall_height))
pygame.draw.rect(screen, (255, 255, 255), (x, y, width, height))
pygame.display.update()
pygame.quit()
I am trying to make a top down game and I need functional walls for it to work, i have tried it multiple times but when i hit two directions at once the player phases through the wall. the system i have now is supposed to cancel out any velocity the player has by subtracting/adding it to the current (x, y) coordinates but it doesn’t work, i have also tried making it so there is supposed to be no reaction when you press the button but that breaks the code too.
import pygame
import random
#sets up the screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("collision test")
#defines the size and coords for the player
wall_height, wall_width = 200, 20
wall_x, wall_y = 400, 300
last_key = 0
x = 400
y = 300
width = 40
height = 60
vel = 5
#where the main code is run
run = True
while run:
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#movement code
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > vel:
last_key = "left"
x -= vel
if keys[pygame.K_RIGHT] and x < 800 - width:
last_key = "right"
x += vel
if keys[pygame.K_UP] and y > vel:
last_key = "up"
y -= vel
if keys[pygame.K_DOWN] and y < 600 - height - vel:
last_key = "down"
y += vel
#draws the player
screen.fill((0,0,0))
wall = pygame.draw.rect(screen, (244, 247, 30), (wall_x, wall_y, wall_width, wall_height))
player = pygame.draw.rect(screen, (255, 255, 255), (x, y, width, height))
#collision code
if player.colliderect(wall) and last_key == "left":
x += vel
elif player.colliderect(wall) and last_key == "right":
x -= vel
elif player.colliderect(wall) and last_key == "up":
y += vel
elif player.colliderect(wall) and last_key == "down":
y -= vel
pygame.display.update()
pygame.quit()
so you are basically throwing velocity in the opposite direction if the player collides with the wall which isnt a bad thought
I would recommend either putting a rect (rectangle) around the wall and if you collide with that rect then your velocity is == 0
alternatively you can import math and through a math equation for finding distance between two object like, then when you are within a certain range you set vel ==0
distance = math.sqrt((math.pow(x - wall_x, 2)) + (math.pow(y - wally, 2)))
if distance < 20:
vel = 0
naturally the distance of 20 was just a guess that could be tweaked
Here is a solution, it uses built in collision detection methods and it is easier to "create/build" the world, just change sth in the matrix (explanation is in code comments (without comments this is around only 80 lines of code)):
# some useless comments too in the sense that they explain what code does
# import pygame
import pygame
# initialise pygame, set display and clock
pygame.init()
screen = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()
# constant for tile size, this size is because screen is 600x600 pixels,
# there are 12 tiles per column per row so each tile gets displayed
TILE = 50
# world matrix, this is where you create the world layout of tiles,
# if you want to have thinner walls, increase the row and grid count
# and decrease the tile size
world = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
# tile list which will contain all the valid tiles (rectangles)
# you could append also the number and then change the number
# in the world matrix to apply some different textures
tile_list = []
for row_num, row in enumerate(world):
for col_num, col in enumerate(row):
# if the current tile is 1 in the matrix then calculate
# its position and append the rectangular area to the tile_list
if col == 1:
x, y = col_num * TILE, row_num * TILE
rect = pygame.Rect(x, y, TILE, TILE)
tile_list.append(rect)
# player stuff, like player's rectangle to allow for
# easier collision detection
player_rect = pygame.Rect(60, 60, 30, 30)
vel = 3
fps = 60
while True:
clock.tick(fps)
screen.fill((0, 0, 0))
# get pressed keys
keys = pygame.key.get_pressed()
# delta x and delta y values that allow to move
# player in either direction, they are like
# a helper variable since
# they don't directly affect player movement thus
# allowing for additional checks like collision detection
dx, dy = 0, 0
# the basic key stuff, the reason to reduce instead of set the
# value is in case both keys are pressed so that the player stays in place
# basically makes both keys equivalent
if keys[pygame.K_UP]:
dy -= vel
if keys[pygame.K_DOWN]:
dy += vel
if keys[pygame.K_RIGHT]:
dx += vel
if keys[pygame.K_LEFT]:
dx -= vel
# this is where collision detection starts
# first I create two projections of where the player might be
# depending on what keys they pressed, `.move()` returns a new
# rectangle allowing to easily apply `.colliderect() method
x_projection = player_rect.move(dx, 0)
y_projection = player_rect.move(0, dy)
# now for each placed tile check against collision
for tile in tile_list:
# also draw the rectangle here too
pygame.draw.rect(screen, (255, 255, 0), tile)
# can add the border
# pygame.draw.rect(screen, (255, 255, 255), tile, width=1)
# `pygame.Rect` has a built-in method for checking the collision
if x_projection.colliderect(tile):
# depending on player movement calculate delta x
# so that the player can touch the wall and there
# wouldn't be any gaps between player and wall
# the same logic for checking
# y collision
if dx > 0:
dx = tile.left - player_rect.right
elif dx < 0:
dx = tile.right - player_rect.left
elif y_projection.colliderect(tile):
if dy > 0:
dy = tile.top - player_rect.bottom
elif dy < 0:
dy = tile.bottom - player_rect.top
# move the player's rectangle in place meaning
# it moves the actual rectangle instead of returning
# an new one as `.move` would do
player_rect.move_ip(dx, dy)
# draw the player cube on screen
pygame.draw.rect(screen, (255, 0, 0), player_rect)
# can also draw border
# pygame.draw.rect(screen, (0, 0, 255), player_rect, width=1)
# event checking
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
# remember to update the display
pygame.display.update()
Sources:
If you have any questions, be sure to ask them!
I’ll give you an complete answer with minimal changes to your code.
Change the order. Do the collision detection before the drawing. Create pygame.Rect
objects:
wall = pygame.Rect(wall_x, wall_y, wall_width, wall_height)
player = pygame.Rect(x, y, width, height)
Restrict the position of the player to the boundaries of the wall:
if player.colliderect(wall) and last_key == "left":
x += vel
x = wall.right
elif player.colliderect(wall) and last_key == "right":
x -= vel
x = wall.left - width
elif player.colliderect(wall) and last_key == "up":
y += vel
y = wall.bottom
elif player.colliderect(wall) and last_key == "down":
y -= vel
y = wall.top - height
Complete example:
import pygame
#sets up the screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("collision test")
#defines the size and coords for the player
wall_height, wall_width = 200, 20
wall_x, wall_y = 400, 300
last_key = 0
x = 400
y = 240
width = 40
height = 60
vel = 5
#where the main code is run
run = True
while run:
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#movement code
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > vel:
last_key = "left"
x -= vel
if keys[pygame.K_RIGHT] and x < 800 - width:
last_key = "right"
x += vel
if keys[pygame.K_UP] and y > vel:
last_key = "up"
y -= vel
if keys[pygame.K_DOWN] and y < 600 - height - vel:
last_key = "down"
y += vel
wall = pygame.Rect(wall_x, wall_y, wall_width, wall_height)
player = pygame.Rect(x, y, width, height)
#collision code
if player.colliderect(wall) and last_key == "left":
x += vel
x = wall.right
elif player.colliderect(wall) and last_key == "right":
x -= vel
x = wall.left - width
elif player.colliderect(wall) and last_key == "up":
y += vel
y = wall.bottom
elif player.colliderect(wall) and last_key == "down":
y -= vel
y = wall.top - height
#draws the player
screen.fill((0,0,0))
pygame.draw.rect(screen, (244, 247, 30), (wall_x, wall_y, wall_width, wall_height))
pygame.draw.rect(screen, (255, 255, 255), (x, y, width, height))
pygame.display.update()
pygame.quit()