超人游戏_将障碍画在背景中用pygame.mask.from_threshold实现超人和不同颜色障碍精准碰撞检测

网上有很多超人游戏,例如:超人训练、超人锤练、超人磨练、超人考验、超人飞跃、超人冲刺等。一般用火柴人扮演超人,能跑、能跳、能攀峭壁,能力超强,游戏场面宏大,关卡一个比一个难。但由于只是学习编程,精力应放在用pygame实现超人的跑、跳、攀爬等各种动作上,因此仅设置3个关卡,来验证超人动作代码。查看游戏源代码是不可能的,只能是自己玩或看别人玩游戏,想办法用pygame实现看到的动作。这些游戏都是敏捷类游戏,老人可能玩不了,小孩玩坏眼晴。幸好在哔哩哔哩网站上有一个玩“超人训练”游戏的视频,显示了通关的各个动作,根据所看到动作完成pygame“超人训练”的游戏。读者也可对照视频,查看所做游戏是否实现了“超人训练”游戏的大部分动作。该链接如下:https://www.bilibili.com/video/av370211982/
视频中的超人游戏使用了大障碍图形,在x轴方向障碍图形很长。对于大障碍图形,如每次移动都从大障碍图形取出能充满整个窗口的部分图形作为分障碍图形,那么为了侦测分障碍图形中的障碍,必须为分障碍图形创建mask,这样每移动一次,就要重新创建1次mask,显然不太合理。当然也可将整个大障碍图形作为一个角色,创建大障碍图形的mask,角色移动,实际是大障碍图形的反向移动,该方法优点是在x和y方向可同时移动,实现比较简单,如果关卡不太多,也是一个可行方法,但是如关卡太多,大障碍图形必然很大,这样做可能会影响程序运行速度。还可用传统方法,将大障碍图形分割为多个和窗体等宽的分障碍图形,分障碍图形在y方向可以和窗体等高,这样超人在y方向自己运动。也可象视频中的游戏,分障碍图形比窗体要宽,这样超人在y方向自己也不运动,由分障碍图形在y方向反向移动完成。本例采用分障碍图形在y方向和窗体等高,大障碍图形分割后,所有分障碍图形如下。
在这里插入图片描述
共有5个图形,除去开始和结束图形,共有3个关卡。每个分障碍图形由3种颜色组成,白色为底色,设为透明,超人在黑色障碍上行走,超人碰到红色障碍游戏结束。为了使用分障碍图形,必须在程序中用surface类实例保存分障碍图形,得到并保存该surface类实例的侦测黑色的mask和侦测红色的mask,将所有分障碍图形及其两个mask保存到字典bgs中,这里n是键,是分障碍图形号。值是列表,格式为:[障碍图形的surface类实例,该实例的黑色碰撞mask,该实例的红色碰撞mask]。见第135-139行。其中用pygame.mask.from_threshold创建侦测某种颜色mask,原理请参见本人的博文:函数pygame.mask.from_threshold()用阈值确定mask碰撞点原理及使用方法。
超人有多个造型,每个造型都应有自己的mask。如造型比较多,一般将多个造型集中放到一张图片中,换造型时候从大图中取出相应的造型,这时必须为取出造型创建mask,超人每改变一次造型,就要创建1次mask,比较费时。可一次性分割所有造型,或使用独立的造型图片,并为其创建mask,保存到列表中,方便使用。
本程序超人有7个独立的造型图片,都是面向右侧,超人向右侧移动要使用这7个图片作为造型。超人如向左侧移动,要面向左侧,还需要7个造型,从已有的7个造型图片沿y轴翻转得到。因此共计14个造型。每个造型都要有自己的mask。直接使用这些独立的造型图片,为每个造型创建mask,保存到字典mans中。具体代码见第140-149行。7个造型图片如下:
在这里插入图片描述
超人要不断进行造型变换。如造型变换前未碰到黑色,造型变换后,必须保证造型仍未碰到黑色。这对于超人造型图形设计有一些要求。超人站立(或卧式)图片要等高宽,超人图形中心点在图片中心,超人图形距图片上(下、左或右)边界距离距离一致,在变换后,变换前后造型中心对齐,使到黑色障碍的距离,造型变换前后基本相同。
在视频中显示的游戏,超人动作包括:向左或向右奔跑、爬行、坐着向下滑行、跳跃和攀爬峭壁。用上下左右和空格键变换不同动作。根据这些,本程序设定这5个键用途,左键:超人造型面向左侧,沿x轴向左侧前进;右键:超人造型面向右侧,沿x轴向右侧前进;向下键:超人趴下准备爬行;向上键:超人坐下准备向下滑行;空格键:跳跃,如碰到峭壁攀爬峭壁。因此可以看出,不能同时按下左键和右键。因为爬行、坐着向下滑行和跳跃都需要前进,因此左(或右)键和向上、向下或空格键中某1个键可同时按下。用变量man_state记录超人运动动状态,man_state=1跑步、=2爬行、=3坐下滑、=4跳跃、=5攀爬。为了同时查看某时刻键盘这5个键是否按下,用语句pygame.key.get_pressed()得到所有键按下状态,并保存到列表key_list中(第162行)。change_state(key_list)用来根据玩家所按下键确定超人当前状态(第45行)。这个方法必须在所有有关超人代码的最前边调用(第165行)。在其后先要调用超人奔跑man_Move(key_list)方法(第166行),因该方法和造型无关,换句话说,无论那种造型都要沿x轴方向前进或后退,并且在这个方法中,还要决定是否碰到峭壁,如遇到峭壁,必须改变造型为攀爬峭壁造型。因此在其后才能调用方法change_model(center,key_list)(第167行)改变造型。最后调用manJumpClimbDown(key_list)方法(第170行),完成跳跃、攀爬或下落功能。
下边看方法man_Move(key_list)方法(第64行),该方法使超人向左或向右奔跑,具体向那个方向奔跑,决定于dx是正数还是负数,在方法change_state中第47-52行根据按下左键还是右键来确定为正还是负,同时确定超人面向左侧还是右侧。超人实际并不真正奔跑,其x坐标保持不变,是由障碍背景反向移动来完成(第67行)。超人在黑色障碍上奔跑,其脚总是保持在黑色障碍的的上方,和黑色障碍并不接触,如果是上坡,前进就可能碰到黑色障碍,因此每前进一步,就要检测是否碰到黑色障碍(第68行),如碰到黑色障碍,超人沿y方向上升dy值,脱离和黑色障碍接触。但也可能碰到峭壁,无论上移多少,总是会碰到黑色障碍。这里涉及到判断超人是否遇到峭壁的判据。本程序设定上坡的坡度>45度就是峭壁。因此第2次判断是否碰到黑色障碍为真后(第70行),就认为是峭壁了,超人恢复到移动前的位置,将峭壁标志is_cliff设为真,令man_state=5,表示造型要改为攀爬。因此,障碍背景中不能有>45度的上坡。如果需要较大坡度,可在第2次判断是否碰到黑色障碍为真后,进行第3次判断为真后,判断为峭壁,这时坡度大约为70多度。这里没有超人走下坡的代码,而是使用公用下行的代码,在方法manJumpClimbDown()中(第78行)。
change_model(center,key_list)方法(第14行)根据man_state值修改超人造型,参数1是变换造型前的造型的中心位置,变换后的造型要保持这个位置不变,前边提到,这对造型设计有所要求。修改造型很简单,需要注意的是修改造型后的这种情况,在变换造型前超人未和黑色障碍发生碰撞,造型转换后却发生碰撞。造型设计要设法避免这种情况发生,此种情况发生了,程序要进行纠正。原始造型图片尺寸较大,被缩小3倍(第143行),实际使用的站立造型尺寸变为宽40高60,卧式尺寸变为宽60高40。如从爬行转到奔跑,奔跑中心点高,爬行中心点低,两者造型中心点距下边界距离分别为30和20,差值为10,转换后跑步双腿一定会碰到黑色,超人最多上行10,就能避免碰到黑色障碍。第35-39行语句能够解决造型从爬行转跑步产生碰撞问题,即使超人上行过多,第94-99行自动下行程序也能使超人距离黑色障碍不大于dy。如果程序循环3次,上行15,超人仍然和黑色发生碰撞,就会执行第40行语句,说明这次转换不是从爬行转奔跑,而是从奔跑转爬行,爬行造型宽度大于奔跑造型宽度,转换后爬行造型比变换前奔跑造型中心点距左(或右)边界距离多出10,宽度增加使超人在左侧,也许是在右侧碰到黑色障碍,超人先右移10,仍然发生碰撞,再左移20,使碰撞消失。
最后介绍manJumpClimbDown(key_list)方法,将完成跳跃、攀爬和下落功能。先介绍跳跃功能。如当前不是正在跳跃,jump_mark=True,表示允许跳跃。玩家按空格键,在change_state方法中,将令man_state=4(第58行)。执行change_model方法后,将令jump_hight=20(第28行),将使超人在20帧内每帧都上行dy,令jump_mark=False(第29行),在完成本次跳跃前,不允许再修改jump_hight。在方法manJumpClimbDown中,如jump_hight>0,超人上行,直到jump_hight=0或碰到黑色障碍。如跳跃时左右键都未按下,跳跃直上直下,如左键或右键按下,超人跳跃同时还向左或右移动。如碰到峭壁(第88行),将变为攀爬造型,按住空格键,超人沿峭壁连续上升,松开空格键,超人沿峭壁连续下降,直到碰到黑色障碍。不是以上两种情况,超人自动下降,直到碰到黑色障碍(第94-99行)。
bump(what_color)方法是检测是否和参数1指定颜色发生碰撞,发生碰撞返回真,否则返回假。因为在窗体中同时存在两个分障碍背景,所以必须检测超人是否和两个分障碍背景中的障碍是否发生碰撞。侦测和颜色发生碰撞,使用pygame.mask.from_threshold创建侦测某种颜色mask方法。
另外当程序运行后,玩家控制超人运动,在超人左上角有个绿色小圆,这是用来调试的。从前面的描述可知,程序设计目的之一是在屏幕显示的超人,不能碰到黑色障碍且距离黑色障碍非常近处,向左或向右奔跑、向上攀爬或沿峭壁落下。但是有各种原因可能达不到这个目的,使超人显示时碰到黑色障碍。由于超人奔跑、攀爬时距离黑色障碍太近,程序员也许不能准确判断在屏幕显示的超人是否碰到黑色障碍。为此在显示超人前(第177行),增加了第171-174行的代码,判断超人是否和黑色发生碰撞,未发生碰撞,超人左上为绿色圆,否则超人左上为蓝色圆,看到蓝色圆,说明和黑色障碍发生碰撞,要检查原因。最后程序应去掉这些代码。
在第130行取出蓝天白云图片保存为background。第157行是将窗体背景设置为白色,但被注释掉。第158行将窗体背景设置为蓝天白云。而障碍背景bg0-bg4的白色都设置为透明色,这样在这些障碍背景的白色部分都能看到蓝天白云。蓝天白云图形并不参加碰撞检测,对程序运行没有影响,只是增加游戏的可观赏性或使游戏看起来更加真实。运行效果如下,为减少GIF文件尺寸,没加蓝天白云背景。
在这里插入图片描述
完整程序如下。可能有不合理的地方,欢迎批评指正。

