Superman game_ Draw obstacles in the background with pygame.mask.from_threshold enables accurate collision detection of Superman and obstacles of different colors

Posted by gawrrell on Mon, 20 Sep 2021 11:58:05 +0200

There are many Superman games on the Internet, such as Superman training, Superman hammer training, Superman training, Superman test, Superman leap, Superman sprint, etc. Generally, match people play superman. They can run, jump and climb cliffs. They have strong ability. The game scene is grand and the level is more and more difficult. However, because we only learn programming, we should focus on realizing Superman's running, jumping, climbing and other actions with pygame. Therefore, only three levels are set to verify Superman's action code. It is impossible to view the game source code. You can only play by yourself or watch others play games. Try to use pygame to realize the actions you see. These games are agile games. The elderly may not be able to play them, and the children have bad eyes. Fortunately, there is a video of playing "Superman training" on BiliBili website, which shows the various actions of customs clearance, and complete the pygame "Superman training" game according to the actions seen. Readers can also check the video to see whether the game has achieved most of the actions of the "Superman training" game. The link is as follows: https://www.bilibili.com/video/av370211982/
The Superman game in the video uses large obstacle graphics, which are very long in the x-axis direction. For large obstacle graphics, if you take out part of the graphics that can fill the whole window from the large obstacle graphics as the sub obstacle graphics every time you move, in order to detect the obstacles in the sub obstacle graphics, you must create a mask for the sub obstacle graphics. In this way, it is obviously unreasonable to re create a mask every time you move. Of course, you can also use the whole large obstacle graphics as a role to create a mask of the large obstacle graphics. The role movement is actually the reverse movement of the large obstacle graphics. The advantage of this method is that it can move simultaneously in the X and y directions, and the implementation is relatively simple. If there are not too many levels, it is also a feasible method, but if there are too many levels, the large obstacle graphics must be large, Doing so may affect the running speed of the program. The traditional method can also be used to divide the large obstacle graphics into multiple sub obstacle graphics with the same width as the form. The sub obstacle graphics can be as high as the form in the y direction, so that Superman can move by himself in the y direction. It can also be like the game in the video. The obstacle graphics are wider than the window, so Superman doesn't move in the y direction, and it is completed by the reverse movement of the obstacle graphics in the y direction. In this example, the divided obstacle graphics are used to have the same height as the window in the y direction. After the large obstacle graphics are divided, all the divided obstacle graphics are as follows.

There are 5 figures in total. Except for the start and end figures, there are 3 levels in total. Each sub obstacle figure is composed of three colors. White is the background color and set as transparent. Superman walks on the black obstacle, and Superman encounters the red obstacle. The game ends. In order to use the barrier graph, you must save the barrier graph with the surface class instance in the program, obtain and save the black mask and red mask of the surface class instance, and save all the barrier graphs and their two masks in the dictionary bgs. Here n is the key and the barrier graph number. The value is a list in the format of: [surface class instance of obstacle graphics, black collision mask of this instance, red collision mask of this instance]. See lines 135-139. pygame.mask.from_threshold creates a mask to detect a certain color. See my blog post: function pygame.mask.from for the principle_ Threshold () principle and application method of determining mask collision point with threshold.
Superman has multiple shapes, and each shape should have its own mask. If there are many shapes, generally multiple shapes are concentrated in one picture. When changing the shape, take out the corresponding shape from the large picture. At this time, a mask must be created for taking out the shape. Superman needs to create a mask every time he changes the shape, which is time-consuming. You can split all shapes at one time, or use independent shape pictures, create a mask for them, and save them to the list for easy use.
Superman in this program has 7 independent modeling pictures, all facing the right. Superman moves to the right, and these 7 pictures should be used as modeling. If Superman moves to the left, it also needs 7 shapes to face the left. It is obtained by flipping the existing 7 shape pictures along the y axis. Therefore, there are 14 shapes in total. Each shape should have its own mask. Directly use these independent shape pictures to create a mask for each shape and save it to the dictionary mans. See lines 140-149 for specific codes. 7 modeling pictures are as follows:

Superman needs to constantly change his shape. If black is not encountered before modeling transformation, it must be ensured that black is not encountered after modeling transformation. This has some requirements for Superman modeling graphic design. The Superman standing (or horizontal) picture should be of equal height and width. The center point of the Superman figure is in the center of the picture. The distance between the SUPERMAN Figure and the upper (lower, left or right) boundary of the picture is the same. After the transformation, the modeling center is aligned before and after the transformation, so that the distance to the black obstacle is basically the same before and after the transformation.
In the game shown in the video, Superman actions include running left or right, crawling, sitting down, sliding down, jumping and climbing cliffs. Use up, down, left and right and space bar to change different actions. According to these, this program sets the purpose of these five keys. Left key: Superman shape faces the left side and advances to the left along the x-axis; Right button: Superman shape faces the right side and moves forward along the right side of the x-axis; Down key: Superman gets down and is ready to crawl; Up button: Superman sits down and is ready to slide down; Space bar: jump, such as climbing a cliff. Therefore, it can be seen that the left and right keys cannot be pressed at the same time. Because crawling, sitting, sliding down and jumping all need to move forward, the left (or right) key and one of the up, down or spacebar can be pressed at the same time. Using variable man_state records Superman's motion state, man_state=1 running, = 2 crawling, = 3 sitting down, = 4 jumping, = 5 climbing. To check whether the five keys on the keyboard are pressed at the same time, use the statement pygame.key.get_pressed() gets the pressed status of all keys and saves it to the key list_ List (line 162). change_state(key_list) is used to determine the current state of Superman according to the key pressed by the player (line 45). This method must be called at the front of all relevant Superman code (line 165). After that, call Superman running man_Move(key_list) method (line 166), because this method has nothing to do with the shape. In other words, no matter what kind of shape, it must move forward or backward along the x-axis. In this method, it is also necessary to decide whether to encounter the cliff. If you encounter the cliff, you must change the shape to climb the cliff. Therefore, the method change can only be called later_ Model (center, key_list) (line 167) changes the shape. Finally, the manJumpClimbDown(key_list) method (170th lines) is invoked to complete the functions of jumping, climbing or dropping.
Look at the method below_ Move (key_list) method (line 64). This method enables Superman to run left or right. The specific direction depends on whether dx is positive or negative. In method change_ Lines 47-52 in state determine whether it is positive or negative according to whether the left or right button is pressed, and determine whether Superman faces the left or right. Superman doesn't actually run. Its x coordinate remains unchanged and is completed by the reverse movement of the obstacle background (line 67). When Superman runs on a black obstacle, his feet always stay above the black obstacle and do not contact with the black obstacle. If he goes uphill, he may encounter a black obstacle. Therefore, every step forward, it is necessary to detect whether he encounters a black obstacle (line 68). If he encounters a black obstacle, Superman will increase the dy value along the y direction and get out of contact with the black obstacle. But you may also encounter cliffs. No matter how much you move up, you will always encounter black obstacles. Here comes the criterion for judging whether Superman meets a cliff. This procedure sets the gradient of the uphill slope > 45 degrees to be the cliff. Therefore, after judging whether the black obstacle is true for the second time (line 70), it is considered to be a cliff. Superman returns to the position before moving and marks the cliff as is_ Set cliff to true to make man_state=5, indicating that the shape should be changed to climbing. Therefore, there can be no uphill slope > 45 degrees in the obstacle background. If a large slope is required, it can be judged as a cliff after the second judgment of whether it meets a black obstacle is true, and the third judgment is true. At this time, the slope is about more than 70 degrees. There is no code for Superman to go downhill. Instead, use the code for the public downlink in the method manJumpClimbDown() (line 78).
change_model(center,key_list) method (line 14) according to man_ The state value modifies the Superman shape. Parameter 1 is the center position of the shape before the transformation. The transformed shape should keep this position unchanged. As mentioned earlier, this has requirements for shape design. Modifying the shape is very simple. It should be noted that after modifying the shape, Superman did not collide with black obstacles before changing the shape, but collided after changing the shape. Modeling design should try to avoid this situation. If this situation occurs, the program should be corrected. The size of the original shape picture is large and reduced by three times (line 143). The actual standing shape size becomes 40 wide and 60 high, and the horizontal size becomes 60 wide and 40 high. If you change from crawling to running, the running center point is high and the crawling center point is low. The distance between the two modeling center points and the lower boundary is 30 and 20 respectively, and the difference is 10. After conversion, your running legs will encounter black. Superman can go up for 10 at most, so as to avoid encountering black obstacles. The statements in lines 35-39 can solve the collision problem caused by the transformation of modeling from crawling to running. Even if Superman goes up too much, the automatic down program in lines 94-99 can make Superman's distance from the black obstacle no greater than dy. If the program circulates 3 times and goes up 15 times, Superman still collides with black, the sentence in line 40 will be executed, indicating that the conversion is not from crawling to running, but from running to crawling. The width of crawling shape is greater than that of running shape. After conversion, the distance from the center point of running shape to the left (or right) boundary is 10 more than that before transformation. The increase of width makes Superman on the left, Maybe it's a black obstacle on the right. Superman moves 10 to the right, still collides, and then moves 20 to the left to make the collision disappear.
Finally, the manJumpClimbDown(key_list) method is introduced, which will complete the functions of jumping, climbing and falling. First introduce the jump function. If you are not currently jumping, jump_mark=True, indicating that jumping is allowed. Players press the spacebar and click change_ In the state method, make man_ State = 4 (line 58). Execute change_ After the model method, it will make jump_ High = 20 (line 28), which will make Superman go up dy and jump in every frame within 20 frames_ Mark = false (line 29). It is not allowed to modify jump before completing this jump_hight. In the method manJumpClimbDown, such as jump_ High > 0, Superman goes up until jump_ High = 0 or encounter a black obstacle. If the left and right keys are not pressed during the jump, the jump goes straight up and down. If the left or right key is pressed, Superman jumps and moves left or right at the same time. If you encounter a cliff (line 88), it will become a climbing shape. Press and hold the space bar, Superman will rise continuously along the cliff. Release the space bar, Superman will fall continuously along the cliff until you encounter a black obstacle. Instead of the above two cases, Superman automatically descends until it encounters a black obstacle (lines 94-99).
The bump(what_color) method detects whether there is a collision with the color specified in parameter 1. If there is a collision, it returns true, otherwise it returns false. Because there are two sub obstacle backgrounds in the form at the same time, it is necessary to detect whether Superman collides with the obstacles in the two sub obstacle backgrounds. Detect collision with color, use pygame.mask.from_threshold creates a mask method that detects a color.
In addition, when the program runs, the player controls Superman's movement. There is a small green circle in the upper left corner of Superman, which is used for debugging. It can be seen from the previous description that one of the purposes of program design is that Superman displayed on the screen can not touch the black obstacle and is very close to the black obstacle, run left or right, climb up or fall down along the cliff. However, there are various reasons that may not achieve this goal, making Superman encounter black obstacles when displaying. Because Superman runs and climbs too close to the black obstacle, programmers may not be able to accurately judge whether the Superman displayed on the screen encounters the black obstacle. Therefore, before displaying Superman (line 177), the code in lines 171-174 is added to judge whether Superman collides with black. If there is no collision, the top left of Superman is a green circle, otherwise the top left of Superman is a blue circle. If you see a blue circle, it indicates that Superman collides with black obstacles, and the reason should be checked. Finally, the program should remove these codes.
In line 130, take out the blue sky and white clouds picture and save it as background. Line 157 sets the form background to white, but it is commented out. Line 158 sets the form background to blue sky and white clouds. The white of the obstacle background bg0-bg4 is set to transparent color, so that the blue sky and white clouds can be seen in the white part of these obstacle backgrounds. Blue sky and white cloud graphics do not participate in collision detection and have no impact on the operation of the program. They just increase the observability of the game or make the game look more real. The operation effect is as follows. In order to reduce the size of GIF file, no blue sky and white cloud background is added.

