How do I check collision between a line and a rect in pygame?

Question:

I am currently creating a game in python with pygame and my AI is currently “seeing” my character through the walls and shoot at it, but the AI is not supposed to shoot. So my question is : how to prevent that ? I’ve thought about a line collision where the line goes from my AI to my character, and if this line collide a wall then this AI don’t shoot.
Any help would be appreciated, thanks a lot !

Asked By: FloW

||

Answers:

This is a great question!

Your rectangle can be thought of as 4 lines:

(x, y)        → (x+width, y)        # top
(x+width, y)  → (x+width, y+height) # right
(x, y+height) → (x+width, y+height) # bottom
(x, y)        → (x, y+height)       # left 

Taking your intersecting line, it’s possible to use the two-lines intersecting formula to determine if any of these lines intersect (but be careful of parallel lines!)

However the formula (specified in linked Wikipedia article) determines if the lines intersect anywhere on the 2D plane, so it needs to be further refined. Obviously the code can quickly throw away any intersections that occur outside the window dimensions.

Once the “infinite-plane” collision-point has been determined (which is a reasonably quick determination), then a more fine-grained intersection can be determined. Using Bresenham’s algorithm, enumerate all the points in the intersecting line, and compare them with a 1-pixel rectangle based on each side of your square. This will tell you which side of the rectangle intersected.

If you only need to know if the rectangle was hit, just check the whole rectangle with pygame.Rect.collidepoint() for each point in the line.

enter image description here

Of course once you have all those points generated, it’s easily to not bother with the 2D line collision, but for long lines the code must make a lot of checks. So testing the 2D intersection first really speeds it up.

Answered By: Kingsley

Basically, is doesn’t exist a method nor any pygame functionality to detect collisions with lines, that’s why I had to come up with the solution I’m about to show.

Using the following link, at section formulas / Given two points on each line segment, you can find a formula to know if two lines intersect each other, and if they do, where exactly.

The basic idea is to check if for every ray in the lightsource there is an intersection with any of the four sides of the rectangle, if so, the lightray should end at that same side of the rectangle.

import pygame, math

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Rays')
pygame.mouse.set_visible(False)

DENSITY = 500
RADIUS = 1000

run = True
while run:
    screen.fill('black')

    rect = pygame.Rect(50, 200, 100, 50)
    pygame.draw.rect(screen, 'red', rect)

    for i in range(DENSITY):
        mouse_pos = pygame.mouse.get_pos()
        pos_fin = (RADIUS * math.cos(2*math.pi / DENSITY * i) + mouse_pos[0], RADIUS * math.sin(2*math.pi / DENSITY * i) + mouse_pos[1])
        if rect.collidepoint(mouse_pos) == False:
            for extrem_1, extrem_2 in [(rect.bottomright, rect.topright), (rect.topright, rect.topleft), (rect.topleft, rect.bottomleft), (rect.bottomleft, rect.bottomright)]:
                deno = (mouse_pos[0] - pos_fin[0]) * (extrem_1[1] - extrem_2[1]) - (mouse_pos[1] - pos_fin[1]) * (extrem_1[0] - extrem_2[0])
                if deno != 0:
                    param_1 = ((extrem_2[0] - mouse_pos[0]) * (mouse_pos[1] - pos_fin[1]) - (extrem_2[1] - mouse_pos[1]) * (mouse_pos[0] - pos_fin[0]))/deno
                    param_2 = ((extrem_2[0] - mouse_pos[0]) * (extrem_2[1] - extrem_1[1]) - (extrem_2[1] - mouse_pos[1]) * (extrem_2[0] - extrem_1[0]))/deno
                    if 0 <= param_1 <= 1 and 0 <= param_2 <= 1:
                        p_x = mouse_pos[0] + param_2 * (pos_fin[0] - mouse_pos[0])
                        p_y = mouse_pos[1] + param_2 * (pos_fin[1] - mouse_pos[1])
                        pos_fin = (p_x, p_y)
            pygame.draw.aaline(screen, 'white', mouse_pos, pos_fin)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    
    pygame.display.update()
pygame.quit()

It is maybe not the best, and most optimised piece of code but at the end you should get something that works.

Answered By: HornyPigeon54

The easiest way to detect the collision between a rectangle and a line is to use pygame.Rect.clipline:

Returns the coordinates of a line that is cropped to be completely inside the rectangle. If the line does not overlap the rectangle, then an empty tuple is returned.

e.g.:

rect = pygme.Rect(x, y, width, height)
if rect.clipline((x1, y1), (x2, y2)):
    print("hit")

Minimal example

import pygame

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

rect = pygame.Rect(180, 180, 40, 40)
speed = 5
lines = [((20, 300), (150, 20)), ((250, 20), (380, 250)), ((50, 350), (350, 300))]

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

    keys = pygame.key.get_pressed()
    rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
    rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
    rect.centerx %= window.get_width()
    rect.centery %= window.get_height()

    color = "red" if any(rect.clipline(*line) for line in lines) else "green"

    window.fill(0)
    pygame.draw.rect(window, color, rect)
    for line in lines:
        pygame.draw.line(window, "white", *line)
    pygame.display.flip()

pygame.quit()
exit()
Answered By: Rabbid76
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.