函数pygame.mask.from_threshold()用阈值确定mask碰撞点原理及使用方法

Posted geng_zhaoying

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数pygame.mask.from_threshold()用阈值确定mask碰撞点原理及使用方法相关的知识,希望对你有一定的参考价值。

通常一个游戏中会有很多角色出现,而这些角色之间的“碰撞”在所难免,例如炮弹是否击中了飞机等。碰撞检测在绝大多数游戏中都是一个必须处理的至关重要的问题。除了角色之间的“碰撞”,在游戏设计中也经常用到侦测角色和指定颜色的碰撞,以及侦测角色和另1角色特定部位的碰撞。本文用实例介绍如何用pygame.mask.from_threshold()方法创建实现上述功能的pygame.mask。该方法使用阈值,而不是使用是否透明确定参加碰撞的像素点。
该方法相当于两个方法,如只是用前3个参数,这个方法返回一个mask,使用这个mask能侦测另1个角色和具有这个mask的角色中指定颜色的碰撞。如使用参数4,参数2将被忽略,相当于另一个方法,下边再讨论。
如不使用参数4和5,官方文档给出调用格式如下,该方法返回一个pygame.mask,这个mask只对参数2指定颜色产生碰撞响应。参数1是需要创建mask的surface,参数2是设定的颜色,参数3是颜色阈值,个人理解是surface中图形实际颜色和参数2指定颜色允许一定偏差。这种用法成功做成2个例子。
pygame.mask.from_threshold(Surface,color,threshold=(0,0,0,255))-> Mask
在游戏程序设计中,一个角色可能由不同颜色组成,但希望仅碰到一种颜色发生碰撞。第一个例子介绍了实现该功能的具体步骤。该例画一个大的红色圆环,环内有个蓝色的实心圆,再画一个黄色的小圆。移动黄色小圆接近大圆,希望碰到大圆的红色圆环时,检测不到碰撞,只有碰到大红色圆环内蓝色的圆时,才能检测到碰撞,发生碰撞时改变窗体背景色。完整程序如下。

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))            #创建1个100X100的Surface实例image
        self.image.set_colorkey((0,0,0))                  #设置image中颜色(0,0,0)的颜色为透明色
        self.image.fill((0, 0, 0))                        #底色为黑色,由于上条,底色变为透明        
        self.radius=radius
        self.width=width        #下句,在image上画实心圆或画圆环,self.width=0,画实心圆,>0画圆环
        pygame.draw.circle(self.image,pygame.Color(color),(50,50),self.radius,self.width)
        if self.width>0:        #如果self.width>0,画实心圆后,还要画一个实心蓝色圆
            pygame.draw.circle(self.image,pygame.Color('blue'),(50,50),25,0)
            #用下式设置mask,无论该sprite有多少种颜色,只有蓝色和其它sprite碰撞后,检测结果为真
            #所得mask,仅将蓝色所在位置设置为1,其他颜色和底色都设置为0,即蓝色参加碰撞检测            
            self.mask=pygame.mask.from_threshold(self.image,pygame.Color('blue'),(1,1,1,255))#圆环角色mask            
        else:
            self.mask=pygame.mask.from_surface(self.image)  #黄色小圆的mask
        self.rect = self.image.get_rect(center=pos)         #将image移到指定位置
    def draw(self,aSurface):
        aSurface.blit(self.image,self.rect) #从Sprite派生类不放入Group,类实例显示需调用自定义draw        
pygame.init()
screen = pygame.display.set_mode((200,100))
pygame.display.set_caption("和蓝色发生碰撞")
clock = pygame.time.Clock()
circleRed=Circle((50,50),'red',45,20)                       #创建大红色圆环和内部蓝色实心圆,Circle实例
circleyellow=Circle((150,50),'yellow',15,0)                 #创建黄色小圆,Circle实例
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:            #按键按下事件
            if event.key==pygame.K_RIGHT:         #按键盘右方向键使球向右
                circleyellow.rect.x+=5                            
            elif event.key==pygame.K_LEFT:        #按键盘左方向键使球向左
                circleyellow.rect.x-=5            
    if pygame.sprite.collide_mask(circleRed,circleyellow):    #检测两圆是否发生碰撞   
        screen.fill((200, 200, 200))               #发生碰撞,窗体背景变灰色 
    else:
        screen.fill((255, 255, 255))               #不发生碰撞,窗体背景白色
    circleRed.draw(screen)
    circleyellow.draw(screen)
    clock.tick(10)
    pygame.display.update()
