pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
925 stars 152 forks source link

FRect.clipline not working properly #3047

Open bigwhoopgames opened 2 months ago

bigwhoopgames commented 2 months ago

pygame-ce 2.5.0 (SDL 2.30.3, Python 3.12.0)

Current behavior:

When using FRects there are instances when clipline does not return the correct line clip

Expected behavior:

Clipline to clip using floats correctly

The below code is an example where a player and an enemy should not be able to see each other through the obstacle rects but they can. The line should be red to indicate that the clipline returned a clip and therefore the player and enemy are out of line of sight.

import pygame

player_pos = [21.015, 41.359]
enemy_pos = [17.321, 90.1746]

obstacles = [pygame.FRect(18, 82, 4, 4),
             pygame.FRect(14, 82, 4, 4),]

# Main function
def main():
    # Set up the display
    display_width = 480
    display_height = 270

    screen = pygame.display.set_mode((display_width, display_height), flags = pygame.SCALED)
    pygame.display.set_caption("clipline test")

    player_rect = pygame.FRect(0, 0, 6, 6)
    player_rect.center = player_pos

    enemy_rect = pygame.FRect(0, 0, 6, 6)
    enemy_rect.center = enemy_pos

    running = True

    # Main loop
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESC:
                    running = False

        screen.fill((0, 0, 0))

        pygame.draw.rect(screen, 'blue', player_rect)
        pygame.draw.rect(screen, 'purple', enemy_rect)

        for obstacle in obstacles:
            pygame.draw.rect(screen, 'white', obstacle)

        color = 'green'

        for obstacle in obstacles:
            if obstacle.clipline(enemy_rect.center, player_rect.center):
                color = 'red'

        pygame.draw.line(screen, color, enemy_rect.center, player_rect.center)

        pygame.display.flip()

    pygame.quit()

if __name__ == "__main__":
    main()
itzpr3d4t0r commented 2 months ago

I've checked on this and I believe this one is on SDL.

bigwhoopgames commented 2 months ago

I ended up just doing a workaround with this code:

def collide_line_line(p1, p2, p3, p4):
    return ((p4[1] - p1[1]) * (p2[0] - p1[0]) > (p2[1] - p1[1]) * (p4[0] - p1[0])) != \
           ((p4[1] - p2[1]) * (p2[0] - p1[0]) > (p2[1] - p2[1]) * (p4[0] - p2[0])) and \
           ((p3[1] - p1[1]) * (p2[0] - p1[0]) > (p2[1] - p1[1]) * (p3[0] - p1[0])) != \
           ((p4[1] - p1[1]) * (p2[0] - p1[0]) > (p2[1] - p1[1]) * (p4[0] - p1[0]))

def collide_line_rect(p1, p2, rect):

    # check line collision
    if (collide_line_line(p1, p2, rect.topleft, rect.topright) or
        collide_line_line(p1, p2, rect.topright, rect.bottomright) or
        collide_line_line(p1, p2, rect.bottomright, rect.bottomleft) or
        collide_line_line(p1, p2, rect.bottomleft, rect.topleft)):
        return True

    # check if both points are inside the rectangle
    if (rect.x <= p1[0] <= rect.x + rect.w and
        rect.x <= p2[0] <= rect.x + rect.w and
        rect.y <= p1[1] <= rect.y + rect.h and
        rect.y <= p2[1] <= rect.y + rect.h):
        return True

    return False
itzpr3d4t0r commented 2 months ago

Oh ok so what you really wanted was checking for collision, kinda like Line.colliderect(). Luckily, that's coming in the geometry module with the Line class.

bigwhoopgames commented 2 months ago

Ultimately yes, but clipline should still be returning a line segment where it overlaps.