import pygame

def bump(what_color):   #如果what_color='red'(或='black'),检查超人是否和左背景和右背景中红(黑)色发生碰撞
    global bg_No,man_No,man_rect,bg_x
    offset = man_rect.x - bg_x, man_rect.y          #超人和左背景差,被减数和减数次序不能交换
    offset1= man_rect.x - (bg_x+640), man_rect.y    #超人和右背景差,注意背景y坐标=0
    if what_color=='red':                           #bgs和mans的数据结构见第137行和142行
        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)
#根据man_state(超人状态)改变造型,参数1是超人中心坐标,造型改变,其中心坐标不变
def change_model(center,key_list):                  #参数2记录键盘那些键按下
    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为跑步状态
        if key_list[pygame.K_LEFT] or key_list[pygame.K_RIGHT]:         #如左或右键按下
            man_No+=1       #跑步有4个造型,一帧后变为下1造型。每个跑步造型图片矩形应等宽高,
            if man_No>3:    #>3,从0开始。矩形和超人中心点要重合,所有超人图距矩形上下左右边界最后相同,
                man_No=0                #避免原未发生碰撞,造型转换后却发生碰撞
        elif man_No>3:            
            man_No=0    #man_No是造型号
    elif man_state==2:  #man_state=2为卧倒状态,只有一个造型,也可增加造型
        man_No=4
    elif man_state==3:  #man_state=3为坐下滑状态,只有一个造型,也可增加造型
        man_No=5          
    elif man_state==4 and jump_mark and not(is_cliff):#如为跳跃状态(=4)同时允许上跳且不是攀爬状态
        jump_hight=20   #如允许跳跃,首先开始上跳,每帧增加dy,共20帧到达上跳顶点,然后开始下落
        jump_mark=False #正在跳跃,不允许修改jump_hight,跳跃完成,使jump_mark=True,允许再次跳跃
    elif man_state==5:  #攀爬状态
        man_No=6
    man_image=mans[man_No][man_face]  #man_No是造型号,man_face是超人面向那个方向,方向不同造型也不同
    man_mask=mans[man_No][man_face+1] #该造型的mask
    man_rect=man_image.get_rect(center=center) #记录造型位置和宽高的Rect类实例,造型改变中心位置不变   
    for n in range(3):        #首先假设从爬行转到跑步,跑步中心点高,爬行低,转换后跑步双腿到黑色中
        if bump('black'):     #超人必须上移直到不和黑色碰撞。爬行和跑步中心点到下边界分别为20、30   
            man_rect.centery-=5  #超人需上移10,这里循环3次,上行15,如上行过多,
        else:                    #第94-99行语句会自动将超人爬行造型下降到句黑色障碍<dy
            return
    if bump('black'):  #到此一定不是从爬行转奔跑,而是从奔跑转爬行。爬行造型宽度大于奔跑宽度,碰到黑色
        bg_Move(-10)   #转换后爬行造型比变换前奔跑造型中心点距左(或右)边界距离多出10,超人先右移10
        if bump('black'): #仍产生碰撞,超人左移20
            bg_Move(20)

