pygame-KidsCanCode系列jumpy-part17-mask-collide碰撞檢測


這節我們研究下pygame的幾種碰撞檢測模式:

如上圖,左側是默認的檢測模式:基於矩形的檢測(這也是性能最好的模式), 右側是基於圓形的檢測(性能略差於矩形檢測)。

矩形檢測法雖然性能好,但是缺點也很明顯:檢測不准確,上圖中"飛機與目標"從視覺上看,根本沒碰到。

 

為了改進,pygame給這二種模式,新增了xxx_ratio的方法,允許指定檢測時,指定二個目標的疊加程度,只有達到指定的疊加值,才認為是真正碰撞到了,參考下圖:

注:ratio的值越大,表示疊加的部分越少

我們寫一個小程序來測試一下:

  1 import pygame
  2 from os import path
  3 from xml.dom.minidom import parse
  4 
  5 SIZE = (WIDTH, HEIGHT) = (265, 320)
  6 FPS = 45
  7 
  8 BLACK = 0, 0, 0
  9 WHITE = 255, 255, 255
 10 
 11 pygame.init()
 12 pygame.mixer.init()
 13 
 14 screen = pygame.display.set_mode(SIZE)
 15 pygame.display.set_caption("My Game")
 16 clock = pygame.time.Clock()
 17 
 18 spritesheet_image_file_name = path.join(path.dirname(__file__), "../img/spritesheet_jumper.png")
 19 spritesheet_xml_file_name = path.join(path.dirname(__file__), "../img/spritesheet_jumper.xml")
 20 spritesheet_image = pygame.image.load(spritesheet_image_file_name)
 21 spritesheet_image.set_colorkey(BLACK)
 22 spritesheet_dom_tree = parse(spritesheet_xml_file_name)
 23 root_textures = spritesheet_dom_tree.documentElement
 24 sub_textures = root_textures.getElementsByTagName("SubTexture")
 25 dic_image = {}
 26 
 27 
 28 def get_image_rect(img_name):
 29     if dic_image.get(img_name):
 30         return dic_image[img_name]
 31     for texture in sub_textures:
 32         name = texture.getAttribute("name")
 33         if img_name == name:
 34             dic_image[img_name] = pygame.Rect(
 35                 int(texture.getAttribute("x")),
 36                 int(texture.getAttribute("y")),
 37                 int(texture.getAttribute("width")),
 38                 int(texture.getAttribute("height"))
 39             )
 40             return dic_image[img_name]
 41 
 42 
 43 def get_image(img_name):
 44     rect = get_image_rect(img_name);
 45     image = pygame.Surface((rect.width, rect.height))
 46     image.blit(spritesheet_image, (0, 0), rect)
 47     image.set_colorkey(BLACK)
 48     return image
 49 
 50 
 51 class Demo(pygame.sprite.Sprite):
 52     def __init__(self, image, pos):
 53         pygame.sprite.Sprite.__init__(self)
 54         self.image = image
 55         self.rect = self.image.get_rect()
 56         self.rect.x = pos[0]
 57         self.rect.y = pos[1]
 58 
 59 
 60 running = True
 61 pos1 = (20, 5)
 62 pos2 = (125, 110)
 63 
 64 demo1 = Demo(get_image("bunny1_walk1.png"), pos1)
 65 demo2 = Demo(get_image("bunny2_stand.png"), pos2)
 66 
 67 all_sprites = pygame.sprite.LayeredUpdates()
 68 all_sprites.add(demo1, layer=2)
 69 all_sprites.add(demo2, layer=1)
 70 
 71 group2 = pygame.sprite.Group()
 72 group2.add(demo2)
 73 
 74 while running:
 75     clock.tick(FPS)
 76 
 77     for event in pygame.event.get():
 78         if event.type == pygame.QUIT:
 79             running = False
 80         if event.type == pygame.KEYDOWN:
 81             if event.key == pygame.K_LEFT:
 82                 demo1.rect.x += -3
 83             if event.key == pygame.K_RIGHT:
 84                 demo1.rect.x += 3
 85             if event.key == pygame.K_UP:
 86                 demo1.rect.y += -3
 87             if event.key == pygame.K_DOWN:
 88                 demo1.rect.y += 3
 89 
 90     screen.fill(BLACK)
 91 
 92     all_sprites.draw(screen)
 93 
 94     pygame.draw.rect(screen, (0, 255, 0), demo2.rect, 1)
 95     pygame.draw.rect(screen, (255, 0, 0), demo1.rect, 1)
 96 
 97     font = pygame.font.SysFont("Menlo", 25, True)
 98 
 99     # 默認的檢測模式(rect)
100     if pygame.sprite.spritecollide(demo1, group2, False):
101         pos_txt = font.render("hit:true", 1, (255, 255, 128))
102     else:
103         pos_txt = font.render("hit:false", 1, (255, 255, 128))
104     screen.blit(pos_txt, (150, 10))
105 
106     pygame.display.update()
107 
108 pygame.quit()
View Code

這是默認的Rect檢測模式。把100行換成:

    # 矩形檢測(至少要重疊1-0.7=30%才算發生了碰撞)
    if pygame.sprite.spritecollide(demo1, group2, False, pygame.sprite.collide_rect_ratio(0.7)):

再看下效果:

 

繼續,換成圓形檢測試下:

    # 圓形檢測(至少要重疊1-0.7=30%才算發生了碰撞)
    if pygame.sprite.spritecollide(demo1, group2, False, pygame.sprite.collide_circle_ratio(0.7)):

那么,有沒有一種方法能做到精確檢測呢?當然有,有一種基於mask(在繪圖軟件中,也稱為遮罩或蒙版)的檢測方法,類似把背景去掉后,像素級的碰撞檢測,當然性能也是最差的。

首先要在Sprite的類上,指定mask:

1 class Demo(pygame.sprite.Sprite):
2     def __init__(self, image, pos):
3         pygame.sprite.Sprite.__init__(self)
4         self.image = image
5         self.rect = self.image.get_rect()
6         self.rect.x = pos[0]
7         self.rect.y = pos[1]
8         #指定蒙版
9         self.mask = pygame.mask.from_surface(self.image)
View Code

然后把檢測代碼改成:

    # 基於mask的檢測
    if pygame.sprite.spritecollide(demo1, group2, False, pygame.sprite.collide_mask):

好了,利用上面學到的知識,把上節的遺留問題:"player與enemy的碰撞檢測不准確" 解決一下,效果如下:

優化前 優化后

 

源碼示例:https://github.com/yjmyzz/kids-can-code/tree/master/part_17


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM