Today we are going to learn collision detection. Most games need to do collision detection because you need to know whether the ball collided, whether the bullet hit the target, whether the protagonist stepped on the shit.
How should that be achieved?
To put it bluntly, the principle is simple, that is, to detect whether there is overlap between the two elves, such as the ball in our last lesson, in the case of Figure 1, they do not overlap, that is, there is no collision.
Figure 1When the collision occurs, width = r1 + r2, as shown in Figure 2.
Figure 2When they overlap and intersect, width < R1 + r2, as shown in Figure 3:
Figure 3So we can judge whether the two balls collide or not. We just need to check whether the distance between the centers of the two balls is less than or equal to the sum of their radii.
Let's first try to write our own collision detection function:
(Of course, I know that there are ready-made sprite modules, but I suggest that before you learn anything, we should understand its principles, understand its principles, and then you will learn other languages and other knowledge with half the effort.)
The function name is collide_check().
from pygame.locals import * from random import * import pygame import math import sys class Ball(pygame.sprite.Sprite) : #Inheritance of animation wizard base class def __init__ (self,imgae,position,speed,bg_size) : pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(imgae).convert_alpha() self.rect = self.image.get_rect() #Get the size of the ball self.rect.left , self.rect.top = position #Place the ball where it appears self.speed = speed #set speed self.width , self.height = bg_size[0] , bg_size[1] #Getting the active boundary is the boundary of the background. def move(self): self.rect = self.rect.move(self.speed) #Move at your own speed if self.rect.right < 0: #The right side of the picture has gone beyond the left side of the boundary, that is, the whole ball has gone out of bounds. self.rect.left = self.width #Let him come back from the right border if self.rect.bottom < 0: #The bottom of the picture is beyond the top of the boundary. self.rect.top = self.height #Let him come back from the bottom if self.rect.left > self.width: #The left side of the picture is beyond the right side of the boundary. self.rect.right = 0 #Let him come back from the left if self.rect.top > self.height: #If the top of the picture has gone beyond the bottom of the boundary self.rect.bottom = 0 #Let him come back from the top #Judging Collision Detection Function def collide_check(item,target): col_balls = [] #Adding collision balls for each in target: #Detection of all target balls in target #Distance between two spherical centers distance = math.sqrt( math.pow( (item.rect.center[0] - each.rect.center[0]) , 2 ) + \ math.pow( (item.rect.center[1] - each.rect.center[1]) , 2) ) if distance <= ( item.rect.width + each.rect.width ) / 2: #If the distance is less than or equal to the sum of the radii between the two, that is half of the sum of the two diameters. col_balls.append(each) #Add the collision ball to the list return col_balls def main() : pygame.init() bg_image = r"D:\Code\Python\Pygame\pygame6: animated sprites\background.png" ball_image = r"D:\Code\Python\Pygame\pygame6: animated sprites\gray_ball.png" running = True #There are many ways to exit the program for the sake of the future. bg_size = width , height = 1024 , 681 #Background size screen = pygame.display.set_mode(bg_size) # Set the background size background = pygame.image.load(bg_image).convert_alpha() #Background of painting balls = [] # Create five balls BALL_NUM = 5 for i in range (BALL_NUM) : #Generate 5 balls position = randint (0,width-100) , randint(0,height-100) #The reason for subtracting 100 is that the size of the sphere image is 100 and the position is randomly generated. speed = [ randint (-10,10) , randint(-10,10) ] ball = Ball(ball_image,position,speed,bg_size) #Objects that generate balls balls.append(ball) #Add all sphere objects to the class table for easy management clock = pygame.time.Clock() #Generating refresh frame rate controller while running : for event in pygame.event.get(): if event.type == QUIT: sys.exit() screen.blit(background, (0, 0)) #Draw the background onto screen for each in balls: #Each ball is moved and redrawn each.move() screen.blit(each.image, each.rect) for i in range (BALL_NUM) : #Cycle 5 balls to determine if the ball has collided with the other four balls. item = balls.pop(i) #Because it's judgment and the other four balls, you need to take the ball out first. if collide_check( item , balls ): #Call the collision detection function. If the result is true, there is a collision ball. item.speed[0] = - item.speed[0] #Backward motion after collision item.speed[1] = - item.speed[1] balls.insert(i , item) #Finally, don't forget to put the ball back in place. pygame.display.flip() clock.tick(30) if __name__ == "__main__": main()
Here we can see that there are still some shortcomings.
The two balls will collide constantly, and then they can't get away from each other. The reason is explained in the video. It's not difficult to understand, so it's no longer described.
The solution is also simple: a collision detection is carried out when a small ball is generated, and if it collides, a small ball can be generated again.
from pygame.locals import * from random import * import pygame import math import sys class Ball(pygame.sprite.Sprite) : #Inheritance of animation wizard base class def __init__ (self,imgae,position,speed,bg_size) : pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(imgae).convert_alpha() self.rect = self.image.get_rect() #Get the size of the ball self.rect.left , self.rect.top = position #Place the ball where it appears self.speed = speed #set speed self.width , self.height = bg_size[0] , bg_size[1] #Getting the active boundary is the boundary of the background. def move(self): self.rect = self.rect.move(self.speed) #Move at your own speed if self.rect.right < 0: #The right side of the picture has gone beyond the left side of the boundary, that is, the whole ball has gone out of bounds. self.rect.left = self.width #Let him come back from the right border. if self.rect.bottom < 0: #The bottom of the picture is beyond the top of the boundary. self.rect.top = self.height #Let him come back from the bottom if self.rect.left > self.width: #The left side of the picture is beyond the right side of the boundary. self.rect.right = 0 #Let him come back from the left if self.rect.top > self.height: #If the top of the picture has gone beyond the bottom of the boundary self.rect.bottom = 0 #Get him back from the top #Judging Collision Detection Function def collide_check(item,target): col_balls = [] #Adding collision balls for each in target: #Detection of all target balls in target #Distance between two spherical centers distance = math.sqrt( math.pow( (item.rect.center[0] - each.rect.center[0]) , 2 ) + \ math.pow( (item.rect.center[1] - each.rect.center[1]) , 2) ) if distance <= ( item.rect.width + each.rect.width ) / 2: #If the distance is less than or equal to the sum of the radii between the two, that is half of the sum of the two diameters. col_balls.append(each) #Add the collision ball to the list return col_balls def main() : pygame.init() bg_image = r"D:\Code\Python\Pygame\pygame6: animated sprites\background.png" ball_image = r"D:\Code\Python\Pygame\pygame6: animated sprites\gray_ball.png" running = True #There are many ways to exit the program for the sake of the future. bg_size = width , height = 1024 , 681 #Background size screen = pygame.display.set_mode(bg_size) # Set the background size background = pygame.image.load(bg_image).convert_alpha() #Background of painting balls = [] # Create five balls BALL_NUM = 5 for i in range (BALL_NUM) : #Generate 5 balls position = randint (0,width-100) , randint(0,height-100) #The reason for subtracting 100 is that the size of the sphere image is 100 and the position is randomly generated. speed = [ randint (-10,10) , randint(-10,10) ] ball = Ball(ball_image,position,speed,bg_size) #Objects that generate balls while collide_check(ball,balls): #If the generated ball collides with the previous ball, the ball is regenerated. ball.rect.left , ball.rect.top = randint (0,width-100) , randint(0,height-100) balls.append(ball) #Add all sphere objects to the class table for easy management clock = pygame.time.Clock() #Generating refresh frame rate controller while running : for event in pygame.event.get(): if event.type == QUIT: sys.exit() screen.blit(background, (0, 0)) #Draw the background onto screen for each in balls: #Each ball is moved and redrawn each.move() screen.blit(each.image, each.rect) for i in range (BALL_NUM) : #Cycle 5 balls to determine if the ball has collided with the other four balls. item = balls.pop(i) #Because it's judgment and the other four balls, you need to take the ball out first. if collide_check( item , balls ): #Call the collision detection function. If the result is true, there is a collision ball. item.speed[0] = - item.speed[0] #Backward motion after collision item.speed[1] = - item.speed[1] balls.insert(i , item) #Finally, don't forget to put the ball back in place. pygame.display.flip() clock.tick(30) if __name__ == "__main__": main()
It's collision detection written by myself. Next, let's talk about ready-made ones.
Why do we talk about the existing collision detection functions provided by sprite? I wonder if you have noticed that our collide_check() function is only applicable to collision detection between circles, other polygons (such as rectangles, triangles) and some irregular polygons, so what to do, we will not get the corresponding results, we can try to write a collision detection function for each case, nor can we.
But Pygame's Sprite module has actually provided a mature collision detection function for us to use, which is why we want to inherit our class from Sprite base class of Sprite module.
The sprite module provides a spritecollide() method to detect whether a sprite collides with other sprites in the specified group. (Basically similar to the collide_check() principle we wrote)
spritecollide(sprite, group, dokill, collided = None)
The first parameter, sprite, is to specify the detected wizard; (that is, the item we wrote in it)
The second parameter group is to specify a group (that is, the target list we wrote), which is the group of sprite, so sprite.Group() is used to generate it.
The third parameter, dokill, is to set whether the collision detection wizard is deleted from the group, and to True, then delete.
The fourth parameter collided is to specify a callback function to customize a particular detection method. If the fourth parameter is ignored, the default is to detect rect attributes between wizards.
(We can't use the default detection method here. Look at the picture below. The situation in the picture will be detected as a collision, but actually the two balls haven't collided yet.)
The collide_circle(left, right) method is suitable for detecting collisions between two circles. The parameters of left and right are two wizards respectively, so we use it directly.
collided = pygame.sprite.collide_circle
In addition, the collide_circle() method requires the wizard to have a radius attribute, so we add a radius attribute to the Ball class.
The code is as follows:
from pygame.locals import * from random import * import pygame import math import sys class Ball(pygame.sprite.Sprite) : #Inheritance of animation wizard base class def __init__ (self,imgae,position,speed,bg_size) : pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(imgae).convert_alpha() self.rect = self.image.get_rect() #Get the size of the ball self.rect.left , self.rect.top = position #Place the ball where it appears self.speed = speed #set speed self.width , self.height = bg_size[0] , bg_size[1] #Getting the active boundary is the boundary of the background. self.radius = self.rect.width / 2 def move(self): self.rect = self.rect.move(self.speed) #Move at your own speed if self.rect.right < 0: #The right side of the picture has gone beyond the left side of the boundary, that is, the whole ball has gone out of bounds. self.rect.left = self.width #Let him come back from the right border. if self.rect.bottom < 0: #The bottom of the picture is beyond the top of the boundary. self.rect.top = self.height #Let him come back from the bottom if self.rect.left > self.width: #The left side of the picture is beyond the right side of the boundary. self.rect.right = 0 #Let him come back from the left if self.rect.top > self.height: #If the top of the picture has gone beyond the bottom of the boundary self.rect.bottom = 0 #Let him come back from the top def main() : pygame.init() bg_image = r"D:\Code\Python\Pygame\pygame6: animated sprites\background.png" ball_image = r"D:\Code\Python\Pygame\pygame6: animated sprites\gray_ball.png" running = True #There are many ways to exit the program for the sake of the future. bg_size = width , height = 1024 , 681 #Background size screen = pygame.display.set_mode(bg_size) # Set the background size background = pygame.image.load(bg_image).convert_alpha() #Background of painting balls = [] group = pygame.sprite.Group() #Because you need to use your own group to use your own functions, here we create a # Create five balls BALL_NUM = 5 for i in range (BALL_NUM) : #Generate 5 balls position = randint (0,width-100) , randint(0,height-100) #The reason for subtracting 100 is that the size of the sphere image is 100 and the position is randomly generated. speed = [ randint (-10,10) , randint(-10,10) ] ball = Ball(ball_image,position,speed,bg_size) #Objects that generate balls while pygame.sprite.spritecollide(ball , group , False , pygame.sprite.collide_circle): #If the generated ball collides with the previous ball, the ball is regenerated. ball.rect.left , ball.rect.top = randint (0,width-100) , randint(0,height-100) balls.append(ball) #Add all sphere objects to the class table for easy management group.add(ball) #Group has add () and remove () methods clock = pygame.time.Clock() #Generating refresh frame rate controller while running : for event in pygame.event.get(): if event.type == QUIT: sys.exit() screen.blit(background, (0, 0)) #Draw background aaa onto screen for each in balls: #Each ball is moved and redrawn each.move() screen.blit(each.image, each.rect) for each in group : #Cycle 5 balls to determine if the ball has collided with the other four balls. group.remove(each) #Because it's judgment and the other four balls, you need to take the ball out first. if pygame.sprite.spritecollide(each,group,False,pygame.sprite.collide_circle): #Call the collision detection function. If the result is true, there is a collision ball. each.speed[0] = - each.speed[0] #Backward motion after collision each.speed[1] = - each.speed[1] group.add(each) #Finally, don't forget to put the ball back in place. pygame.display.flip() clock.tick(30) if __name__ == "__main__": main()