def change_state(key_list):         #改变超人运动状态,man_state=1奔跑,=2爬行,=3坐下滑,=4跳跃,5=攀爬
    global man_face,dx,man_state,bg_No,is_cliff    
    if key_list[pygame.K_LEFT]:     #如左键按下
        man_face=2                  #超人面向移动方向
        dx=abs(dx)                 #超人右移,实际上是背景左移
    elif key_list[pygame.K_RIGHT]:
        man_face=0
        dx=-abs(dx)
    if key_list[pygame.K_DOWN]:     #按下下箭头键,爬行
        man_state=2            
    elif key_list[pygame.K_UP]:     #按下上箭头键,坐着下滑
        man_state=3          
    elif key_list[pygame.K_SPACE]:  #跳跃
        man_state=4
    elif is_cliff:                  #遇到峭壁标记,在man_Move(key_list)方法中判定遇到峭壁
        man_state=5
    else:                           #奔跑
        man_state=1

def man_Move(key_list):         #超人沿x轴方向左右移动
    global dx,dy,is_cliff,man_rect,man_No,man_state
    if key_list[pygame.K_LEFT] or key_list[pygame.K_RIGHT]:     #如左或右键按下,超人沿x轴方向移动        
        bg_Move(dx)#超人沿x方向移动,实际不动,而是背景反向移动,注意dx在change_state方法中确定了方向
        if bump('black'):                           #如超人沿x方向移动碰到黑色
            man_rect.centery-=dy                    #超人上行dy,期望不再碰到黑色
            if bump('black'):                       #如还碰到黑色,认为碰到峭壁,设置is_cliff=True                
                bg_Move(-dx)                        #超人退回原位置,如此设置,要求上行坡度<45度
                man_rect.centery+=dy                #如再增加1个if bump('black'):,上行坡度<70度
                is_cliff=True
                man_state=5                
        else:             
             is_cliff=False             

