Function pyGame mask. from_ Threshold () principle and application method of determining mask collision point with threshold

Posted by LoneTraveler on Sat, 18 Dec 2021 09:55:32 +0100

Usually there are many characters in a game, and the "collision" between these characters is inevitable, such as whether the shell hit the plane or not. Collision detection is a very important problem in most games. In addition to the "collision" between characters, it is often used in game design to detect the collision between characters and specified colors, as well as the collision between characters and specific parts of another character. This paper introduces how to use pyGame mask. from_ The threshold () method creates a pyGame that implements the above functions mask. This method uses threshold instead of transparency to determine the pixels participating in the collision.
This method is equivalent to two methods. If only the first three parameters are used, this method returns a mask. Using this mask can detect the collision between another character and the specified color in the character with this mask. If parameter 4 is used, parameter 2 will be ignored, which is equivalent to another method, which will be discussed below.
If parameters 4 and 5 are not used, the official document gives the call format as follows, and the method returns a pyGame Mask, which generates collision response only for the color specified in parameter 2. Parameter 1 is the surface where the mask needs to be created, parameter 2 is the set color, and parameter 3 is the color threshold. Personal understanding is that there is a certain deviation between the actual color of the graphics in the surface and the color specified in parameter 2. This usage is successfully made into two examples.
pygame.mask.from_threshold(Surface,color,threshold=(0,0,0,255))-> Mask
In game programming, a character may be composed of different colors, but he wants to collide with only one color. The first example introduces the specific steps to realize this function. In this example, draw a large red ring with a blue solid circle inside, and then draw a small yellow circle. Move the small yellow circle close to the big circle. When you want to encounter the red circle of the big circle, you can't detect the collision. Only when you encounter the blue circle in the big red circle, you can detect the collision. When the collision occurs, change the background color of the window. The complete procedure is as follows.

import pygame
class Circle(pygame.sprite.Sprite):
    def __init__(self,pos,color,radius,width):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((100,100))            #Create a 100X100 Surface image instance
        self.image.set_colorkey((0,0,0))                  #Set the color of the color (0,0,0) in the image to transparent
        self.image.fill((0, 0, 0))                        #The background color is black. Due to the previous bar, the background color becomes transparent        
        self.radius=radius
        self.width=width        #Next sentence, draw a solid circle or circle on the image, self Width = 0, draw a solid circle, > 0, draw a ring
        pygame.draw.circle(self.image,pygame.Color(color),(50,50),self.radius,self.width)
        if self.width>0:        #If self If width > 0, draw a solid blue circle after drawing a solid circle
            pygame.draw.circle(self.image,pygame.Color('blue'),(50,50),25,0)
            #Set the mask with the following formula. No matter how many colors the sprite has, the detection result is true only after blue collides with other sprites
            #For the resulting mask, only the position of blue is set to 1, and other colors and background colors are set to 0, that is, blue participates in collision detection            
            self.mask=pygame.mask.from_threshold(self.image,pygame.Color('blue'),(1,1,1,255))#Torus character mask            
        else:
            self.mask=pygame.mask.from_surface(self.image)  #Yellow small round mask
        self.rect = self.image.get_rect(center=pos)         #Move the image to the specified location
    def draw(self,aSurface):
        aSurface.blit(self.image,self.rect) #The class derived from Sprite is not put into the Group, and the class instance shows that the user-defined draw needs to be called        
pygame.init()
screen = pygame.display.set_mode((200,100))
pygame.display.set_caption("Collision with blue")
clock = pygame.time.Clock()
circleRed=Circle((50,50),'red',45,20)                       #Create a large red Circle and an inner blue filled Circle, Circle instance
circleyellow=Circle((150,50),'yellow',15,0)                 #Create a small yellow Circle, Circle instance
run=True
while run:
    screen.fill((255, 255, 255))
    for event in pygame.event.get():
        if event.type == pygame.QUIT: 
            run=False        
        if event.type==pygame.KEYDOWN:            #Key press event
            if event.key==pygame.K_RIGHT:         #Press the right arrow key on the keyboard to make the ball right
                circleyellow.rect.x+=5                            
            elif event.key==pygame.K_LEFT:        #Press the left arrow key on the keyboard to turn the ball to the left
                circleyellow.rect.x-=5            
    if pygame.sprite.collide_mask(circleRed,circleyellow):    #Detect whether the two circles collide   
        screen.fill((200, 200, 200))               #When a collision occurs, the form background turns gray 
    else:
        screen.fill((255, 255, 255))               #No collision occurs, and the form background is white
    circleRed.draw(screen)
    circleyellow.draw(screen)
    clock.tick(10)
    pygame.display.update()
