大家好!我是近视的脚踏实地,虽然近视,但是脚踏实地。这一篇继续要完善飞机大战的游戏,这篇主要完成的内容是完成玩家飞机普通子弹和中大型敌机血槽的代码
上一章完成了完美检测碰撞的功能,这章来完成玩家的普通子弹,子弹有两种:一种是一次只发射一颗,一种是补给发放的超级子弹,一次可以射2颗子弹。子弹的运动轨迹是直线向上的,速度要略快于飞机的速度,子弹如果超出屏幕尽头的话,就是当子弹对象的rect矩形对象的top小于0的话,那么我们就重新绘制子弹,飞机在哪里,子弹就重新绘制在哪里,击中敌机的时候,子弹也是需要重新绘制的。
那么子弹类和飞机类一样的,也需要一个active属性,通过该属性来判断该子弹是否需要重新绘制,子弹也单独定义为一个模块,下面开始走起
bullet.py
# bullet.py
import pygame
#普通子弹
class Bullet1(pygame.sprite.Sprite):
def __init__(self,position):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("images/bullet1.png").convert_alpha()
self.rect = self.image.get_rect()
self.rect.left,self.rect.top = position
self.speed = 12
self.active = True
self.mask = pygame.mask.from_surface(self.image)
def move(self):
self.rect.top -= self.speed
if self.rect.top < 0:
self.active = False
def reset(self,position):
self.rect.left,self.rect.top = position
self.active = True
代码解析:同样的先把pygame模考导入接着class Bullet1(pygame.sprite.Sprite): 首先来定义个普通子弹的类,后面还会两个子弹的情况,这个类同样要继承Sprite这个类,待会要做碰撞检测。
接着 def init(self,position): 来初始化子弹的状态了,那么如先来初始化pygame模块里Sprite类的init方法,这个init方法相当于其他编程语言的构造方法,self相当于this指针。
接着self.image = pygame.image.load(""images/bullet1.png).convert_alpha() 来加载子弹的图片,同样通过带alpha通道的来转换,执行后就得到了一个子弹图片的Surface对象了
接着self.rect = self.image.get_rect() 就是获取子弹图片Surface对象的rect对象,描述图片位置信息用的。
接着 self.rect.left,self.rect.top = position 初始化子弹的位置,那就是等于传进来的positon,那待会实例化这个子弹类时传进来的position就应该是飞机矩形对象的中央上方发射
接着self.speed = 12 设置飞机速度为12,略大于飞机的速度,接着self.active = True 给子弹设置active的属性,默认为True,待会根据这个属性来判断是否重绘制子弹
接着self.mask = pygame.mask.from_surface(self.image) 因为待会要进行一个判断,要判断这个子弹跟敌机是否发生碰撞,然后要进行一个完美检测,所以需要一个mask属性,上一章也讲过了,那么就是调用mask模块里边的from_surface方法,将传进来的surface对象的非透明部分做以下标志,做一下mark,然后返回给mask属性,后边调用collide_mask方法,他就会自动对比两个精灵的mask部分是否发生碰撞,而不是整个矩形部分
接着def move(self): 就是来定义子弹的移动的方法,子弹的移动非常简单,就修改子弹矩形对象的top坐标就可以了,因为子弹是一直往上走的所以self.rect.top -= self.speed ,那么 if self.rect.top < 0: 如果说子弹的位置小于0的话,就说明跑到屏幕上方看不见的位置了,那么self.active = False 把它的active属性设置为False,说明他需要重新绘制
接着def reset(self,position): 就是当子弹的active属性为False的时候,来调用这个reset()方法,相当于在一次初始化,self.rect.left,self.rect.top = position 然后self.active = True 把它的active属性再次设置为True。
代码解析:那么首先import bullet 把刚刚写好的模块导入进来,接着就是在生成飞机之后就来生成子弹,那么bullet1 = [] 先来一个列表用于把生成的子弹都放到这个bullet1列表里边去
然后从上节课生成飞机的经验我们可以知道,一个一个生成我们需要从这个bullet1列表里边里边索引,所以我们先bullet1_index = 0给他一个用来索引列表的变量
接着BULLET1_NUM = 4 来个宏定义,定义总共有4颗子弹,因为我们发现只用4颗子弹在加上他这个速度刚好就完成了屏幕大约80%的射程
接着for i in range(BULEET1_NUM): 就是依次生成子弹,实例化子弹对象,bullet1.append(me.rect.midtop)) 把实例化后的子弹都添加到bullet1 = [] 列表 ,那么实例化他需要一个position变量,那么子弹生成的位置应该是飞机顶部中央,me 就是玩家的实例化对象,这个rect矩形类他有定于很多个索引的方式,这里midtop 就是表示顶部中央的意思,比如之前还有center_x,center_y 分别x正中央的坐标,y正中央的坐标
接下来就来设置每十帧就发射一颗子弹,你不能同时发,同时发射先只显示为一颗子弹了,他就是重叠起来了,那么我们就在绘制敌机之前来把子弹绘制出来,然后后边在检测会不会撞到敌机。
那么就是if not(delay % 10): 每十帧,就调用一次,这里意思就是说只又delay 的值是能够被10整除的,那么才会执行这里边的内容,那么就是每一次while循环,这个delay就减1,前边为了延迟玩家飞机图片切换,突突突效果更明显已经用过了延迟的小技巧,已经把delay设置为100了,每次减1,减到0又试设置为100。那么一次while循环就是一帧嘛。
接着就是bullet1[bullet1_index].reset(me.rect.midtop) 这里就用到了刚刚的索引变量了,一个个子弹从bullet列表中索引出来生成出来,然后子弹是要随着玩家的操作不断地移动的,所以呢,每次reset就把玩家当前这个时候的位置,给他传进去,传给这个position,然后reset之后,他就会设置这个子弹的位置位于这个飞机顶端的中央。接着bullet1_index =(bullet1_index + 1) % BULLET1_NUM 需要把bullet1_index指向下一个索引,比如说现在是1,那么就指向2,2的话就变成3,那最多不能超过4,所以 % BULLET1_NUM 就可以完成了
接下来我们还需要检测每个子发射出去的子弹会不会击中敌机,如果击中敌机的话,敌机的active属性就要设置为False,子弹的active属性也是设置为False
那么for b in bullet1: 就从装了子弹实例化对象的列表中迭代遍历来索引,然后if b.active: 只有活动的子弹才可能去击中敌机,active为False 的实例化对象就忽略,接着b.move() 让子弹开始飞,然后screen.blit(b.image,b.rect) 把子弹画出来
接着enemy_hit = pygame.sprite.spritecollide(b,enemies,False,pygame.sprite.collide_mask) 就是来看看当前这个子弹b是否跟存了所有敌机的列表的任何敌机发生碰撞,这个列表前边生成敌机的时候已经定义好了,而且是存放了大中小型的都有,第三个参数False就表示不用把跟b发生碰撞的敌机从enemies列表中删除,最后一个参数就是指定了是检测mask部分是否发生碰撞,即完美的碰撞检测
接着if enemy_hit: 刚刚那个方法会把和b碰撞的敌机以列表的形式返回,这里就来判断里边是否有敌机,有就b.active = False ,首先打中了,我这个子弹就没了,接着 for e in enemy_hit: 遍历这个列表,把这个列表的所有敌机的active属性设置为False
那么先在的情况是,对于中型和大型敌机,我们一颗子弹就可以把他们秒杀了,那就显得敌机也太脆弱了,所以接下来先给他们添加一个energy的属性,这个属性就是显示他们还有多少生命值,俗话讲就是还有多少血,就是打一下减一滴血,打两下减两滴血,如果有8滴血就可以打8下,打完8滴血他才会死
enemy.py
代码解析:这里是需要定义成类的属性,因为等等要在外面用到它,在mian里边也会用到,所以定义为类的全局变量,那么这里就是中型飞机要打8下才会毁灭,接着self.energy = MidEnemy.energy 在__init__里边给self.energy赋值,然后是要用类的形式来引用,然后reset方法里边也同样来重新给他加上生命值
接着大型敌机生命值给20,下面同样也要来赋值,reset方法也要来修改,这里就不赘述了。
那么就是每当中、大型敌机被子弹击中的时候,我们先将他们对应的energy属性的值减一,一直减,减到energy的值为0的时候,我们才让敌机给毁灭
那么就是在敌机被击中时,就来f e in mid_enemies or big_enemies: 来判断是否是中大型敌机,是的话就e.energy -= 1 减1生命值,然后if e.energy == 0 来判断他们的生命值是否等于0,是的话e.active = False 就把他们的active属性设置为False,另一种情况就是小型敌机了,直接设置他的active属性为False就可以了
关于上面的mid_enemies 和 big_enemies 这两个列表,因为前面在创建敌机的时候,他都会把新创建的敌机加入到两个group中,一个是加入到他自己的group,就是这个mid_enemies ,另一个就是加入一个存着所有敌机的enemies列表
接下来我们可以为中大型敌机添加一个血槽的显示功能,这样我们可以更直观的让玩家知道敌机还剩多少生命。
那我们就在绘制中大型敌机这里需要进行一个修改,就是在绘制完敌机的时候,我们就来绘制一下他的血槽,那其实血槽就是一条直线,那么我们可以先绘制一条黑色的直线,然后在上面绘制一条绿色的直线,黑色的长度是总共血槽的长度,而绿色的长度表示当前他剩下的energy生命值
那么就先去上面定义一下需要用到的各种颜色,接着pygame.draw.line(screen,BLACK, (each.rect.left,each.rect.top - 5), (each.rect.right,each.rect.top - 5),2) 调用pygame的draw模块的line,画直线的方法,第一个参数就是指定画在screen 对象上,第二个参数就指定画的颜色,第三个参数就是画的直线的开始位置,那么就是这个飞机矩形对象左上角往上大概5个像素的距离,第四个参数就是画的直线的结束位置,就是飞机矩形对象右上角往上大概5个像素的点 ,连起来就是一条直线啦,然后第五个参数再来设置一下直线的宽度为2
那么接着energy_remain = each.energy / enemy.BigEnemy.energy 先来计算还剩多少生命值 的比例,就是当前的生命值除以 enemy模块的BigEnemy类的类变量energy,然后if energy_remain > 0.2: 来判断,如果大于20%就显示绿色,否则显示红色
接着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) 首先第三个参数是一样的,起点都是一样,然后血条的右边的终点我们就要算算比例了,那就是起点开始each.rect.left ,然后再加上还有多少血,each.rect.width *energy_remain,因为each.rect.width 飞机矩形的宽度刚好就是整个血条的长度,拿来乘上边计算得到还剩的比例,就可以得到绿色或者红色还要话多长了, 然后这只是x坐标的,还有y坐标的each.rect.top - 5,不变,还剩飞机矩形往上5个像素,宽度也还是2个像素
那么接着中型飞机的也是类似的,记得把相应变量名改正就会了,这里就不赘述了
测试结果:
那么当中型敌机和大型敌机,被子弹击中,但并不至于毁灭的时候,他们应该是要有特效的,表示他被我们打中了,那么就先在enemy模块中,为这两个类加载特效图片
接着我们还需要一个hit属性,来表示他是否被击中,然后检测这个属性就知道他是否被击中了,默认情况下是没有被击中的,然后中型和大型都要加上
修改main.py
那么就是在子弹击中大型和中型飞机的时候,把它的hit属性改为True,就表示他被打到了
被打到的话,接着在绘制图片的时候就应该区分开了,那么就是if each.hit: 如果hit属性为True,就绘制被打到的特效,else的话就绘制他们的正常形态,那么绘制出被打到的特效后,就又把它的hit属性设置为False,下次他就不会进这里了,而是进到下面正常的绘制,就表示他在被打中的那一刹那,他才会画这个特效,然后下边中型飞机也是一样的道理,这里就不赘述了
测试结果:
enemy.py
#enemy.py
import pygame
from random import *
#小型敌机
class SmallEnemy(pygame.sprite.Sprite):
def __init__(self,bg_size):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("images/enemy1.png").convert_alpha()
self.destroy_images = []
self.destroy_images.extend([\
pygame.image.load("images/enemy1_down1.png").convert_alpha(), \
pygame.image.load("images/enemy1_down2.png").convert_alpha(), \
pygame.image.load("images/enemy1_down3.png").convert_alpha(), \
pygame.image.load("images/enemy1_down4.png").convert_alpha() \
])
self.rect = self.image.get_rect()
self.width,self.height = bg_size[0],bg_size[1]
self.speed = 2
self.active = True
self.rect.left,self.rect.top = \
randint(0, self.width - self.rect.width),\
randint(-5 * self.height, 0)
self.mask = pygame.mask.from_surface(self.image)
def move(self):
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.reset()
def reset(self):
self.active = True
self.rect.left,self.rect.top = \
randint(0, self.width - self.rect.width),\
randint(-5 * self.height,0)
#中型敌机
class MidEnemy(pygame.sprite.Sprite):
energy = 8
def __init__(self,bg_size):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("images/enemy2.png").convert_alpha()
self.image_hit = pygame.image.load("images/enemy2_hit.png").convert_alpha()
self.destroy_images = []
self.destroy_images.extend([\
pygame.image.load("images/enemy2_down1.png").convert_alpha(), \
pygame.image.load("images/enemy2_down2.png").convert_alpha(), \
pygame.image.load("images/enemy2_down3.png").convert_alpha(), \
pygame.image.load("images/enemy2_down4.png").convert_alpha() \
])
self.rect = self.image.get_rect()
self.width,self.height = bg_size[0],bg_size[1]
self.speed = 1
self.active = True
self.rect.left,self.rect.top = \
randint(0, self.width - self.rect.width),\
randint(-10 * self.height,-self.height)
self.mask = pygame.mask.from_surface(self.image)
self.energy = MidEnemy.energy
self.hit = False
def move(self):
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.reset()
def reset(self):
self.active = True
self.energy = MidEnemy.energy
self.rect.left,self.rect.top = \
randint(0, self.width - self.rect.width),\
randint(-10 * self.height,-self.height)
#大型敌机
class BigEnemy(pygame.sprite.Sprite):
energy = 20
def __init__(self,bg_size):
pygame.sprite.Sprite.__init__(self)
self.image1 = pygame.image.load("images/enemy3_n1.png").convert_alpha()
self.image2 = pygame.image.load("images/enemy3_n2.png").convert_alpha()
self.image_hit = pygame.image.load("images/enemy3_hit.png").convert_alpha()
self.destroy_images = []
self.destroy_images.extend([\
pygame.image.load("images/enemy3_down1.png").convert_alpha(), \
pygame.image.load("images/enemy3_down2.png").convert_alpha(), \
pygame.image.load("images/enemy3_down3.png").convert_alpha(), \
pygame.image.load("images/enemy3_down4.png").convert_alpha(), \
pygame.image.load("images/enemy3_down5.png").convert_alpha(), \
pygame.image.load("images/enemy3_down6.png").convert_alpha() \
])
self.rect = self.image1.get_rect()
self.width,self.height = bg_size[0],bg_size[1]
self.speed = 1
self.active = True
self.rect.left,self.rect.top = \
randint(0, self.width - self.rect.width),\
randint(-15 * self.height,-5 * self.height)
self.mask = pygame.mask.from_surface(self.image1)
self.energy = BigEnemy.energy
self.hit = False
def move(self):
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.reset()
def reset(self):
self.active = True
self.energy = BigEnemy.energy
self.rect.left,self.rect.top = \
randint(0, self.width - self.rect.width),\
randint(-15 * self.height,-5 * self.height)
main.py
import pygame
import sys
import traceback
import myplane
import enemy
import bullet
from pygame.locals import *
pygame.init()
pygame.mixer.init()
bg_size = width,height = 480,700
screen = pygame.display.set_mode(bg_size)
pygame.display.set_caption("飞机大战 -- Monster ZF")
background = pygame.image.load("images/background.png").convert()
BLACK = (0,0,0)
GREEN = (0,255,0)
RED = (255,0,0,0)
WHITE = (255,255,255)
# 载入游戏音乐
pygame.mixer.music.load("sound/game_music.ogg")
pygame.mixer.music.set_volume(0.2)
bullet_sound = pygame.mixer.Sound("sound/bullet.wav")
bullet_sound.set_volume(0.2)
bomb_sound = pygame.mixer.Sound("sound/use_bomb.wav")
bomb_sound.set_volume(0.2)
supply_sound = pygame.mixer.Sound("sound/supply.wav")
supply_sound.set_volume(0.2)
get_bomb_sound = pygame.mixer.Sound("sound/get_bomb.wav")
get_bomb_sound.set_volume(0.2)
get_bullet_sound = pygame.mixer.Sound("sound/get_bullet.wav")
get_bullet_sound.set_volume(0.2)
upgrade_sound = pygame.mixer.Sound("sound/upgrade.wav")
upgrade_sound.set_volume(0.2)
enemy3_fly_sound = pygame.mixer.Sound("sound/enemy3_flying.wav")
enemy3_fly_sound.set_volume(0.2)
enemy1_down_sound = pygame.mixer.Sound("sound/enemy1_down.wav")
enemy1_down_sound.set_volume(0.1)
enemy2_down_sound = pygame.mixer.Sound("sound/enemy2_down.wav")
enemy2_down_sound.set_volume(0.2)
enemy3_down_sound = pygame.mixer.Sound("sound/enemy3_down.wav")
enemy3_down_sound.set_volume(0.5)
me_down_sound = pygame.mixer.Sound("sound/me_down.wav")
me_down_sound.set_volume(0.2)
def add_small_enemies(group1,group2,num):
for i in range(num):
e1 = enemy.SmallEnemy(bg_size)
group1.add(e1)
group2.add(e1)
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)
def main():
pygame.mixer.music.play(-1)
#生成玩家飞机
me = myplane.MyPlane(bg_size)
#生成所有敌机汇总
enemies = pygame.sprite.Group()
#生成小型敌机
small_enemies = pygame.sprite.Group()
add_small_enemies(small_enemies,enemies,15)
#生成中型敌机
mid_enemies = pygame.sprite.Group()
add_mid_enemies(mid_enemies,enemies,4)
#生成大型敌机
big_enemies = pygame.sprite.Group()
add_big_enemies(big_enemies,enemies,2)
#生成普通子弹
bullet1 = []
bullet1_index = 0
BULLET1_NUM = 4
for i in range(BULLET1_NUM):
bullet1.append(bullet.Bullet1(me.rect.midtop))
clock = pygame.time.Clock()
# 中弹图片索引
e1_destroy_index = 0
e2_destroy_index = 0
e3_destroy_index = 0
me_destroy_index = 0
#用于切换图片
switch_image = True
#用于延迟
delay = 100
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
#检测用户的键盘操作
key_pressed = pygame.key.get_pressed()
if key_pressed[K_w] or key_pressed[K_UP]:
me.moveUp()
if key_pressed[K_s] or key_pressed[K_DOWN]:
me.moveDown()
if key_pressed[K_a] or key_pressed[K_LEFT]:
me.moveLeft()
if key_pressed[K_d] or key_pressed[K_RIGHT]:
me.moveRight()
screen.blit(background,(0,0))
# 发射子弹
if not(delay % 10):
bullet1[bullet1_index].reset(me.rect.midtop)
bullet1_index =(bullet1_index + 1) % BULLET1_NUM
# 检测子弹是否击中敌机
for b in bullet1:
if b.active:
b.move()
screen.blit(b.image,b.rect)
enemy_hit = pygame.sprite.spritecollide(b,enemies,False,pygame.sprite.collide_mask)
if enemy_hit:
b.active = False
for e in enemy_hit:
if e in mid_enemies or e in big_enemies:
e.hit = True
e.energy -= 1
if e.energy == 0:
e.active = False
else:
e.active = False
# 绘制大型敌机
for each in big_enemies:
if each.active:
each.move()
if each.hit:
#绘制被打到的特效
screen.blit(each.image_hit,each.rect)
each.hit = False
else:
if switch_image:
screen.blit(each.image1,each.rect)
else:
screen.blit(each.image2,each.rect)
# 绘制血槽
pygame.draw.line(screen, BLACK, \
(each.rect.left, each.rect.top - 5), \
(each.rect.right, each.rect.top - 5), \
2)
# 当生命大于20%显示绿色,否则显示红色
energy_remain = each.energy / enemy.BigEnemy.energy
if energy_remain > 0.2:
energy_color = GREEN
else:
energy_color = RED
pygame.draw.line(screen, energy_color, \
(each.rect.left, each.rect.top - 5), \
(each.rect.left + int(each.rect.width * energy_remain), \
each.rect.top - 5), 2)
#即将出现在画面中,播放音效
if each.rect.bottom == -50:
enemy3_fly_sound.play(-1)
else:
# 毁灭
if not(delay % 3):
if e3_destroy_index == 0:
enemy3_down_sound.play()
screen.blit(each.destroy_images[e3_destroy_index], each.rect)
e3_destroy_index = (e3_destroy_index + 1) % 6
if e3_destroy_index == 0:
enemy3_fly_sound.stop()
each.reset()
# 绘制中型敌机:
for each in mid_enemies:
if each.active:
each.move()
if each.hit:
screen.blit(each.image_hit, each.rect)
each.hit = False
else:
screen.blit(each.image,each.rect)
# 绘制血槽
pygame.draw.line(screen, BLACK, \
(each.rect.left, each.rect.top - 5), \
(each.rect.right, each.rect.top - 5), \
2)
# 当生命大于20%显示绿色,否则显示红色
energy_remain = each.energy / enemy.MidEnemy.energy
if energy_remain > 0.2:
energy_color = GREEN
else:
energy_color = RED
pygame.draw.line(screen, energy_color, \
(each.rect.left, each.rect.top - 5), \
(each.rect.left + int(each.rect.width * energy_remain), \
each.rect.top - 5), 2)
else:
# 毁灭
if not(delay % 3):
if e2_destroy_index == 0:
enemy2_down_sound.play()
screen.blit(each.destroy_images[e2_destroy_index], each.rect)
e2_destroy_index = (e2_destroy_index + 1) % 4
if e2_destroy_index == 0:
each.reset()
# 绘制小型敌机:
for each in small_enemies:
if each.active:
each.move()
screen.blit(each.image,each.rect)
else:
# 毁灭
if not(delay % 3):
if e1_destroy_index == 0:
enemy1_down_sound.play()
screen.blit(each.destroy_images[e1_destroy_index], each.rect)
e1_destroy_index = (e1_destroy_index + 1) % 4
if e1_destroy_index == 0:
each.reset()
#检测玩家飞机是否被撞
enemies_down = pygame.sprite.spritecollide(me,enemies,False,pygame.sprite.collide_mask)
if enemies_down:
#me.active = False
for e in enemies_down:
e.active = False
# 绘制玩家飞机
if me.active:
if switch_image:
screen.blit(me.image1,me.rect)
else:
screen.blit(me.image2, me.rect)
else:
# 毁灭
if not(delay % 3):
if me_destroy_index == 0:
me_down_sound.play()
screen.blit(me.destroy_images[me_destroy_index], me.rect)
me_destroy_index = (me_destroy_index + 1) % 4
if me_destroy_index == 0:
print("Game Over!")
running = False
#切换图片
if not (delay % 5):
switch_image = not switch_image
delay -= 1
if not delay:
delay = 100
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
try:
main()
except SystemExit:
pass
except:
traceback.print_exc()
pygame.quit()
input()
本篇博客到这就完啦,非常感谢您的阅读,下一篇继续来完成飞机大战游戏,如果对您有帮助,可以帮忙点个赞或者来波关注鼓励一下喔 ,嘿嘿