def manJumpClimbDown(key_list):    #跳跃、攀爬和下落功能方法
    global dy,man_state,is_cliff,man_y,jump_mark,jump_hight,man_rect    
    if jump_hight>0:#实现跳跃,如此时左右键未按下,跳跃直上直下,如按下,象跳远那样前进
        man_rect.centery-=dy         #每帧减少dy
        if man_rect.y<=0 or bump('black'):  #如碰到上边界或黑色
            man_rect.centery+=5     #保持原位置
            jump_hight=0            #上跳结束,
        else:
            jump_hight-=1           #每帧上跳dy,jump_hight减1,直到为0,跳跃上行结束
    else:                           #不是跳跃,那么可能是下行或攀爬
        if key_list[pygame.K_SPACE] and man_rect.y>0 and is_cliff:  #满足这些条件是攀爬
            man_state=5             #攀爬状态号为5
            change_model(man_rect.center,key_list) #变为攀爬造型
            man_rect.centery-=5     #攀爬上行
            if man_rect.y<=0 or bump('black'):  #如碰到上边界或黑色
                man_rect.centery+=5             #保持原位置
        else:                       #到此,超人自动下落,直到碰到黑色           
            man_rect.centery+=5
            if bump('black'):    
                man_rect.centery-=5
                jump_mark=True        
                is_cliff=False