pygame.quit()


在游戏程序设计中,如不同角色有不同颜色,可能希望仅碰到一种颜色角色时发生碰撞。例如有青苹果和红苹果,只希望摘到红苹果。又如飞机大战,蓝色敌机入侵,我方用高射炮射击敌方飞机并派红色飞机出战,高射炮应只击中敌方蓝色飞机等。第2个例子介绍了只和指定颜色角色发生碰撞的方法。该例有20个不同颜色的圆形角色,位置固定不变,还有1个能随鼠标移动的圆,移动的圆只有碰到蓝色固定位置的圆才发生碰撞,发生碰撞后将改变窗体背景色。完整程序如下。

import pygame
import random
class Circle(pygame.sprite.Sprite):
    def __init__(self,pos,color,*grps): #*args表示有多个(个数不定)无名参数,它本质是一个tuple(元组)
        super().__init__(*grps)
        self.image = pygame.Surface((32, 32))           #创建1个32X32的Surface实例image
        self.image.set_colorkey((0,0,0))                #设置image中黑色为透明色
        self.image.fill((0, 0, 0))                      #底色为黑色,由于上条,底色变为透明
        pygame.draw.circle(self.image, pygame.Color(color), (15, 15), 15) #在image上画圆
        self.rect = self.image.get_rect(center=pos)     #将image移到指定位置        
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) #创建Circle类实例并增加到列表objects
for sprite in objects: #为列表中Circle实例设置mask。不能在Circle类构造函数中设置mask,不报错,结果不正确   
    sprite.mask=pygame.mask.from_threshold(sprite.image, pygame.Color('blue'),(1,1,1,255))
player = Circle(pygame.mouse.get_pos(), 'green')  #移动的圆,可以碰撞20个有固定位置的圆
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()


