1

I have a player sprite

class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.images = []
        for i in range(1,5):
            img = pygame.image.load(os.path.join('assets/player', 'run-' + str(i) + '.png')).convert_alpha()
            self.images.append(img)
        self.image = self.images[0]
        self.rect = self.image.get_rect()

    def control(self,x,y):
        self.movex += x
        self.movey += y

    def update(self):
        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey    
 

And some boundaries I want to clamp them to

 self.boundaries    =    [   [[(475,350),(575,350),(500,550),(300,550)],],
                                    [[(0,0),(100,100),(200,200)]],
                                    [[(0,0),(100,100),(200,200)]],
                                ]

So far the player sprite can be clampled on to the display surface rect correctly but how would I clamp onto a dynamic polygon with the player sprite rect.

self.stages.level=0
self.stages_boundary_index=0

        ##self.stages.player.rect.clamp_ip(pygame.display.get_surface().get_rect())

        #Detection between sprite and a boundary
        """             
        x = self.stages.boundaries[self.stages.level][self.stages.boundary_index]
        print(x)
        elem=Rect(x[0],x[1],x[2],x[3])
        self.stages.player.rect.clamp_ip(elem) 
        """

Edited: Tried this but it looks like the boundaries are shifted a bit.

def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):

    # normalized direction of the lines and start of the lines
    P  = pygame.math.Vector2(*l1_p1)
    line1_vec = pygame.math.Vector2(*l1_p2) - P
    R = line1_vec.normalize()
    Q  = pygame.math.Vector2(*l2_p1)
    line2_vec = pygame.math.Vector2(*l2_p2) - Q
    S = line2_vec.normalize()

    # normal vectors to the lines
    RNV = pygame.math.Vector2(R[1], -R[0])
    SNV = pygame.math.Vector2(S[1], -S[0])
    RdotSVN = R.dot(SNV)
    if RdotSVN == 0:
        return False

    # distance to the intersection point
    QP  = Q - P
    t = QP.dot(SNV) / RdotSVN
    u = QP.dot(RNV) / RdotSVN

    return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()

def colideRectLine(rect, p1, p2):
    return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
            collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
            collideLineLine(p1, p2, rect.bottomright, rect.topright) or
            collideLineLine(p1, p2, rect.topright, rect.topleft))

def collideRectPolygon(rect, polygon):
    for i in range(len(polygon)-1):
        if colideRectLine(rect, polygon[i], polygon[i+1]):
            return True
    return False

class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.images = []
        for i in range(1,5):
            img = pygame.image.load(os.path.join('assets/player', 'run-' + str(i) + '.png')).convert_alpha()
            self.images.append(img)
        self.image = self.images[0]
        self.rect = self.image.get_rect()

    def control(self,x,y):
        self.movex += x
        self.movey += y

    def update(self,boundary):

        if self.movex > 0:
            self.image = self.images[self.frame//ani] 
            if self.movey !=0:
                if collideRectPolygon(self.rect, boundary):
                    self.rect.x+=self.movex
                    self.rect.y+=self.movey
                else:
                    self.rect.x-=self.movex
                    self.rect.y-=self.movey
                    self.movex=0
                    self.movey=0
            else:
                if collideRectPolygon(self.rect, boundary):
                    self.rect.x+=self.movex
                else:
                    self.rect.x-=self.movex
                    self.movex=0
        elif self.movex < 0:
            self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)
            if self.movey !=0:
                if collideRectPolygon(self.rect, boundary):
                    self.rect.x+=self.movex
                    self.rect.y+=self.movey   
                else:
                    self.rect.x-=self.movex
                    self.rect.y-=self.movey
                    self.movex=0
                    self.movey=0
            else:
                if collideRectPolygon(self.rect, boundary):
                    self.rect.x+=self.movex
                
                else:
                    self.rect.x-=self.movex
                    self.movex=0
        else:
            self.image = self.images[self.frame//ani] 
            if self.movey !=0:
                if collideRectPolygon(self.rect, boundary):
                    self.rect.y+=self.movey
                else:
                    self.rect.y-=self.movey
                    self.movey=0
            else:
                pass
Arundeep Chohan
  • 9,779
  • 5
  • 15
  • 32

1 Answers1

2

One approach is explained in the answer to the question Detecting collisions between polygons and rectangles in Pygame. Another easy way to achieve what you want is to use pygame.mask. The basic idea is to count the mask bits of the object and check if the number of bits is equal to the number of overlapping bits between the object mask and the area mask. This approach also works for concave boundaries and boundaries with "holes".

Create a pygame.mask from the object image and count the bits of the mask:

object_mask = pygame.mask.from_surface(object_image)
object_mask_count = object_mask.count()
object_rect = object_image.get_rect(center = (object_x, object_y))

Create a white image of the area and convert the image to a mask

white_area_image = ...
area_mask = pygame.mask.from_surface(white_area_image)
area_rect = object_image.get_rect(topleft = (area_left, area_top))

After moving the object, count the number of overlapping bits between the masks with overlap_area(). Discard the movement if the number of overlapping bits is less than the number of mask bits:

new_object_rect = object_rect.copy()
new_object_rect.x += move_x
new_object_rect.y += move_y

offset_x = new_object_rect.x - area_rect.x
offset_y = new_object_rect.y - area_rect.y
overlap_count = area_mask.overlap_area(object_mask, (offset_x, offset_y))

if overlap_count == object_mask_count:
    object_rect = new_object_rect

Also see PyGameExamplesAndAnswers - Mask


Minimal example

import pygame, math

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

cage_mask_image = pygame.Surface((200, 300), pygame.SRCALPHA)
pygame.draw.circle(cage_mask_image, "white", (100, 100), 100)
pygame.draw.rect(cage_mask_image, "white", (0, 100, 200, 200))
cage_mask = pygame.mask.from_surface(cage_mask_image)
cage_rect = cage_mask_image.get_rect(center = (200, 200))

bird_image = pygame.image.load('bird.png').convert_alpha()
bird_mask = pygame.mask.from_surface(bird_image)
bird_mask_count = bird_mask.count()
bird_rect = bird_image.get_rect(center = (200, 200))
speed = 5

cage_color = (64, 64, 64)
cage_image = pygame.Surface((200, 300), pygame.SRCALPHA)
pygame.draw.arc(cage_image, cage_color, (0, 0, 200, 200), 0, math.pi, 5)
pygame.draw.rect(cage_image, cage_color, (0, 100, 200, 200), 5)
pygame.draw.line(cage_image, cage_color, (100, 0), (100, 300), 5)
for x in [33, 66, 134, 167]:
    pygame.draw.line(cage_image, cage_color, (x, 100), (x, 300), 5)
for x in [33, 66]:
    pygame.draw.arc(cage_image, cage_color, (x, 0, 200-x*2, 200), 0, math.pi, 5)
   

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()
    new_bird_rect = bird_rect.copy()
    new_bird_rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
    new_bird_rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
    offset_x = new_bird_rect.x - cage_rect.x
    offset_y = new_bird_rect.y - cage_rect.y
    overlap_count = cage_mask.overlap_area(bird_mask, (offset_x, offset_y))
    if overlap_count == bird_mask_count:
        bird_rect = new_bird_rect

    window.fill("lightblue")
    window.blit(bird_image, bird_rect)
    window.blit(cage_image, cage_rect)
    pygame.display.flip()

pygame.quit()
exit()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174