I was trying to make a working collision system for my simple game, when i noticed a weird bug. There is a rectangle with random postion, travelling up with a constant velocity. Then there is a ball also with random position, but it can be moved using arrows. When the ball hits the rectangle it is supposed to go up with the rectangle as if the rectangle had "caught the ball" and when the ball leaves the rectangle it is supossed to fall again.
This is my code:
import time
import random
import pygame
from pygame.locals import *
pygame.init()
# Window size.
screen_width = 800
screen_height = 400
# Rectangle size and position.
position_x = random.randint(0, screen_width-150)
position_y = random.randint(0, screen_height-50)
r_width = random.randint(150, (screen_width - position_x))
r_height = 10
rect_gap = 50
velocity_r = 1
# Ball properties.
radius = 10
xcoor = random.randint((radius + 2), screen_width-r_width)
ycoor = 20
gravity = 1
velocity_b = 1
# colors
white = (255, 255, 255,)
black = (0, 0, 0)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
light_blue = (78, 231, 245)
dark_green = (37, 125, 0)
# Window.
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Keep Falling")
screen.fill(light_blue)
# Class for generating the Ball.
class Ball:
def __init__(self, screen, color, xcoor, ycoor, radius, ):
self.screen = screen
self.color = color
self.xcoor = xcoor
self.ycoor = ycoor
self.radius = radius
def draw(self, color, xcoor, ycoor, radius):
pygame.draw.circle(screen, color, (xcoor, ycoor), radius)
# Class for generating the
class Rectangle:
def __init__(self, screen, color, position_x, position_y, r_width, r_height):
self.screen = screen
self.color = color
self.position_x = position_x
self.position_y = position_y
self.r_width = r_width
self.r_height = r_height
def draw(self, color, position_x, position_y, r_width, r_height):
pygame.draw.rect(screen, dark_green, (position_x,
position_y, r_width, r_height))
# game loop
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill(light_blue)
time.sleep(0.005)
# Upward movement of the rectangle.
position_y -= velocity_r
# Teleportation of the triangle to the bottom.
if position_y < 0 - r_height:
position_y = screen_height+radius
key = pygame.key.get_pressed()
# Ball controls and collisions with the rectangle from the sides.
if key[pygame.K_LEFT]:
xcoor -= velocity_b
if xcoor < 0:
xcoor = screen_width
if ycoor > position_y and ycoor < (position_y + r_height) and xcoor == (position_x + r_width + radius):
xcoor += velocity_b
if key[pygame.K_RIGHT]:
xcoor += velocity_b
if xcoor > screen_width:
xcoor = 0
if ycoor > position_y and ycoor < (position_y + r_height) and xcoor == (position_x - radius):
xcoor -= velocity_b
# Teleporation of the ball to the top.
if ycoor > screen_height:
ycoor = 0
# Collision system.
if ycoor == (position_y+r_height+radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
ycoor += velocity_b
if ycoor == (position_y - radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
gravity = 0
ycoor -= velocity_r
if ycoor < 0:
ycoor = screen_height
if ycoor == (position_y - radius) and xcoor <= position_x or xcoor >= (position_x + r_width):
gravity = 1
# Falling of the ball.
ycoor += gravity
# Ball and rectangle display.
Rectangle.draw(screen, dark_green, position_x, position_y, r_width, r_height)
Ball.draw(screen, white, xcoor, ycoor, radius)
pygame.display.update()
pygame.quit()
My collision system is based on coordinates of the ball and rectangle, as shown here:
# Collision system.
if ycoor == (position_y+r_height+radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
ycoor += velocity_b
if ycoor == (position_y - radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
gravity = 0
ycoor -= velocity_r
if ycoor < 0:
ycoor = screen_height
if ycoor == (position_y - radius) and xcoor <= position_x or xcoor >= (position_x + r_width):
gravity = 1
For a unknown reason, in this case the ball only gets "caught" only every other time, otherwise it falls trough the triangle.
I came up with an alternative which is also based on coordinates
while ycoor == (position_y - radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
gravity = 0
ycoor -= velocity_r
if ycoor < 0:
ycoor = screen_height
Using this method also causes bugs.
gravity = 1
after the loop causes the ball to not collide with the rectangles at all.Is there a bug or is my code logically flawed and i should redo the whole collision detection system?
Thank you for any suggestions.
The issue is, that rectangle moves by on and the ball moves by one in every frame. As a result, the bottom of the ball doesn't always hit the top of the rectangle exactly. Sometimes the condition ycoor == (position_y - radius)
is not fulfilled.
You have to evaluate if the bottom of the ball is "in the range" of the top of the rectangle:
if ycoor == (position_y - radius) and ...
if (position_y - radius - 1) <= ycoor <= (position_y - radius + 1) and ...
For instnace:
while run:
# [...]
if (position_y - radius - 1) <= ycoor <= (position_y - radius + 1) and xcoor >= position_x and xcoor <= (position_x + r_width):
gravity = 0
ycoor -= velocity_r
if ycoor < 0:
ycoor = screen_height
if (position_y - radius - 1) <= ycoor <= (position_y - radius + 1) and xcoor <= position_x or xcoor >= (position_x + r_width):
gravity = 1
Anyway, I recommend to use a pygame.Rect
object and either .collidepoint()
or colliderect()
to find a collision between a rectangle and an object.
rect1 = pygame.Rect(x1, y1, w1, h1)
rect2 = pygame.Rect(x2, y2, w2, h2)
if rect1.colliderect(rect2):
# [...]
For instance:
run = True
while run:
# [...]
# Teleporation of the ball to the top.
if ycoor > screen_height:
ycoor = 0
rect_rect = pygame.Rect(position_x, position_y, r_width, r_height)
ball_rect = pygame.Rect(xcoor-radius, ycoor-radius, radius*2, radius*2)
if rect_rect.colliderect(ball_rect):
ycoor = position_y - radius
else:
# Falling of the ball.
ycoor += gravity
# Ball and rectangle display.
# [...]