pygame.quit()


In game programming, if different characters have different colors, you may want to collide when you encounter only one color character. For example, there are green apples and red apples. I just want to pick red apples. Another example is the aircraft war, the invasion of blue enemy aircraft, our side shoots enemy aircraft with anti-aircraft guns and sends red aircraft to fight, and the anti-aircraft guns should only hit enemy blue aircraft. The second example introduces the method of colliding only with characters with specified colors. In this example, there are 20 circular characters with different colors, whose positions are fixed, and one circle that can move with the mouse. The moving circle will collide only when it touches the circle with a fixed blue position. After the collision, the background color of the form will be changed. The complete procedure is as follows.

import pygame
import random
class Circle(pygame.sprite.Sprite):
    def __init__(self,pos,color,*grps): #*args indicates that there are multiple (indefinite) unknown parameters, which is essentially a tuple
        super().__init__(*grps)
        self.image = pygame.Surface((32, 32))           #Create a 32X32 Surface instance image
        self.image.set_colorkey((0,0,0))                #Set black to transparent in image
        self.image.fill((0, 0, 0))                      #The background color is black. Due to the previous bar, the background color becomes transparent
        pygame.draw.circle(self.image, pygame.Color(color), (15, 15), 15) #Draw a circle on the image
        self.rect = self.image.get_rect(center=pos)     #Move the image to the specified location        
screen = pygame.display.set_mode((800, 600))
colors = ['red', 'lightgreen', 'yellow', 'blue']
objects = pygame.sprite.Group()
for _ in range(20):
    pos = random.randint(100, 700), random.randint(100, 600)
    Circle(pos, random.choice(colors), objects) #Create an instance of the Circle class and add it to the list objects
for sprite in objects: #Set the mask for the Circle instance in the list. The mask cannot be set in the Circle class constructor. No error is reported and the result is incorrect   
    sprite.mask=pygame.mask.from_threshold(sprite.image, pygame.Color('blue'),(1,1,1,255))
player = Circle(pygame.mouse.get_pos(), 'green')  #The moving circle can collide with 20 circles with fixed positions
run=True
while run:
    for e in pygame.event.get():
        if e.type == pygame.QUIT: 
            run=False
    player.rect.center = pygame.mouse.get_pos()        
    if pygame.sprite.spritecollideany(player, objects, pygame.sprite.collide_mask):        
        screen.fill((200, 200, 200))
    else:
        screen.fill((130, 130, 130))
    objects.update()
    objects.draw(screen)
    screen.blit(player.image,player.rect)        
    pygame.display.flip()
pygame.quit()


