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

posted @ 2019-03-24 20:59 菩提树下的杨过 阅读(...) 评论(...) 编辑 收藏