The complete procedure is as follows. There may be unreasonable places. Criticism and correction are welcome.

import pygame

def bump(what_color):   #If what_color='red '(or =' Black '), check whether Superman collides with the red (black) color in the left background and right background
    global bg_No,man_No,man_rect,bg_x
    offset = man_rect.x - bg_x, man_rect.y          #Superman and left background difference, subtraction and subtraction order cannot be exchanged
    offset1= man_rect.x - (bg_x+640), man_rect.y    #Superman and right background difference. Note that the background y coordinate = 0
    if what_color=='red':                           #See lines 137 and 142 for the data structure of bgs and mans
        return bgs[bg_No][2].overlap(mans[man_No][man_face+1],offset) or\
                    bgs[bg_No+1][2].overlap(mans[man_No][man_face+1],offset1)        
    else:
        return bgs[bg_No][1].overlap(mans[man_No][man_face+1],offset) or\
                    bgs[bg_No+1][1].overlap(mans[man_No][man_face+1],offset1)
#According to man_ State changes the shape. Parameter 1 is the Superman center coordinate. When the shape changes, its center coordinate remains unchanged
def change_model(center,key_list):                  #Valuepoint 2 record which keys are pressed on the keyboard
    global man_mask,man_image,man_rect,man_No,man_face,man_state,jump_hight,jump_mark,is_cliff
    if man_state==1:        #man_state==1 is running status
        if key_list[pygame.K_LEFT] or key_list[pygame.K_RIGHT]:         #If the left or right key is pressed
            man_No+=1       #Running has 4 shapes, which will change to the next one after one frame. Each running shape picture rectangle should be equal in width and height,
            if man_No>3:    #>3, starting from 0. The center points of the rectangle and Superman should coincide. All Superman images are the same from the top, bottom, left and right boundaries of the rectangle,
                man_No=0                #Avoid collision after shape conversion without collision
        elif man_No>3:            
            man_No=0    #man_No is the model number
    elif man_state==2:  #man_state=2 is in the lying down state. There is only one shape. You can also add shapes
        man_No=4
    elif man_state==3:  #man_state=3 is the sitting and sliding state. There is only one shape, and the shape can also be added
        man_No=5          
    elif man_state==4 and jump_mark and not(is_cliff):#If it is in jumping state (= 4), it is allowed to jump up and not climb
        jump_hight=20   #If jumping is allowed, start jumping up first, increase dy for each frame, reach the top of jumping up for 20 frames in total, and then start falling
        jump_mark=False #Jump is in progress. It is not allowed to modify jump_ High, jump complete, make jump_mark=True, allow to jump again
    elif man_state==5:  #Climbing state
        man_No=6
    man_image=mans[man_No][man_face]  #man_No is the model number, man_face is Superman facing in that direction. Different directions and shapes are also different
    man_mask=mans[man_No][man_face+1] #mask of this shape
    man_rect=man_image.get_rect(center=center) #Record the Rect class instance of shape position and width and height. The center position of shape change remains unchanged   
    for n in range(3):        #First, suppose you change from crawling to running. The center point of running is high and crawling is low. After the change, your legs run to black
        if bump('black'):     #Superman must move up until it doesn't collide with black. The distance from the center point of crawling and running to the lower boundary is 20 and 30 respectively   
            man_rect.centery-=5  #Superman needs to move up 10. Here, cycle 3 times and go up 15. If there are too many going up,
        else:                    #Lines 94-99 will automatically reduce the Superman crawling shape to black obstacle < dy
            return
    if bump('black'):  #This must not be from crawling to running, but from running to crawling. The crawling shape width is greater than the running width, and meets black
        bg_Move(-10)   #The distance between the center point of the transformed crawling shape and the left (or right) boundary is 10 more than that before the transformation, and Superman moves 10 to the right first
        if bump('black'): #There is still a collision. Superman moves left by 20
            bg_Move(20)

