之前的工作已经基本上将我方飞机的图形显示工作做的差不多了,这篇博客中我们将开始添加敌方飞机——小型敌机、中型敌机(直升机)和大型敌机(坦克)。新建一个enemy.py文件,导入pygame和random模块,开始编写吧(还是要注意文件编码问题,以后就不再啰嗦了)。
敌方飞机类与我方飞机模块有一定的相似性,但不会左右移动,不会发射子弹等等。小型敌机是敌方飞机中最基本的类型,一击毙命,没有血量、没有出场音效。中型敌机有一定血量,损毁时附带特殊音效。大型敌机血量最多,出场和损毁时都有特殊音效,游戏中中型敌机和大型敌机都将显示血槽。
1、小型敌机类
和我方飞机一样,小型敌机类也是继承自Pygame的精灵类,在初始化过程中需要先调用基类的初始化函数,在加载飞机图像、获取飞机图像尺寸,设置小飞机的移动速度等等:
class SmallEnemy(pygame.sprite.Sprite): def __init__(self, bg_size): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("image/enemy1.png") # 加载敌方飞机图片 self.rect = self.image.get_rect() # 获得敌方飞机的位置 self.width, self.height = bg_size[0], bg_size[1] # 本地化背景图片位置 self.speed = 2 # 设置敌机的速度
在初始化小飞机的位置时,需要稍微做一点工作:
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定义敌机出现的位置 randint(-5 * self.rect.height, -5) # 保证敌机不会在程序已开始就立即出现 )
小飞机在初始化时需要随机指定其位置,其x左边(横向宽度)在(0,width - rect.width)之间,保证飞机能够在整个屏幕的X轴的任何位置出现并且不会越界。其top(纵向高度)在(-5 * rect.height, -5)之间,这里之所以最大值设置为负数(-5),是为了保证程序开始运行时敌机不会立即出现,给用户以反应时间。这里还有一个小细节需要注意,就是如果Python的代码太长,在换行时建议使用小括号来指定范围,不推荐只使用“\”来连接各个行。
2、move()和reset()函数
在定义完小型飞机的数据结构之后,需要定义其移动操作move()函数(成员函数)。敌机的移动函数功能相对单一,只是在屏幕上以固定速度向下移动:
def move(self): # 定义敌机的移动函数 if self.rect.top < self.height: self.rect.top += self.speed else: self.reset()
这里即可体现出speed参数的作用。当然还需要编写一个reset()成员函数来将已经移动出屏幕的敌机进行重置:
def reset(self): # 当敌机向下移动出屏幕时 self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), randint(-5 * self.rect.height, 0) )
3、中型敌机
中型敌机结构与小型敌机类此,区别在于中型敌机的图标、血量以及损毁音效等方面,其中敌机血量以及损毁音效的添加我们在之后的博文中再加以介绍:
class MidEnemy(pygame.sprite.Sprite): def __init__(self, bg_size): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("image/enemy2.png") # 加载敌方飞机图片 self.rect = self.image.get_rect() # 获得敌方飞机的位置 self.width, self.height = bg_size[0], bg_size[1] # 本地化背景图片位置 self.speed = 1 # 设置敌机的速度,应该比小型敌机速度稍慢 self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定义敌机出现的位置 randint(-10 * self.rect.height, -self.rect.height) ) def move(self): # 定义敌机的移动函数 if self.rect.top < self.height: self.rect.top += self.speed else: self.reset() def reset(self): # 当敌机向下移动出屏幕时 self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定义敌机出现的位置 randint(-10 * self.rect.height, -self.rect.height) # 保证一开始不会有中型敌机出现 )
4、大型敌机
大型敌机作为boss级别的存在,其不仅仅具有高血量、特殊出场音效以及损毁音效,其在移动过程中也存在图片切换的特效(与我方飞机尾部喷气特效类似),因此在加载图片时同样需要加载两张图片,以便在主程序显示的过程中进行切换:
class BigEnemy(pygame.sprite.Sprite): def __init__(self, bg_size): pygame.sprite.Sprite.__init__(self) self.image1 = pygame.image.load("image/enemy3_n1.png") # 加载敌方飞机图片,其中大型飞机有帧切换的特效 self.image2 = pygame.image.load("image/enemy3_n2.png") self.rect = self.image1.get_rect() # 获得敌方飞机的位置 self.width, self.height = bg_size[0], bg_size[1] # 本地化背景图片位置 self.speed = 2 # 设置敌机的速度 self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定义敌机出现的位置 randint(-15 * self.rect.height, -5 * self.rect.height) ) def move(self): # 定义敌机的移动函数 if self.rect.top < self.height: self.rect.top += self.speed else: self.reset() def reset(self): # 当敌机向下移动出屏幕时 self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定义敌机出现的位置 randint(-15 * self.rect.height, -5 * self.rect.height) )
5、敌方飞机实例化控制函数
在完成敌机模块enemy.py的构建之后,我们开始在主程序中将敌机实例化并显示在屏幕上,首先在程序开始时导入已经编写好的模块:
import enemy
注意我们的打飞机程序在设计时是设计成有难度分级的,玩家得分越多、需要挑战的难度等级就越高;难度等级越高,所出现的敌机数量就越多,可见敌机的实例数量是随时可能发生变化的,直接实例化具体数量的敌机显然不满足要求,在此我们通过编写一个实例化控制函数来实现敌机对象的动态添加,以小型敌机添加函数为例:
# ====================敌方飞机生成控制函数==================== def add_small_enemies(group1, group2, num): for i in range(num): e1 = enemy.SmallEnemy(bg_size) group1.add(e1) group2.add(e1)
这个函数结构简单,通俗易懂,即将指定个敌机对象添加到精灵组(sprite.group)中。参数group1、group2是两个精灵组类型的形参,用以存储多个精灵对象(敌机)。需要注意的一点是group既然是特定的精灵组结构体,在向其内部添加精灵对象时需要调用其对应的成员函数add(),不能使用列表添加函数append()。至于for循环这些python的基础知识这里就不再赘述
以此类推,编写中型敌机和大型敌机的实例化控制函数:
def add_mid_enemies(group1, group2, num): for i in range(num): e2 = enemy.MidEnemy(bg_size) group1.add(e2) group2.add(e2) def add_big_enemies(group1, group2, num): for i in range(num): e3 = enemy.BigEnemy(bg_size) group1.add(e3) group2.add(e3)
友情提示,所有函数都应该在第一次使用之前进行定义,也就是说以上三个函数都应该定义在main()函数之前,以保证在main()函数中顺利调用。
6、敌机实例化
第一步,在main函数中,在进入while循环之前,调用enemy.py模块,向精灵组中添加敌机精灵对象:
# ====================实例化敌方飞机==================== enemies = pygame.sprite.Group() # 生成敌方飞机组 small_enemies = pygame.sprite.Group() # 敌方小型飞机组 add_small_enemies(small_enemies, enemies, 1) # 生成若干敌方小型飞机 mid_enemies = pygame.sprite.Group() # 敌方小型飞机组 add_mid_enemies(mid_enemies, enemies, 1) # 生成若干敌方中型飞机 big_enemies = pygame.sprite.Group() # 敌方小型飞机组 add_big_enemies(big_enemies, enemies, 1) # 生成若干敌方大型飞机
先调用pygame.sprite.Group()生成精灵组,这里需要生成两种精灵组,一种精灵组用以存储所有敌机精灵(不区分小型中型大型),另一种则是针对不同型号敌机创建不同的精灵组来存储。之所以这样做,是因为不同类型的敌机之前既有共同属性,又有各自的特殊属性,在处理中更为方便。精灵组生成之后调用对应的生成控制函数来向其中添加敌机精灵对象即可(这里先将添加数量均设置为1)。
第二步,显示敌机。绘制小型敌机和中型敌机的方法类似,即将当前精灵组中的所有对象通过for()循环进行索引,并逐个blit()到屏幕对象中,并激活其内部的移动函数使其移动,注意这部分代码应该位于while循环之内:
for each in small_enemies: # 绘制小型敌机并自动移动 each.move() screen.blit(each.image, each.rect)
for each in mid_enemies: if each.active: each.move() screen.blit(each.image, each.rect)
大型敌机在绘制时需要添加图片切换的特效,参照之前我方飞机的尾部喷气特效的实现方法:
for each in big_enemies: # 绘制大型敌机并自动移动 each.move() if switch_image: screen.blit(each.image1, each.rect) # 绘制大型敌机的两种不同的形式 else: screen.blit(each.image2, each.rect)
if each.rect.bottom == -50: big_enemy_flying_sound.play(-1)
这里当大型敌机即将出场时rect.bottom == -50,需要实现播放出场音效,并且是循环播放(参数设置为-1),如上所示。OK,运行代码,就会看到敌方飞机陆续从屏幕上飞过,不过这里应该有一个bug,就是大型敌机出场时不能顺利播放出场音效,这涉及Pygame的一个声道阻塞的问题,我们将在后续的博文中进行总结,今天就先进行到这里吧。