2

This is the first time I try to use mask collisions and can't find reference for this specific situation (polygon and sprite mask collision).

I want the capability for sprites to detect other sprites using a 'field-of-view' method. Here, is an example of my Pygame application:

enter image description here

A bunch of worms with their FOV shown for debugging. In this scenario, I want them to see the little green dots. Firstly, I can generate that fov as a polygon. I draw it on the pygame.display Surface (referred to as program.screen). Then, I check each 'target' in the sprite group and check for rect collision between the polygon and the target - works fine. So all the math and calculations are correct, but I cannot get the next bit with mask to mask collision to work. With the code I attach, the print("Detection") statement is never called.

#Check if entity can detect its target given its fov
#
#@param entity: pygame.sprite.Sprite object performing the look() function
#@param fov: The angle and depth of view of 'entity', in the form of [angle (degrees), depth (pixels)]
#@param target: pygame.sprite.Sprite object the 'entity' is looking out for
#@param program: the main instance that runs and handles the entire application

def look(entity, fov, target, program):
    r = fov[1] * np.cos(np.pi / 180 * fov[0])
    coords_1 = (r * np.sin(entity.rot * np.pi / 180) + entity.x, r * np.cos(entity.rot * np.pi / 180) + entity.y)
    coords_2 = (r * np.sin((entity.rot + 2*fov[0]) * np.pi / 180) + entity.x, r * np.cos((entity.rot + 2*fov[0]) * np.pi / 180) + entity.y)
    poly_coords = ((entity.x, entity.y), coords_1, coords_2)  # The coordinates of the fov

    view = pygame.draw.polygon(program.screen, WHITE, poly_coords)  # Draw the fov white for indication
    pygame.display.update(view)
    # Iterate over all sprites
    for i in program.all_sprites:
        if isinstance(i, target):
            # Rough check for if this item is colliding with the fov polygon
            if view.colliderect(i.rect):
                # Exact check if this item is roughly colliding with the fov polygon
                mask = pygame.mask.from_surface(i.image)
                if pygame.mask.from_surface(program.screen).overlap(mask, (0, 0)):
                    # For now, colour the polygon red as indication
                    signal = pygame.draw.polygon(program.screen, RED, [(entity.x, entity.y), coords_1, coords_2])
                    print("Detection")
                    pygame.display.update(signal)

When I use

if pygame.sprite.collide_mask(mask, pygame.mask.from_surface(program.screen)):

for the mask check, I get an

AttributeError: 'pygame.mask.Mask' object has no attribute 'rect'

I also tried to draw the polygon on a different surface with the same result as the first code (no detection):

def look(entity, fov, target, program):
    r = fov[1] * np.cos(np.pi / 180 * fov[0])
    coords_1 = (r * np.sin(entity.rot * np.pi / 180) + entity.x, r * np.cos(entity.rot * np.pi / 180) + entity.y)
    coords_2 = (r * np.sin((entity.rot + 2*fov[0]) * np.pi / 180) + entity.x, r * np.cos((entity.rot + 2*fov[0]) * np.pi / 180) + entity.y)
    poly_coords = ((entity.x, entity.y), coords_1, coords_2)  # The coordinates of the fov

    view_layer = pygame.Surface((500, 500))  # some random size big enough to hold the fov
    #view_layer.fill(BLUE)
    view = pygame.draw.polygon(view_layer, WHITE, poly_coords)  # Draw the fov white for indication
    pygame.display.update(view)
    program.screen.blit(view_layer, (min(poly_coords[0]), max(poly_coords[1])))
    pygame.display.update(view)
    # Iterate over all sprites
    for i in program.all_sprites:
        if isinstance(i, target):
            # Rough check for if this item is colliding with the fov polygon
            if view.colliderect(i.rect):
                # Exact check if this item is roughly colliding with the fov polygon
                mask = pygame.mask.from_surface(i.image)
                if pygame.mask.from_surface(view_layer).overlap(mask, (0, 0)):
                    # For now, colour the polygon red as indication
                    signal = pygame.draw.polygon(program.screen, RED, [(entity.x, entity.y), coords_1, coords_2])
                    print("Detection")
                    pygame.display.update(signal)