关于参数3:threshold,中文意思是:阈值。对于参数3官方英文文档说明是:the threshold range used to check the difference between two colors.中文译文是:用来检测两种颜色之差的阈值范围。个人理解可能是在surface图形中颜色值和参数2的颜色值允许一定偏差,就是说虽然参数2是纯色,但参数1图形中的颜色有些偏差也能产生碰撞,只要小于设定阈值。在成功的两个例子中对参数3threshold的数值范围做了一些实验。发现参数3可以是rgba,也可以是rgb。还发现只考虑rgb,r、g或b的取值范围都是正整数1到255,不能为0,否则不能产生碰撞,这是因为两个相同颜色的差值是(0,0,0),为了使(0,0,0)在阈值内,因此rgb每种颜色的阈值必须要大于等于1。由此可以推论,阈值应是按颜色的红、绿和蓝的值单独计算后,分别进行比较的,例如参数threshold=(1,2,3),那么红绿蓝的阈值分别是1、2和3。比较两种颜色是否在阈值内,就要分别计算红、绿和蓝色的差值绝对值,即|r1-r2|、|g1-g2|和|b1-b2|,如果红、绿和蓝色的差值都小于threshold对应的阈值,说明两种颜色差值在阈值内。为了进一步验证以上所述内容的正确性,继续对以上两个程序做一些实验。先将两程序所使用的颜色值列出:红:255,0,0,绿:0,255,0,蓝:0,0,255,白:255,255,255,黑:0,0,0,浅绿:144,238,153,黄:255,255,0。在第1个程序中,使用了红蓝黑白黄5种颜色,黑色是参数1(Surface类实例)的图片底色,白色是窗体背景色,黄色是黄色圆的颜色,白色和黄色和参数threshold无关。修改第1例第17行中参数3的值为(255,255,255,255),该程序运行后仍能只和蓝色圆发生碰撞,开始感到很奇怪,但计算后发现程序是正确的。计算蓝色和黑红蓝的差值绝对值:蓝-黑=(0,0,255),蓝-红=(255,0,255),蓝-绿=(0,255,255),蓝-蓝=(0,0,0),显然只有蓝色在阈值内,能产生碰撞。第2个程序使用了上边列出的所有7种颜色,运行后,绿色圆移动只有碰到固定位置蓝色圆才产生碰撞,碰到其它固定圆不能产生碰撞。修改第2例第19行中参数3=(255,255,255,255),再一次运行,绿色圆移动碰到蓝色圆和浅绿色圆都产生了碰撞,碰到红色和黄色圆仍然不能产生碰撞。计算蓝-浅绿=(144,238,102),因此满足了碰撞条件。通过上述实验,可确认参数threshold的阈值格式是(Nr,Ng,Nb,a)或(Nr,Ng,Nb),Nr、Ng和Nb取值范围是正整数1-255。两种颜色的差值计算方法是分别计算两颜色的红、绿和蓝色的差值绝对值,即|r1-r2|、|g1-g2|和|b1-b2|,只有|r1-r2|<Nr、|g1-g2|<Ng和|b1-b2|<Nb同时成立,才能认为两种颜色的差在threshold的阈值内。由此还可以看出,判断是否发生碰撞的唯一依据是阈值,只要小于阈值,所有颜色都能发生碰撞。如果希望只和蓝色相近的颜色发生碰撞,参数3应该是:(红色允许最大值,绿色允许最大值,蓝色允许最大差值),这3个值越大,距离蓝色越远,越小距离蓝色越近,极限是(1,1,1)。这些大部分是个人看法,可能不正确,希望读者指正,万分感激。
如果参数4和5被使用,官方文档给出调用格式如下。参数1是需要创建mask的surface,参数2被忽略不被使用,参数3是颜色阈值,参数4是另一个surface,(以下是个人理解)该方法将逐一将参数1代表的Surface的所有像素值(包括底色)和参数4代表的Surface的所有对应像素值(包括底色)相减,差值在参数3确定的阈值范围内的像素点,在返回mask中设置为1,该点将参加碰撞检测。
pygame.mask.from_threshold(Surface,color,threshold=(0,0,0,255),othersurface=None,palette_colors=1)-> Mask
根据以上对该方法理解,编写了第3个程序。和第1个程序类似,该例画一个大的红色圆环,环内有个蓝色的实心圆,再画一个黄色的小圆。移动黄色小圆接近大圆,希望碰到大圆的红色圆环时,检测不到碰撞,只有碰到大红色圆环内蓝色的圆时,才能检测到碰撞。创建mask语句和第1个例子不同,创建mask语句如下。其中,参数1是circleRed是红色圆环内有蓝色实心圆Surface的image,参数2忽略不使用,参数3是阈值,参数4是创建另一个Surface,circleRed1,其宽和高和circleRed相同,但仅有一个蓝色实心圆,和circleRed的实心蓝色圆位置相同,颜色也相同。显然,该条语句不能放在第12行位置,因为此时circleRed1还不存在,应放在第28行位置。
circleRed.mask=pygame.mask.from_threshold(circleRed.image,pygame.Color(‘blue’),(1,1,1,255),circleRed1.image)
该方法将逐一将circleRed.image的所有像素值(包括底色)和circleRed1.image的所有对应像素值(包括底色)相减,差值在阈值(1,1,1,255)范围内的像素点,在返回circleRed.mask中设置为1。那么有哪些对应像素点颜色相减呢?首先circleRed.image底色和circleRed1.image底色,它们不应该参加碰撞,因此两个底色颜色要不同,circleRed.image底色为白色(第10行),circleRed1.image底色为黑色(第15行)。然后是circleRed.image红色圆环和circleRed1.image底色,显然大于阈值。最后circleRed.image蓝色和circleRed1.image蓝色,颜色相同,显然小于阈值。如此设置,将只有两圆中位置对应的的蓝色像素点,在返回circleRed.mask中设置为1,参加碰撞检测。
将设置circleyellow.mask语句放在第16行位置,不报错,但不能得到正确的mask,后来放到第29行位置,才能得到正确的mask。
如果减小circleRed1蓝色圆的半径,或移动蓝色圆位置,就会影响参数1的碰撞位置。使用第30、31行语句,可查看mask指定位置的值是1还是0。这是一种程序调试方法。
完整程序如下。效果图和第1例相同。

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))            #创建1个100X100的Surface实例               
        self.radius=radius
        self.width=width        
        if self.width>0:
            self.image.set_colorkey((200,200,200))         #设置image中白色为透明色
            self.image.fill((200,200,200))                 #底色为白色,由于上条,底色变为透明
            pygame.draw.circle(self.image,pygame.Color('blue'),(50,50),25,0)            
            self.mask=None  #大圆mask            
        else:
            self.image.set_colorkey((0,0,0))                #设置image中黑色为透明色
            self.image.fill((0, 0, 0))                      #底色为黑色,由于上条,底色变为透明 
            self.mask=None #pygame.mask.from_surface(self.image)  #在此设置mask不能成功?
        pygame.draw.circle(self.image,pygame.Color(color),(50,50),self.radius,self.width) #在image上画圆
        self.rect = self.image.get_rect(center=pos)         #将image移到指定位置
    def draw(self,aSurface):
        aSurface.blit(self.image,self.rect) #从Sprite派生类不放入Group,类实例显示需调用自定义draw        