def change_state(key_list):         #Change Superman's state of motion, man_state=1 running, = 2 crawling, = 3 sitting down, = 4 jumping, 5 climbing
    global man_face,dx,man_state,bg_No,is_cliff    
    if key_list[pygame.K_LEFT]:     #If the left key is pressed
        man_face=2                  #Superman faces the moving direction
        dx=abs(dx)                 #Superman moves right, actually the background moves left
    elif key_list[pygame.K_RIGHT]:
        man_face=0
        dx=-abs(dx)
    if key_list[pygame.K_DOWN]:     #Press the down arrow key to crawl
        man_state=2            
    elif key_list[pygame.K_UP]:     #Press the up arrow key and slide down
        man_state=3          
    elif key_list[pygame.K_SPACE]:  #jump
        man_state=4
    elif is_cliff:                  #Encountered cliff mark, in man_ In the move (key_list) method, it is determined that a cliff is encountered
        man_state=5
    else:                           #running
        man_state=1

def man_Move(key_list):         #Superman moves left and right along the x-axis
    global dx,dy,is_cliff,man_rect,man_No,man_state
    if key_list[pygame.K_LEFT] or key_list[pygame.K_RIGHT]:     #If the left or right button is pressed, Superman moves along the x-axis        
        bg_Move(dx)#Superman moves along the x direction, but the background moves in the opposite direction. Note that dx is in change_ The direction is determined in the state method
        if bump('black'):                           #If Superman moves in the x direction and touches black
            man_rect.centery-=dy                    #Superman, I hope I don't encounter black again
            if bump('black'):                       #If you still encounter black, it is considered that you encounter a cliff and set is_cliff=True                
                bg_Move(-dx)                        #Superman returns to the original position. If it is set in this way, the upward slope is required to be less than 45 degrees
                man_rect.centery+=dy                #If you add another if bump('black '): the upward slope is less than 70 degrees
                is_cliff=True
                man_state=5                
        else:             
             is_cliff=False             