What is wrong with the ways I'm trying to do it, or is there a better way for doing it?

Thanks, Kres

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Chris Anast
  • 51
  • 1
  • 5

2 Answers2

1

pygame.sprite.collide_mask() is for the use with pygame.sprite.Sprite objects. The arguments of pygame.sprite.collide_mask() are sprites but not masks. The sprite objects need to have the attributes .rect and .mask. The rect attributes are required to calcaulte the position offset between the objects on the screen and the mask are pygame.mask.Mask objects with contain the bit masks.

e.g.

if pygame.sprite.collide_mask(sprite1, sprite2):
    print("hit")

See also How can I made a collision mask?


If you don't use pygame.sprite.Sprite you have to use pygame.mask.Mask.overlap.

e.g. if you have 2 pygame.mask.Mask objects (mask1, mask2) at the positions (x1, y1) and (x2, y2):

offset = (x2 - x1), (y2 - y1)
if mask1.overlap(mask2, offset):
    print("hit") 

See also Collision between masks in PyGame and PyGame collision with masks is not working.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
0

Thank you Rabbid76! There were other issues as well with my code which is why I thought of posting this as the answer.

I made a separate surface to draw the polygon on. Some more maths involved to define the rect that the polygon is inscribed to, and to translate the original (display) polygon coordinates onto the new surface. The surface should be of the size of the bounding rect calculated.

As for the mask calculation itself, that was basically Rabbid76's answer.

def look(entity, fov, target, program):
    r = fov[1] * np.cos(np.pi / 180 * fov[0])
    coords_1 = (r * np.sin(entity.rot * np.pi / 180) + entity.x, r * np.cos(entity.rot * np.pi / 180) + entity.y)
    coords_2 = (r * np.sin((entity.rot + 2*fov[0]) * np.pi / 180) + entity.x, r * np.cos((entity.rot + 2*fov[0]) * np.pi / 180) + entity.y)
    poly_coords = ((entity.x, entity.y), coords_1, coords_2)  # The coordinates of the fov
    poly_rect = hf.poly_get_box(poly_coords)

    view_layer = pygame.Surface((poly_rect.width, poly_rect.height), pygame.SRCALPHA)
    #view_layer.fill(DODGER_BLUE)  # for debugging only
    view_layer.convert_alpha()

    new_poly_coords = hf.poly_coords_in_surface(poly_coords, (poly_rect.x, poly_rect.y))
    pygame.draw.polygon(view_layer, WHITE, new_poly_coords, 0)  # Draw the fov white for indication
    program.screen.blit(view_layer, (poly_rect.x, poly_rect.y))  # blits/updates/flips for debugging only
    pygame.display.update(poly_rect)
    pygame.display.flip()
    # Iterate over all sprites
    for i in program.all_sprites:
        if isinstance(i, target):
            # Rough check for if this item is colliding with the fov polygon
            if poly_rect.colliderect(i.rect):
                # Exact check if this item is roughly colliding with the fov polygon
                view_mask = pygame.mask.from_surface(view_layer)
                mask = pygame.mask.from_surface(i.image)
                pygame.display.flip()
                if view_mask.overlap(mask, (int(i.x) - poly_rect.x, int(i.y) - poly_rect.y)):
                    # For now, colour the polygon red as indication
                    signal = pygame.draw.polygon(program.screen, RED, [(entity.x, entity.y), coords_1, coords_2])
                    print("Detection")
                    pygame.display.update(signal)

Helper functions:

# Defines a width and height for a 3-point polygon
def poly_get_box(coords):
    xs = []; ys = []
    for i in coords:
        xs.append(i[0])
        ys.append(i[1])

    width = np.ceil(max(xs) - min(xs))
    height = np.ceil(max(ys) - min(ys))
    box = pygame.Rect(np.ceil(min(xs)), np.ceil(min(ys)), width, height)

    return box

# Translates polygon coordinates from display coordinates to view-layer coordinates
def poly_coords_in_surface(polyCoords, surfCoords):
    new_coords = []
    for i in polyCoords:
        x = i[0] - surfCoords[0]
        y = i[1] - surfCoords[1]
        new_coords.append((x, y))

    return new_coords
Chris Anast
  • 51
  • 1
  • 5