牛骨文教育服务平台(让学习变的简单)

Python编写微信打飞机小游戏(七)

 这边博客我们为大型敌机和中型敌机设置血量并以血槽的形式显示出来,同时解决之前遇到的声道阻塞的BUG(声道阻塞的问题详见之前的博文)。

  1、为敌机添加表示血量的成员变量

  所谓敌机血量,就是指敌机在挂掉之前能够挨几发子弹,这是敌机对象的一个成员属性,因此我们在中型敌机(MidEnemy)和大型敌机(BigEnemy)中添加energy成员变量:

class MidEnemy(pygame.sprite.Sprite):
    energy = 5
    def __init__(self, bg_size):
        ………………
        self.energy = MidEnemy.energy

class BigEnemy(pygame.sprite.Sprite):
    energy = 15
    def __init__(self, bg_size):
        ………………
        self.energy = BigEnemy.energy

  注意这里之所以将energy初始化为类的全局变量以及类对象的成员变量两种形式,是因为在接下来绘制血槽的过程中,需要计算当前血量和总血量的比值,全局energy用以保存总血量(定值),类对象的成员变量energy(随着被击中的次数而递减)表示当前血量,这一点在后面绘制血槽的过程中将再次解释。从代码中可以看出,我们将中型敌机血量设计为承受5发子弹,大型敌机的血量设计为承受15发子弹的数量(皮糙肉厚)。

  当然在对应的reset()函数中需要重置energy变量的值,将其重新设置为满血:

self.energy = MidEnemy.energy

  以及

self.energy = BigEnemy.energy

  2、为中型敌机和大型敌机添加中弹受损图片

  既然中型敌机和大型敌机并不是一击毙命,因此有必要在其中弹时配以特效图片(虽然是5毛特效)以表征该敌机血量正在减少,相关图片资源已经存于image文件夹中,只需在中型敌机和大型敌机的类内部(init()函数中)进行加载:

self.image_hit = pygame.image.load("image/enemy2_hit.png")  # 加载中型敌机中弹图片
self.image_hit = pygame.image.load("image/enemy3_hit.png")  # 加载大型敌机中弹图片

  与此同时,我们需要知道中型敌机和大型敌机在什么时候被击中,以便播放中弹画面,因此需要在中型敌机和大型敌机内部添加一个表示当前飞机是否被击中的标志位:

self.hit = False  # 飞机是否被击中标志位

  并且在对应reset()函数中重置其属性:

self.hit = False

  加载完相关资源后,接下来开始转入主程序模块进行绘制血槽的工作。

  3、定义绘制血槽所用背景颜色

  为了反映当前血量情况,突出剩余血量的比重,我们采用如下的血槽绘制机制:血槽的底色为黑色,当前血量用绿色线条表示,并随energy变量的减少而缩短,当血量低于百分之二十时,血量的显示由绿色变为红色,为了方便对指定颜色的调用,我们在main函数开始部分(while循环之外)对各个颜色进行宏定义:

    color_black = (0, 0, 0)
    color_green = (0, 255, 0)
    color_red = (255, 0, 0)
    color_white = (255, 255, 255)

  稍微注意这里Pygame的彩色分量顺序就是平时大家所熟知的R,G,B。(opencv中为BGR)

  4、绘制血槽

  绘制血槽的操作是在敌方飞机绘制过程中同步进行的,考虑到代码的多层嵌套关系,这里先将绘制血槽的完整代码给出,再作解释:

            for each in big_enemies:  # 绘制大型敌机并自动移动
                if each.active:  # 如果飞机正常存在
                    # 飞机移动move()
                    if not each.hit:  
              # 如果飞机未被击中# 绘制大型敌机的两种不同的形式
            else:
                        screen.blit(each.image_hit, each.rect)
                        each.hit = False

                    # ====================绘制血槽====================
                    pygame.draw.line(screen, color_black,
                                     (each.rect.left, each.rect.top - 5),
                                     (each.rect.right, each.rect.top - 5),
                                     2)
                    energy_remain = each.energy / enemy.BigEnemy.energy
                    if energy_remain > 0.2:  # 如果血量大约百分之二十则为绿色,否则为红色
                        energy_color = color_green
                    else:
                        energy_color = color_red
                    pygame.draw.line(screen, energy_color,
                                     (each.rect.left, each.rect.top - 5),
                                     (each.rect.left + each.rect.width * energy_remain, each.rect.top - 5),
                                     2)
                    if each.rect.bottom == 0:
                       # 播放大型飞机的音效(循环播放)
                else:  # 如果飞机已撞毁
                    pass

  这里代码有点多,我们逐行解释。通过for语句轮询大型敌机精灵组中的每个敌机对象,如果该飞机对象为激活状态(if each.active:),则进行接下来的飞机移动、血槽绘制的工作,否则进入飞机损毁的代码程序,包括播放音效以及损毁图片等等(之前博文已经介绍过)。如果飞机状态为激活状态,则需要判断其内部的“hit”标志位来判断当前飞机是否中弹,如果hit = false,则说明飞机没有中弹,正常绘制飞机的状态即可(大型敌机有帧切换特效,详见之前博文),如果hit = true,则说明飞机当前中弹,需要绘制飞机的中弹图片,同时重置hit标志位。

  接下来是血槽,只要飞机处于激活状态,就需要绘制血槽,而无需考虑飞机当前是否中弹。首先通过pygame.draw.line()函数绘制血槽背景,背景颜色为黑色,线的长度与精灵对象的宽度相等,位置处于精灵图片上方五个像素的位置。之后通过energy_remain = each.energy / enemy.BigEnemy.energy计算当前剩余血量百分比,这里即体现出了之前在定义energy时将其定义为类全局变量和对象局部变量两种形式的优势。

  得到剩余血量比重后,则判断当前剩余血量是否大于百分之二十,如果大于0.2,则血槽颜色为绿色,否色为红色。指定好血槽颜色之后即可再次调用pygame.draw.line()来绘制血槽长度,位置和背景位置相同,但血槽长度需要通过“血槽背景长度(总长度)剩余血量百分比”来获得,即代码中的“each.rect.width energy_remain”,这样当前血槽长度就和当前血量(self.energy)成正比。

  同理,中型敌机的血槽绘制方式和上面大型敌机的绘制方式基本一样的,只需将在计算剩余血量时将“enemy.BigEnemy.energy”改为“enemy.MidEnemy.energy”即可。程序编写到这里,运行将会看到中型敌机和大型敌机都将会顶着一个血槽光环出场了,但这里仍然是一击毙命,原因是我们只是绘制了血槽,还没有真正的对血量(energy)进行操作。

  5、添加碰撞检测血量递减机制

  血量的真正意义是通过中弹递减来体现的,即中弹一次(飞机和子弹发生一次碰撞),energy变量就减一,当energy = 0时,飞机损毁,因此我们需要修改一下之前写的碰撞检测处理函数:

            # ====================子弹与敌机的碰撞检测====================
            for b in bullets:
                if b.active:  # 只有激活的子弹才可能击中敌机
                    # 子弹移动,碰撞检测if enemies_hit:  # 如果子弹击中飞机
                        # 子弹损毁
                        for e in enemies_hit:
                            if e in big_enemies or e in mid_enemies:
                                e.energy -= 1
                                e.hit = True  # 表示飞机已经被击中
                                if e.energy == 0:
                                    e.active = False  # 大中型敌机损毁
                            else:
                                e.active = False  # 小型敌机损毁

  这段代码相对来说容易理解,之前在完成子弹和敌机的碰撞检测后,对于发生碰撞的敌机(enemies_hit),我们直接令e.active = false来销毁敌机。然而在这里我们需要判断,如果碰撞的是中型敌机和大型敌机,则将其energy变量减一,并将hit赋值为true表示中弹,在当energy = 0时才执行销毁操作;若发生碰撞的是小型敌机,那无话可说,一击毙命。

  ok,程序运行到这里应该能够顺利执行,本来还想继续介绍一些关于声道阻塞的BUG解决方案,鉴于内容已经不少了,还是放到下次博文吧。