About parameter 3: threshold, it means threshold in Chinese. For parameter 3, the official English document description is: the threshold range used to check the difference between two colors The threshold range used to detect the difference between two colors. Personal understanding may be that there is a certain deviation between the color value in the surface graph and the color value of parameter 2, that is, although parameter 2 is a solid color, some deviation of the color in parameter 1 graph can also cause collision, as long as it is less than the set threshold. In two successful examples, some experiments are done on the numerical range of parameter 3threshold. It is found that parameter 3 can be either rgba or rgb. It is also found that only considering rgb, the value range of R, g or b is a positive integer from 1 to 255, which cannot be 0, otherwise collision cannot occur, because the difference between two same colors is (0,0,0). In order to make (0,0,0) within the threshold, the threshold of each rgb color must be greater than or equal to 1. It can be inferred that the thresholds should be calculated separately according to the values of red, green and blue of the color and compared respectively. For example, if the parameter threshold=(1,2,3), the thresholds of red, green and blue are 1, 2 and 3 respectively. To compare whether the two colors are within the threshold, it is necessary to calculate the absolute value of the difference between red, green and blue, namely | r1-r2 |, | g1-g2 |, and | b1-b2 |. If the difference between red, green and blue is less than the threshold corresponding to threshold, it means that the difference between the two colors is within the threshold. In order to further verify the correctness of the above contents, continue to do some experiments on the above two programs. First list the color values used by the two programs: Red: 255,0,0, green: 0255,0, blue: 0,0255, white: 255255255, black: 0,0,0, light green: 144238153, yellow: 255255,0. In the first program, five colors of red, blue, black, white and yellow are used. Black is the bottom color of the picture of parameter 1(Surface class instance), white is the background color of the form, yellow is the color of the yellow circle, and white and yellow have nothing to do with the parameter threshold. Modify the value of parameter 3 in line 17 of example 1 to (255255255255). After the program runs, it can only collide with the blue circle. It is strange at first, but it is found that the program is correct after calculation. Calculate the absolute value of the difference between blue and black red blue: blue black = (0,0255), blue red = (255,0255), blue green = (0255255), blue blue blue = (0,0,0). Obviously, only blue is within the threshold and can produce collision. The second program uses all 7 colors listed above. After running, the green circle moves and collides only when it touches the blue circle at the fixed position, and cannot collide when it touches other fixed circles. Modify the parameter 3 = (255255255255) in line 19 of example 2. Run again, the green circle moves and collides with the blue circle and light green circle, but still cannot collide with the red and yellow circle. Calculate blue light green = (144238102), so the collision condition is met. Through the above experiments, it can be confirmed that the threshold format of parameter threshold is (Nr,Ng,Nb,a) or (Nr,Ng,Nb), and the value range of Nr, Ng and Nb is positive integer 1-255. The calculation method of the difference between two colors is to calculate the absolute value of the difference between red, green and blue of the two colors, i.e. | r1-r2 |, | g1-g2 |, and | b1-b2 |. Only when | r1-r2 | < Nr, | g1-g2 | < Ng and | b1-b2 | < NB are established at the same time, can the difference between the two colors be considered to be within the threshold. It can also be seen that the only basis for judging whether there is a collision is the threshold. As long as it is less than the threshold, all colors can collide. If you want to collide only with colors similar to blue, parameter 3 should be: (maximum allowable value of red, maximum allowable value of green and maximum allowable difference of blue). The larger the three values, the farther away from blue and the smaller the values, the closer to blue. The limit is (1,1,1). Most of these are personal opinions and may not be correct. I hope readers can correct them. I am very grateful.
If parameters 4 and 5 are used, the official document gives the call format as follows. Parameter 1 is the surface that needs to create a mask, parameter 2 is ignored and not used, parameter 3 is the color threshold, and parameter 4 is another surface. (the following is personal understanding) this method will subtract all pixel values (including the background color) of the surface represented by parameter 1 and all corresponding pixel values (including the background color) of the surface represented by parameter 4 one by one, and the difference is within the threshold range determined by parameter 3, Set it to 1 in the return mask, and the point will participate in collision detection.
pygame.mask.from_threshold(Surface,color,threshold=(0,0,0,255),othersurface=None,palette_colors=1)-> Mask
According to the above understanding of this method, the third program is written. Similar to the first procedure, this example draws a large red ring with a blue solid circle inside, and then draws a small yellow circle. Move the small yellow circle close to the big circle. When you want to touch the red circle of the big circle, you can't detect the collision. Only when you touch the blue circle in the big red circle can you detect the collision. The create mask statement is different from the first example. The create mask statement is as follows. Among them, parameter 1 is circleRed, which is the image with a blue solid circle Surface in the red ring, parameter 2 is ignored and not used, parameter 3 is the threshold, and parameter 4 is to create another Surface, circleRed1, which has the same width and height as circleRed, but only one blue solid circle, which has the same position and color as the solid blue circle of circleRed. Obviously, this statement cannot be placed at line 12, because circleRed1 does not exist at this time and should be placed at line 28.
circleRed.mask=pygame.mask.from_threshold(circleRed.image,pygame.Color('blue'),(1,1,1,255),circleRed1.image)
This method will circle red All pixel values of image (including background color) and circlered1 All the corresponding pixel values (including the background color) of the image are subtracted, and the pixels with the difference within the threshold (1,1,1255) return circlered Set to 1 in the mask. So what corresponding pixel colors are subtracted? First, circlered Image background and circlered1 Image background color, they should not participate in collision, so the two background colors should be different, circlered Image background is white (line 10), circlered1 The image background color is black (line 15). Then circle red Image red circle and circlered1 Image background color, obviously greater than the threshold. Finally, circlered Image blue and circlered1 Image blue, the same color, is obviously less than the threshold. With this setting, only the blue pixels corresponding to the positions in the two circles will be returned to circlered Set 1 in the mask to participate in collision detection.
Circle yellow. Is set The mask statement is placed at line 16 without error, but the correct mask cannot be obtained. Later, it is placed at line 29 to obtain the correct mask.
If you reduce the radius of the blue circle of circleRed1 or move the position of the blue circle, the collision position of parameter 1 will be affected. Use the statements in lines 30 and 31 to check whether the value of the specified location of the mask is 1 or 0. This is a method of program debugging.
The complete procedure is as follows. The effect drawing is the same as the first example.