def manJumpClimbDown(key_list):    #Jumping, climbing and falling function method
    global dy,man_state,is_cliff,man_y,jump_mark,jump_hight,man_rect    
    if jump_hight>0:#Realize the jump, so when the left and right keys are not pressed, jump straight up and down, if pressed, advance like the long jump
        man_rect.centery-=dy         #Reduce dy per frame
        if man_rect.y<=0 or bump('black'):  #If it touches the upper boundary or black
            man_rect.centery+=5     #Keep original position
            jump_hight=0            #The jump is over,
        else:
            jump_hight-=1           #Jump dy, jump on each frame_ High decreases by 1 until it is 0, and the jump uplink ends
    else:                           #If it's not a jump, it could be a descent or climb
        if key_list[pygame.K_SPACE] and man_rect.y>0 and is_cliff:  #Meeting these conditions is climbing
            man_state=5             #Climbing status number is 5
            change_model(man_rect.center,key_list) #Change to climbing style
            man_rect.centery-=5     #Climb up
            if man_rect.y<=0 or bump('black'):  #If it touches the upper boundary or black
                man_rect.centery+=5             #Keep original position
        else:                       #At this point, Superman falls automatically until it touches black           
            man_rect.centery+=5
            if bump('black'):    
                man_rect.centery-=5
                jump_mark=True        
                is_cliff=False

def initialization():           #Initialization program
    global is_cliff,jump_mark,jump_hight,man_state,bg_x,bg_No,man_No,man_face,bgs,mans,key_list
    is_cliff=False              #Does Superman go to the cliff
    jump_mark=True              #Is Superman allowed to jump
    jump_hight=0    #Superman jump up, set to the maximum height allowed to jump up, subtract 1 every frame, = 0, the uplink ends and the downlink starts to jump
    man_state=1     #=1 running, = 2 crawling, = 3 sitting down, = 4 jumping, 5 climbing
    bg_x=-330       #Initial x coordinate of left obstacle background, y=0
    bg_No=0         #Left obstacle background number
    man_No=0                  #Superman's current shape number
    man_face=0                #=0, Superman facing right, = 2, Superman facing left
    key_list = pygame.key.get_pressed()
    change_model((330,200),key_list)

