通常一个游戏中会有很多角色出现,而这些角色之间的“碰撞”在所难免,例如炮弹是否击中了飞机等。碰撞检测在绝大多数游戏中都是一个必须处理的至关重要的问题。除了角色之间的“碰撞”,在游戏设计中也经常用到侦测角色和指定颜色的碰撞,以及侦测角色和另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()

Python碰撞检测代码毕设 pygame碰撞检测返回值_像素点


在游戏程序设计中,如不同角色有不同颜色,可能希望仅碰到一种颜色角色时发生碰撞。例如有青苹果和红苹果,只希望摘到红苹果。又如飞机大战,蓝色敌机入侵,我方用高射炮射击敌方飞机并派红色飞机出战,高射炮应只击中敌方蓝色飞机等。第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()

Python碰撞检测代码毕设 pygame碰撞检测返回值_ci_02


关于参数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()