def initialization():           #初始化程序
    global is_cliff,jump_mark,jump_hight,man_state,bg_x,bg_No,man_No,man_face,bgs,mans,key_list
    is_cliff=False              #超人是否到峭壁
    jump_mark=True              #是否允许超人跳跃
    jump_hight=0    #超人上跳,设置为允许上跳最大高度,每1帧减1,=0,上行结束,开始跳跃的下行
    man_state=1     #=1跑步,=2爬行,=3坐下滑,=4跳跃,5=攀爬
    bg_x=-330       #左障碍背景初始x坐标,其y=0
    bg_No=0         #左障碍背景号
    man_No=0                  #超人当前使用的造型编号
    man_face=0                #=0,超人面向右侧,=2超人面向左侧
    key_list = pygame.key.get_pressed()
    change_model((330,200),key_list)

def bg_Move(dx):              #背景移动,相当于超人反向移动,dx是移动增量,正向右,负向左
    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("检测矩形和颜色的碰撞")
screen = pygame.display.set_mode(size)
background=pygame.image.load("蓝天白云图.png").convert_alpha()
dx=5                      #背景移动速度,超人沿x轴方向不动
dy=5                      #超人沿y轴方向移动速度
bgs={}                    #保存所有障碍背景
mans={}                   #保存所有超人造型
for n in range(5):        #将所有背景图片和两个mask保存到字典bgs
    bg=pygame.image.load("bg"+str(n)+".png").convert_alpha()   #背景图像    
    maskBlack=pygame.mask.from_threshold(bg,pygame.Color('black'),(1,1,1,255))#移动圆只有碰到黑色,才产生碰撞
    maskRed=pygame.mask.from_threshold(bg,pygame.Color('red'),(1,1,1,255))#移动圆只有碰到红色,才产生碰撞
    bgs[n]=[bg,maskBlack,maskRed]#n是键,背景号。值是列表,[背景图片,该图片和黑色碰撞mask,该图片和红色碰撞mask]
for m in range(7):        #将超人所有造型即其mask保存到字典mans
    man=pygame.image.load("人"+str(m+1)+".png").convert_alpha()   #超人原图形为120*180和180*120两种 
    r=man.get_rect()                                    #下句将图形缩小3倍,实际使用超人图形为40*60和60*40两种
    man=pygame.transform.scale(man,(int(r.width//3),int(r.height//3)))
    #man.set_colorkey((255,255,255))             #设置白色为透明色,即造型底色为透明白色
    man1=pygame.transform.flip(man,True,False)  #得到反向对称超人造型
    #man1.set_colorkey((255,255,255))
    mask0=pygame.mask.from_surface(man)         #得到两个造型的mask
    mask1=pygame.mask.from_surface(man1)
    mans[m]=[man,mask0,man1,mask1]              #将两个不同方向造型及其mask保存到mans字典
initialization()                                #初始化方法
fclock = pygame.time.Clock()
fps = 15
running = True
while running:
    if bump('red'):
        initialization()
    #screen.fill(pygame.Color('white'))   #真正背景可以是单色
    screen.blit(background,(0,0))         #也可以是一幅图片,仅增加游戏逼真度,不参加碰撞检测  
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False        
    key_list = pygame.key.get_pressed() #key_list记录键盘那些键按下
    if key_list[pygame.K_r]:     #如r键按下,重玩
        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))            #绘制两个障碍背景
    screen.blit(bgs[bg_No+1][0],(bg_x+640,0))
    manJumpClimbDown(key_list)
    color1=pygame.Color('green')                    #以下4句,调试用
    if bump('black'):
        color1=pygame.Color('blue')                 #超人如和黑色发生碰撞,超人左上有蓝圆
    pygame.draw.circle(man_image,color1,(5,5),5)    #否则为绿色圆,最后程序要删除这4条语句
    if man_rect.y<=0:                               #避免超人跃出上边界
        man_rect.y=1
    screen.blit(man_image, man_rect)    
    pygame.display.update()    
    fclock.tick(fps)
pygame.quit()
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值