飞机大战
准备工作
首先,我们再开始编写程序之前,要明确程序结构,对应关系,合理的准备分析,能使我们的开发工作事半功倍
1、确定我们需要的类
首先,我们做飞机大战,既然需要飞机类(Plane),敌机类(AiPlane),然后是他们两个的基类(Baisc_Plane),用于提供他们重复的属性和方法实现继承。
然后,在这基础之上,我们添加一个子弹类(Bullet),它可以让我们有真正的游戏体验,同样是不可或缺的东西,这三个类,就可以构成我们最基本的飞机大战。
对于后面的流星类,属于优化游戏体验的,后续开发完后,再行添加
2、确立每一个类当中,拥有的属性以及方法
首先先说几个都有的属性和方法
1、self.live 用于判断飞机是否存活
2、self.blood 用于控制飞机血量
3、self.img Actor对象
4、move()移动方法
5、draw()绘制方法
6、oversetup()边界检查
下面列举飞机类作为讨论对象
2.1、飞机类
2.1.1、属性
在属性中,因为要控制飞机移动,所以我们需要上下左右四个标志属性来控制
然后,飞机的得分属性,飞机的子弹列表属性
2.1.2方法
set_direction()方法,控制方向
clear_direction()方法 ,解除方向
飞机大战 主程序
首先需要分析主程序中,需要什么方法?
1、在draw和update中需要的前提变量
因为我们的敌机和子弹、流星对象肯定不可能像玩家一样单独创建控制,所以我们需要一个存放他们对应的列表,并在他们的生命值归零之后,将他们从该列表中移除,这样就不会显示在图像中
2、控制飞机飞行
draw绘制,update更新,除去这两个之外,因为我们的飞机对象需要依靠上下左右的键盘控制移动,所以我们还需要两个方法,一个是获取键盘按下的方法,一个是获取键盘弹起的方法
拥有他们之后,我们的飞机,就可以率先在界面上自由飞行了,实现原理:在这两个方法中调用玩家飞机类对象中的对应方法,根据键盘的反馈获取值,为上下左右赋予布尔值,然后使用布尔值控制飞机移动
def on_key_down(key): if login == True: plane.set_direction(key)#获取键盘按下的值 def on_key_up(key): if login == True: plane.clear_direction(key)#获取键盘弹起的值
3、绘制所有的图像
首先,分析一下我们需要绘制的图像
1、判断是否登录
首先在我们打开游戏后,应该有个登陆界面,也就是我们应该在其他绘制出来的之前,使用布尔值判断是否登录,未登录则先绘制其他图像,而在我们登陆过后,在绘制出另外的图像,并让整个哟咻运行,因此,update方法同样需要判断是否登录,
2、根据登陆状态播放不同音乐
其次,我们准备了两套背景音乐,一套是未登录时播放,一套时开始游戏播放
3、使用工具类实现我们所有的图像绘画,减少代码冗余,提高观赏性,提高效率
最后,我们需要调用在工具类util当中的实现方法,来绘制剩下的所有图像,因为使用了工具类util,使得我们主程序十分洁简,如果出现问题,可以直接找到对应方法进行修改
def draw(): global bgmusic_1,bgmusic_2,login bg = Actor("../images/背景.png") bg.draw() #判断登录 if login==True: if bgmusic_1 == True: pygame.mixer.music.load("music/backmusic1.mp3") # 加载MP3文件 pygame.mixer.music.set_volume(0.8) pygame.mixer.music.play(-1) # 播放mp3文件 bgmusic_1 = False util.show(rank_list,screen,plane,Aiplanes,meteor_list)#工具类util的show方法,所有绘制都在这里实现 plane.show_score()#plane类的绘制得分 else: if bgmusic_2 == True: pygame.mixer.music.load("music/backmusic2.mp3") # 加载MP3文件 pygame.mixer.music.set_volume(0.8) pygame.mixer.music.play(-1) # 播放mp3文件 bgmusic_2 = False login = util.show_login(screen)
4、update更新数据
在update方法中我们大量使用了封装方法来替代原本的上百行代码
1、判断登陆状态
首先,要先判断是否登录在开始运行其他方法
2、最关键的,也就是对子弹,敌机,流星等更新数据,我们这里使用了util工具类封装,在哪里再详细介绍
def update(): if login == True: ''' 1、循环判断发射出的子弹对象状态是否归零,归零则将子弹对象从子弹数组中删除 2、控制子弹移动 move方法 ''' for bullet in plane.bullets: bullet.oversetup()#子弹是否超出范围 if bullet.blood <= 0: plane.bullets.remove(bullet) bullet.move() ''' 1、判断玩家飞机是否存活,存活状态刷新敌机,控制移动,子弹图片变换速度,玩家飞机是否超出边界 ''' if (plane.blood > 0): util.Create_AiPlane(Aiplanes, plane_kind)#工具类自动创造敌机 util.Create_Meteor(meteor_list)#工具类自动创造流星 util.bullet_speed(plane)#工具类自动控制子弹切换图片速度 plane.oversetup()#玩家飞机类边界检测 plane.move() plane.level_up()#玩家飞机类等级提升 util.meteor_check(meteor_list,plane)#工具类流星状态检测 ''' 循环敌机列表,若敌机抵达边界或者碰撞玩家飞机则爆炸以及移除敌机列表,同时控制敌机行动 ''' util.AiPlane_check(plane,Aiplanes)#工具类检测敌机状态
飞机大战 类
1基类(basic_plane)
该类用于为其他类提供基本的继承,包括单个图片对象,血量和绘制边界检测,移动方法
from pgzero.actor import Actor ''' 这是一个包含图片路径,x,y,血量的基类 ''' class basic_plane: def __init__(self, imgPath, x, y, blood): self.img = Actor(imgPath) self.img.y = y self.img.x = x self.blood = blood def draw(self): self.img.draw() def oversetup(self): self.img.y+=1
2、玩家飞机类(Plane)
玩家飞机类我们提供了初始方法,移动模块,显示分数模块,飞机等级提升模块等,以下展示较为关键的模块和功能
2.1初始方法(init):
方法中为飞机类提供了大量的属性,出去继承基类的方法之外,还提供了上下左右的移动标记,子弹列表,飞机得分,当前飞机等级,玩家升级标记等
def __init__(self, imgPath, x, y, blood): #self.img = Actor(imgPath) # self.img.x = x # self.img.y = y # self.blood = blood#血量 ''' 这里使用了继承,上面注释的代码都是父类拥有的 ''' super().__init__(imgPath, x, y, blood) self.up = False self.down = False self.left = False self.right = False self.bullets = []#子弹列表 self.bullet_num = []#显示子弹数量 self.score = 0#用于记录飞机获取分数 self.level = 0#记录飞机当前等级 self.flag = True#flag用于玩家飞机死亡后来控制程序只执行一次 self.flags,self.flag_int = [True,True,True,True,True,True],0 ''' 这里为分数图片加载 ''' for x in range(0, 10): fs1 = Actor("../../work/images/score/" + str(x) + ".png") fs1.x, fs1.y = 50, 20 self.bullet_num.append(fs1)
2.2玩家飞机移动模块(move,set_direction,clear_direction)
玩家飞机移动模块稍微有点复杂,一共用了三个方法来控制他的移动,第一个move是当玩家飞机用于上下左右的真布尔值时开始移动,第二个方法set_direction则是获取键盘按下的值来为上下左右赋布尔值,第三格方法是当键盘弹起时为对应标记赋False值以此控制玩家飞机移动
def move(self): if (self.left==True): self.img.x -=4 if(self.right == True): self.img.x+=4 if(self.up == True): self.img.y-=4 if(self.down == True): self.img.y+=4 ''' 该方法用于从键盘中获取按下值,使用if判断来为飞机提供方向 ''' def set_direction(self,direction): if (direction == pygame.K_LEFT): self.left = True if (direction == pygame.K_RIGHT): self.right = True if(direction == pygame.K_UP): self.up = True if(direction == pygame.K_DOWN): self.down = True #按空格 发射子弹 if(direction == pygame.K_SPACE): if(len(self.bullets)<10): pygame.mixer.init() # 初始化音乐 play = pygame.mixer.Sound("music/shoot_2.mp3") play.set_volume(1) play.play() self.bullets.append(Bullet.Bullet(self.img.x,self.img.y-30,1)) else: print("存在子弹超过上限") ''' 从键盘中获取弹起的值,来清空飞机方向 ''' def clear_direction(self,direction): if (direction == pygame.K_LEFT): self.left = False if (direction == pygame.K_RIGHT): self.right = False if(direction == pygame.K_UP): self.up = False if(direction == pygame.K_DOWN): self.down = False
3敌机类(AiPlane):
敌机类我们提供了初始方法,移动模块,检测碰撞模块,随机移动模块等,以下展示较为关键的模块和功能
3.1检查碰撞模块(check)
将玩家对象作为参数传入方法,循环玩家子弹列表,判断是否碰撞,如果血量归零则玩家加分,,敌机被移除敌机列表,并且玩家与敌机碰撞,玩家死亡
以下是检测碰撞代码
def check(self,plane): #获取玩家子弹列表来判断是否碰撞 for bullet in plane.bullets: #列表不为空执行 if len(bullet.imgs)!=0: if self.int == 2: continue else: if self.imgs[self.int].colliderect(bullet.imgs[0]): self.blood -= 1 bullet.blood -= 1 if self.blood <= 0:#当敌机击毁时 pygame.mixer.init() # 此处是控制敌机被击中音效 play = pygame.mixer.Sound("music/hit_1.mp3") play.set_volume(0.6) play.play() plane.score += self.score#敌机击毁玩家获取分数 if self.score > 0:#此处控制分数只加一次 self.score = 0 self.int = 2 self.dietime = time.time()#让爆炸效果展示一段时间 #玩家与飞机碰撞,玩家gg if self.imgs[self.int].colliderect(plane.img): if self.int == len(self.imgs)-1: pass else: plane.blood = 0
3.2初始方法(init):
在敌机类中,为敌机提供了大量的属性,包括敌机分数,血量,左右方向,敌机移动速度等
def __init__(self, img_1, img_2, img_3, x, y, blood, score): self.int = 0#控制敌机切换图片 self.imgs = [Actor(img_1),Actor(img_2),Actor(img_3)] self.imgs[0].x,self.imgs[1].x,self.imgs[2].x = x,x,x self.imgs[0].y,self.imgs[1].y,self.imgs[2].y = y,y,y self.blood = blood#敌机血量 self.score = score#敌机分数 self.time = time.time()#获取敌机图片更换时间 self.left = False#更改左边的方向 self.right = False#更改右边方向 self.direction_time = time.time()#获取更改方向时间 self.dietime = 1#死亡一秒后清除 self.speed = 0.5#敌机x轴移动速度
3.3随机移动模块(move):
1、为敌机创造两个方向标记,用于控制敌机左右移动,当敌机过于靠近左右边界时,为其赋反方向值,同时使用time.time()控制更改方向时间
下面是敌机类的移动方法,
def move(self): if len(self.imgs)!=None: if self.blood>0: #使用set_time获取每次执行方法不一样的时间来作为最终判断,更具有随机性 set_time = random.uniform(1,8) for img in self.imgs: if time.time() - self.direction_time>set_time: self.direction_time = time.time() if self.left: self.right = True self.left = False else: self.right = False self.left = True img.y+=2 #这里使用self.speed来控制敌机x轴上移动速度 if self.left: img.x+=self.speed else: img.x-=self.speed
4子弹类(Bullet):
子弹类我们提供了初始方法,移动模块,边界检测模块等,以下展示较为关键的模块和功能,该类仅用作与玩家飞机对象,独特的子弹效果
4.1初始方法(init):
由于我们玩家子弹拥有五张图片,所以需要传入一个Actor子弹对象列表来保存,然后提供血量,速度等属性
def __init__(self,x,y,blood): self.imgs = []#子弹图片列表,输出动态子弹 for z in range(1, 6): self.imgs.append(Actor("../images/x" + str(z) + ".png"))#将五张不同状态子弹图片 self.imgs[z-1].x = x self.imgs[z-1].y = y self.blood = blood#子弹血量,初始为1 self.int = 0#int用于控制子弹变换不同状态 self.time = time.time()#用于控制子弹状态变化得时间 self.speed = 0.1#子弹初始速度,注意:python 时间模块以秒为单位
4.2移动、边界判断绘画模块(oversetup,draw,move):
边界判断方法 因为在子弹列表移动过程中是使用for循环将所有图片xy轴一起移动,所有只需要判断其中一个图片是否超出边界即可
绘制同样,但不同在于五张图片我们需要控制切换的时间,通过我们初始方法中定义的self.int属性我们可以获得一个数值,然后在外界通过特定条件更改该值来实现子弹图片特效变得更加华丽(切换图片速度加快)
''' 该方法用于检测子弹列表是否超过边界或者血量低于为0,到达边界或者碰撞敌机目标都将血量归零后,清除该子弹数组对象 ''' def oversetup(self): if len(self.imgs)!=0: if(self.imgs[1].y<0): self.blood = 0 self.imgs.clear() def draw(self): #此处必须添加判空,以确保方法执行不会出现空指针异常 if len(self.imgs)!=0: self.imgs[self.int].draw() ''' 使用time模块控制子弹图片变化速度 同时每一次判断都需要重新获得time,来保证不会越过判断直接执行 ''' if time.time()-self.time>self.speed: if self.int<4: self.int+=1 self.time = time.time() else: self.int = 0 self.time = time.time() ''' 子弹移动方法,循环子弹对象的Actor列表,让所有同一个对象的所有图片同时移动 ''' def move(self): if len(self.imgs)!=0: for z in range(0, 5): self.imgs[z].y -= 8
5流星类(Meteor)
5.1初始方法(init)
通过了上面几个类别,再看下面的流星类,基本就已经没有太多新奇的东西了,无非就是存活标记,Actor对象列表,self.int控制图片切换,self.time控制切换时间
def __init__(self): self.imgs = [Actor("../images/流星1.png"),Actor("../images/流星2.png")] self.x = random.randint(0,800) self.y = random.randint(-800,0) for img in self.imgs: img.x,img.y = self.x,self.y self.live = True self.int = random.randint(0,1) self.time = time.time()
5.2检测碰撞(check)
流星对象可以击中玩家飞机并对玩家飞机造成1点伤害
def cheak(self,plane): if self.imgs[self.int].colliderect(plane.img): plane.blood-=1 self.live = False
工具类(util)
相信对于上面的代码解析,应该发现了提到了大量的util工具类的封装方法,那我们现在就来对这些方法进行逐一解析
1、init_data()初始化数据
该方法用于程序运行时调用,用于从文件当中读取数据,我们从两个文件当中,读取了敌机数据,以及往次游戏排名数据,而所用的方法大同小异
使用with open打开文件,在使用readline()读取数据,然后将其添加到临时数组当中,结束之后在返回两个数组给主程序,这样,我们的主程序中就有了以前的游戏数据了
''' 为飞机大战初始化敌机和历史分数数据,从文件中获取 ''' def init_data(): plane_kind = [] rank_list = [] #敌机数据的读取 with open("AiPlanes.txt", "r") as f: ret = f.readline() while ret: print(ret, end='') plane_kind.append(ret) ret = f.readline() f.close() #排名数组的读取 with open("rank.txt", "r") as f: ret = f.readline() temp = [] while ret: if ret != None: # print(ret, end='') ret = ret.replace("\n", "") temp.append(ret) ret = f.readline() f.close() f.close() ''' 这里是将排名数组中的数据按照逗号分割之后保存到排名数组中,并返回给调用者 ''' for t in temp: socre, level = t.split(",") rank_list.append([socre, level]) return plane_kind, rank_list
2、rank()分数结算排名保存
上面说到了初始化,那就先说结算保存,首先,我们需要从主程序传过来rank_list数组,然后再通过冒泡排序,对他进行排序,在对前十的数据进行保存
核心就是冒泡排序和写入文件
''' 完成历史记录和本次分数的排名,使用的是冒泡排序 在排序完成之后,使用io将排名数组前十的数据保存到文件内 ''' def rank(rank_list): for i in range(1, len(rank_list)): for j in range(0, len(rank_list) - i): if int(rank_list[j][0]) < int(rank_list[j + 1][0]): rank_list[j], rank_list[j + 1] = rank_list[j + 1], rank_list[j] with open("rank.txt", "w") as f: if len(rank_list) > 10: for num in range(0, 10): f.write(str(rank_list[num][0]) + "," + str(rank_list[num][1]) + "\n") else: for num in range(0, len(rank_list)): f.write(str(rank_list[num][0]) + "," + str(rank_list[num][1]) + "\n") f.close()
3、AiPlane_check()检测敌机状态
该方法用于检测敌机列表当中的敌机状态
首先从传进来的敌机列表Aiplanes进行循环,再调用每一个对象的内置方法进行边界检查,速度升级,碰撞检测等调用,随后重点是判断敌机是否存活,不存活则从列表中删除敌机,注意:我们的dietime是为了在死亡后显示敌机爆炸效果0.5秒
def AiPlane_check(plane,Aiplanes): for Ai in Aiplanes: Ai.check(plane) # 检测敌机是否碰撞玩家飞机 Ai.oversetup() # 检测敌机是否超出边界 Ai.speed_up(plane) # 判断敌机是否存活,不存活则切换爆炸图片,在另外一个方法实现,这里使用time控制爆炸图片得以显示0.5秒,之后移除敌机列表 if Ai.blood <= 0 and time.time() - Ai.dietime > 0.5: Aiplanes.remove(Ai) Ai.move()
4、meteor_check()检测流星类状态
这个方法,和上面检测敌机几乎没有任何区别,所有就不展开了
def meteor_check(meteor_list,plane): for meteor in meteor_list: meteor.oversetup()#检测边界 meteor.move()#移动 meteor.cheak(plane)#检测碰撞 if meteor.live == False: meteor_list.remove(meteor)
5、Create_AiPlane(Aiplanes, plane_kind)自动创造敌机
该方法从传进来的敌机列表判断是否存在相应数量,少于则自动添加,x,y轴均是使用随机产生
''' 随机创造敌机,当数量小于10时 ''' def Create_AiPlane(Aiplanes, plane_kind): if (len(Aiplanes) < 10): Ai_random = random.randint(0, 2) # 随机选择一种飞机数据 img_1, img_2, img_3, blood, score = plane_kind[Ai_random].split(",")#将数组按照逗号分割由不同变量接收 x = random.randint(50, 700)#重新获取x轴 y = random.randint(-200, -50)#重新获取y轴 Aiplanes.append(AiPlane(img_1, img_2, img_3, x, y, int(blood), int(score)))#将新的敌机添加到敌机数组 return Aiplanes
6、 Create_Meteor(meteor_list)自动创造流星
还是和上面一样,不展开
def Create_Meteor(meteor_list): if(len(meteor_list) < 12): meteor_list.append(Meteor()) return meteor_list
7、bullet_speed(plane)控制子弹速度
这里使用玩家的得分来控制子弹图片切换速度以及玩家等级
''' 用于控制我方子弹图片变换速度,随着飞机等级而变换 ''' def bullet_speed(plane): if plane.bullets!=None: for bullet in plane.bullets: if plane.score<=100: plane.level = 1 bullet.speed = 0.1 elif 100 < plane.score <=300: bullet.speed = 0.05 plane.level = 2 elif 300 < plane.score <= 600: bullet.speed = 0.02 plane.level = 3 elif 600 < plane.score <= 1000: bullet.speed = 0.009 plane.level = 4 elif 1000<plane.score<=2000: bullet.speed = 0.005 plane.level = 5 elif 3000 < plane.score: bullet.speed = 0.001 plane.level = 6
8、show(rank_list,screen,plane,Aiplanes,meteor_list)展示方法
这个就是包揽了主程序draw方法所有绘制的show方法,没有太多需要讲的,仅仅是为了减少主程序代码,所用到的都是循环遍历绘制,
唯一特别的就是判断玩家死亡,开启保存模式,调用rank方法
''' 用于完成主程序draw方法的绘画 ''' def show(rank_list,screen,plane,Aiplanes,meteor_list): gameover = Actor("../images/游戏结束.png") gameover.y = 310 gameover.x = 370 boom = Actor("../images/boom.png")#爆炸图片对象 count = 70 ''' 以下才是绘制 ''' screen.draw.text("score level",(30,50))#将文字绘画出来 for i in rank_list: screen.draw.text(str(i[0])+" "+str(i[1]), (30,count)) count+=25 Actor("../images/super1.png").draw()#这是子弹数量的图片 screen.draw.text("plane level", (300, 20))#绘制文字 for meteor in meteor_list: meteor.draw() ''' 判断玩家飞机血量,归零则开启保存模式,调用保存方法 ''' if (plane.blood <= 0): if plane.flag==True: score,level = plane.score,plane.level rank_list.append([score,level]) rank(rank_list) plane.flag= False #这里将飞机图片的xy轴传给爆炸图片对象,实现飞机碰撞爆炸 boom.x, boom.y = plane.img.x, plane.img.y boom.draw() gameover.draw() else: plane.draw() #绘制飞机 if (len(Aiplanes) != 0): for Ai in Aiplanes: Ai.draw() #绘制子弹 if (len(plane.bullets) != 0): for bullet in plane.bullets: bullet.draw()
9、show_login(screen)绘制登陆界面
这个方法是单独于绘制未登录的界面,同样没什么好讲的
def show_login(screen): title = Actor("../images/title.jpg") start = Actor("../images/start.png") title.x = 400 title.y = 200 start.x = 400 start.y = 500 title.draw() start.draw() mouse_pos = pygame.mouse.get_pos( ) mouse_x,mouse_y = mouse_pos[0],mouse_pos[1] # print(start.x,start.y) if start.x - start.width/2 < mouse_x < start.x+start.width/2 and start.y -start.height/2 < mouse_y < start.y+start.height/2: login = mouse_even() if login== True: return True else: return False
10、mouse_even()鼠标单击判断登录事件
这里用到了event事件,1025就是鼠标按下单击的事件数字代码
pygame.event.wait()是等待事件
''' 鼠标事件判断 ''' def mouse_even(): event = pygame.event.wait() ''' 此处不可循环所有事件列表,会造成堵塞,导致单击无效 for event in pygame.event.get(): if event.type == 1025 1025是鼠标单击的int代表值 代码为pygame.MOUSEBUTTONDOWN ''' if event.type == 1025: login = True return login