def bg_Move(dx):              #Background movement is equivalent to Superman's reverse movement. dx is the movement increment, positive right and negative left
    global bg_x,bg_No
    bg_x+=dx
    if dx<0:                        
        if bg_x<-640:
            bg_x+=640
            bg_No+=1            
    else:                        
        if bg_x>0:
            bg_x-=640
            bg_No-=1

pygame.init()
size = width, height = 640,480
pygame.display.set_caption("Detect collisions between rectangles and colors")
screen = pygame.display.set_mode(size)
background=pygame.image.load("Blue sky and white clouds.png").convert_alpha()
dx=5                      #Background moving speed, Superman does not move along the x-axis
dy=5                      #Superman moving speed along y axis
bgs={}                    #Save all obstacle backgrounds
mans={}                   #Save all Superman shapes
for n in range(5):        #Save all background pictures and two mask s to the dictionary bgs
    bg=pygame.image.load("bg"+str(n)+".png").convert_alpha()   #background image     
    maskBlack=pygame.mask.from_threshold(bg,pygame.Color('black'),(1,1,1,255))#Moving circles collide only when they touch black
    maskRed=pygame.mask.from_threshold(bg,pygame.Color('red'),(1,1,1,255))#Moving circles collide only when they touch red
    bgs[n]=[bg,maskBlack,maskRed]#n is the key, background number. The value is the list, [background picture, this picture collides with the black mask, this picture collides with the red mask]
for m in range(7):        #Save all Superman shapes, that is, their mask s, to the dictionary mans
    man=pygame.image.load("people"+str(m+1)+".png").convert()   #Superman's original graphics are 120 * 180 and 180 * 120 
    r=man.get_rect()                                    #The following sentence reduces the graphics by three times, and the actual Superman graphics are 40 * 60 and 60 * 40
    man=pygame.transform.scale(man,(int(r.width//3),int(r.height//3)))
    man.set_colorkey((255,255,255))             #Set white as transparent color, that is, the bottom color of the shape is transparent white
    man1=pygame.transform.flip(man,True,False)  #Get the inverse symmetric Superman shape
    man1.set_colorkey((255,255,255))
    mask0=pygame.mask.from_surface(man)         #Get the mask of two shapes
    mask1=pygame.mask.from_surface(man1)
    mans[m]=[man,mask0,man1,mask1]              #Save two different directional shapes and their mask s to the mans dictionary
initialization()                                #Initialization method
fclock = pygame.time.Clock()
fps = 15
running = True
while running:
    if bump('red'):
        initialization()
    #screen.fill(pygame.Color('white'))   #The real background can be monochrome
    screen.blit(background,(0,0))         #It can also be a picture, which only increases the fidelity of the game and does not participate in collision detection  
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False        
    key_list = pygame.key.get_pressed() #key_list records the keys pressed on the keyboard
    if key_list[pygame.K_r]:     #If the r key is pressed, play again
        initialization()
    change_state(key_list)
    man_Move(key_list)    
    change_model(man_rect.center,key_list)    
    screen.blit(bgs[bg_No][0], (bg_x,0))            #Draw two obstacle backgrounds
    screen.blit(bgs[bg_No+1][0],(bg_x+640,0))
    manJumpClimbDown(key_list)
    color1=pygame.Color('green')                    #Try the following four sentences
    if bump('black'):
        color1=pygame.Color('blue')                 #If Superman collides with black, Superman has a blue circle on the upper left
    pygame.draw.circle(man_image,color1,(5,5),5)    #Otherwise, it is a green circle. Finally, the program will delete these four statements
    if man_rect.y<=0:                               #Avoid Superman jumping out of the upper boundary
        man_rect.y=1
    screen.blit(man_image, man_rect)    
    pygame.display.update()    
    fclock.tick(fps)
pygame.quit()

Topics: Python pygame