import pygame
class Circle(pygame.sprite.Sprite):
    def __init__(self,pos,color,radius,width):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((100,100))            #Create a 100X100 Surface instance               
        self.radius=radius
        self.width=width        
        if self.width>0:
            self.image.set_colorkey((200,200,200))         #Set white to transparent in image
            self.image.fill((200,200,200))                 #The background color is white. Due to the previous bar, the background color becomes transparent
            pygame.draw.circle(self.image,pygame.Color('blue'),(50,50),25,0)            
            self.mask=None  #Big circle mask            
        else:
            self.image.set_colorkey((0,0,0))                #Set black to transparent in image
            self.image.fill((0, 0, 0))                      #The background color is black. Due to the previous bar, the background color becomes transparent 
            self.mask=None #pygame.mask.from_surface(self.image)  #Failed to set mask here?
        pygame.draw.circle(self.image,pygame.Color(color),(50,50),self.radius,self.width) #Draw a circle on the image
        self.rect = self.image.get_rect(center=pos)         #Move the image to the specified location
    def draw(self,aSurface):
        aSurface.blit(self.image,self.rect) #The class derived from Sprite is not put into the Group, and the class instance shows that the user-defined draw needs to be called        
pygame.init()
screen = pygame.display.set_mode((300,100))
pygame.display.set_caption("Collision between rectangles")
clock = pygame.time.Clock()
circleRed=Circle((50,50),'red',45,20)                       #Create a blue Circle inside a large red Circle, Circle instance
circleyellow=Circle((150,50),'yellow',15,0)                   #Create a small yellow Circle, Circle instance
circleRed1=Circle((250,50),'blue',20,0)
circleRed.mask=pygame.mask.from_threshold(circleRed.image,(0,0,255),(1,1,1,255),circleRed1.image)
circleyellow.mask=pygame.mask.from_surface(circleyellow.image)
#print(circleRed.mask.get_at((52,52)))
#print(circleyellow.mask.get_at((52,52)))
run=True
while run:
    screen.fill((255, 255, 255))
    for event in pygame.event.get():
        if event.type == pygame.QUIT: 
            run=False        
        if event.type==pygame.KEYDOWN:            #Key press event
            if event.key==pygame.K_RIGHT:         #Press the right arrow key on the keyboard to make the ball right
                circleyellow.rect.x+=5                            
            elif event.key==pygame.K_LEFT:        #Press the left arrow key on the keyboard to turn the ball to the left
                circleyellow.rect.x-=5            
    if pygame.sprite.collide_mask(circleRed,circleyellow):       
        screen.fill((200, 200, 200))               #When a collision occurs, the form background turns gray 
    else:
        screen.fill((255, 255, 255))               #No collision occurs, and the form background is white
    circleRed1.draw(screen)
    circleRed.draw(screen)
    circleyellow.draw(screen)
    clock.tick(10)
    pygame.display.update()
pygame.quit()

Topics: Python pygame