pygame.init()
screen = pygame.display.set_mode((300,100))
pygame.display.set_caption("矩形之间碰撞")
clock = pygame.time.Clock()
circleRed=Circle((50,50),'red',45,20)                       #创建大红色圆环内部蓝色圆,Circle实例
circleyellow=Circle((150,50),'yellow',15,0)                   #创建黄色小圆,Circle实例
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:            #按键按下事件
            if event.key==pygame.K_RIGHT:         #按键盘右方向键使球向右
                circleyellow.rect.x+=5                            
            elif event.key==pygame.K_LEFT:        #按键盘左方向键使球向左
                circleyellow.rect.x-=5            
    if pygame.sprite.collide_mask(circleRed,circleyellow):       
        screen.fill((200, 200, 200))               #发生碰撞,窗体背景变灰色 
    else:
        screen.fill((255, 255, 255))               #不发生碰撞,窗体背景白色
    circleRed1.draw(screen)
    circleRed.draw(screen)
    circleyellow.draw(screen)
    clock.tick(10)
    pygame.display.update()
pygame.quit()

以上是关于函数pygame.mask.from_threshold()用阈值确定mask碰撞点原理及使用方法的主要内容,如果未能解决你的问题,请参考以下文章

测开之函数进阶篇・第五篇《递归函数纯函数匿名函数偏函数》

8InfluxDB常用函数聚合函数,count()函数,DISTINCT()函数,MEAN()函数,MEDIAN()函数,SPREAD()函数,SUM()函数

Kotlin函数式编程 ② ( 过滤函数 | predicate 谓词函数 | filter 过滤函数 | 合并函数 | zip 函数 | folder 函数 | 函数式编程意义 )

Kotlin函数式编程 ② ( 过滤函数 | predicate 谓词函数 | filter 过滤函数 | 合并函数 | zip 函数 | folder 函数 | 函数式编程意义 )

1.19.9.函数概览函数引用精确函数引用模糊函数引用函数解析顺序精确函数引用模糊函数引用自定义函数准备工作概述开发指南函数类求值方法标量函数表值函数聚合函数

Kotlin函数式编程 ① ( 函数式编程简介 | 高阶函数 | 函数类别 | Transform 变换函数 | 过滤函数 | 合并函数 | map 变换函数 | flatMap 变换函数 )