没想到一搁下就是小半年,距离(上)都过去快半年了......

四月份继续优化了一下,然后弄毕业论文一直放到六月初,六月初半玩半弄的一直到七月初才搞定。

运行流程:

 

pygame-飞机大战(下·一)_ico

首先调用初始化函数,初始化完毕后循环执行update()函数更新状态。

各文件结构如下:

pygame-飞机大战(下·一)_ide_02

 

pygame-飞机大战(下·一)_ico_03

1.入口函数Enter.py:

运行Enter即可开始游戏。

调用GameMgr的实例化对象GMI,在while循环中调用GMI的update函数,传参为帧长(1/fps)。

from Codes.Logic.GameMgr import GMI
fps=30#每秒刷新速度
def run_game():
    GMI.init()
    while True:
        GMI.update(1/fps)

run_game()

2.游戏管理GameMgr.py:

分别对Event、Handle等各对象初始化,并初始化四个关卡(Battles),且置自身的gold/time/score为0,目的是为了在某一关通关后保存这个关卡的对应参数并传递给下一关卡。在刷新函数update中,分别执行之前初始化过的各个对象的刷新函数。调用GameStates类的对象检查内部的game_active是否为1(用来判断当前的状态,如果其为1,表示正在游戏进行中),如果为1则先调用returnbattles类外函数获得当前的关卡(GameSettings类内保存了当前关卡号,1为最小),再判断当前关卡的acc是否为1(在关卡的刷新中,如果达到通关条件则比自身的acc置为1),如果为1则把这个关卡的金币积分时间等保存到GameMgr自身对应的属性里,之后置GameSettings类的当前实例化对象的当前关卡(current_checkpoint)+1,并调用returnbattles获得新的当前的关卡,然后调用关卡的pip函数把GameMgr自身的金币时间等传递给该关卡。

from Codes.Base.GameSettings import GSeI
from Codes.Base.BaseSettings import BSI
from Codes.Character.ControllerMgr import CMI
from Codes.Common.DrawBase import DCI
from Codes.Input.Handle import HI
from Codes.Common.Event import EP
from Codes.Battles.Battle1 import BI as Battle1
from Codes.Battles.Battle2 import BI as Battle2
from Codes.Battles.Battle3 import BI as Battle3
from Codes.Battles.Battle4 import BI as Battle4
from Codes.Logic.GameStates import GStI
from Codes.UI.UIMgr import UMI
def returnbattle():
    Battles=[Battle1,Battle2,Battle3,Battle4]
    return Battles[GStI.current_checkpoint-1]

class GameMgr():
    def __init__(self):
        pass
        #设置当前关卡 默认值为第一关
    def init(self):
        EP.init()
        HI.init()
        CMI.init()
        UMI.init()
        DCI.init()
        GSeI.init()
        GSeI.achi()
        GStI.init()
        BSI.init()
        Battle=[Battle1,Battle2,Battle3,Battle4]
        for i in Battle:
            i.init()
        self.score=0
        self.time=0
        self.gold=0

    def update(self,delta):
        BSI.update(delta)
        HI.update(delta)
        UMI.update(delta)
        BI=returnbattle()
        if GStI.game_active:
            CMI.update(delta)
            BI.update(delta)
            if BI.acc==1:
                self.time=BI.time
                self.score=BI.score
                self.gold=BI.gold
                del BI
                GStI.current_checkpoint+=1
                BI=returnbattle()
                BI.pip(self.gold,self.score,self.time)
        DCI.update(delta)

GMI=GameMgr()

3.事件分发器Event.py:

类内存放了一个名为map2ListDistributeEvnet的字典,key为type(按键类型),值为event()【触发事件函数】,addEvent函数用于在字典增加新的字典项,subEvent为删除对应的字典项,dispatch用于根据输入的key,查找字典是否有对应项,如果存在,

则执行对应的event()。

class Event():
    def __init__(self):
        self.map2ListDistributeEvnet={'key':[]}

    def init(self):
        self.map2ListDistributeEvnet.clear()

    def addEvent(self,type,element):
        if type in self.map2ListDistributeEvnet:
            self.map2ListDistributeEvnet(type).append(element)
        else:
            self.map2ListDistributeEvnet[type]=[]
            self.map2ListDistributeEvnet[type].append(element)

    def subEvent(self,type,element):
        if type in self.map2ListDistributeEvnet:
            if element in self.map2ListDistributeEvnet[type]:
                self.map2ListDistributeEvnet[type].remove(element)

    def dispatch(self,type):
        if type not in self.map2ListDistributeEvnet :
            print('按键未注册')
            return
        for event in self.map2ListDistributeEvnet[type]:
            event()
    def clearEvent(self):
        
        self.map2ListDistributeEvnet.clear()

EP=Event()

4.事件处理Handle.py:

和Event配合使用。刷新函数判断当前是否有输入(pygame.event.get()),判断输入的类型:如果是QUIT(界面的❌),则退出;如果是按键按下,触发Event类的dispatch函数,来执行这个按键对应的事件;如果按键抬起且为qwad(在设定中一直按着可以一直移动),则触发Event类的dispatch函数,来执行这个按键对应的事件;如果是鼠标按下,则调用UIMgr对象的check_Mouse函数(根据当前的状态根据鼠标的位置触发事件)。add函数将在Event对象里增加新的字典项。

import sys
import pygame
from Codes.Common.Event import EP
from Codes.UI.UIMgr import UMI

class Handle():
    def init(self):
        pass

    # 配置游戏基本信息
    def update(self, delta):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                EP.dispatch(event.key)
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_a or event.key == pygame.K_d or event.key == pygame.K_w or event.key == pygame.K_s:
                    EP.dispatch(event.key)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pygame.mouse.get_pos()
                UMI.check_Mouse(mouse_x, mouse_y, self)

    def add(self, type, element):
        EP.addEvent(type, element)
        
    def clear(self):
        EP.clearEvent()

    def exit(self):
        sys.exit()


HI = Handle()

5.控制管理ControllerMgr.py:

由于Ship对象中导入了Battles对象,所以Battles对象不能直接导入Ship对象,因此使用第三者ControllerMgr来将Battles的host【通过Battle.sethost函数】设置为Ship()【主操作对象】,这样Battle就可以通过调用自身的host来使用Ship对象。

from Codes.Kinds.Ship import Ship
from Codes.Battles.Battle1 import BI as Battle1
from Codes.Battles.Battle2 import BI as Battle2
from Codes.Battles.Battle3 import BI as Battle3
from Codes.Battles.Battle4 import BI as Battle4
class ControllerMgr():
    def __init__(self):
        pass

    def init(self):
        self.host=Ship()
        self.host.init()
        self.host.position_reset()
        Battle=[Battle1,Battle2,Battle3,Battle4]
        for i in Battle:
            i.setHost(self.host)

    def update(self,delta):
        self.host.update(delta)

CMI=ControllerMgr()

6.界面管理UIMgr.py

界面管理函数负责根据当前的状态显示界面,当发生鼠标点击时(Handle.py里触发)则会调用UIMgr的check_Mouse函数,根据当前的状态(if xxx_active)和点击的位置是否在rect的范围内(rect.collidepoint)来执行当前状态为1对应界面的check函数,该check函数在对应界面的类(UI文件夹下其他py)里声明作用,比如改变当前的状态等。

import pygame
from Codes.UI.BeginGame import BeginGame
from Codes.UI.QuitGame import QuitGame
from Codes.UI.HelpGame import HelpGame
from Codes.UI.RankGame import RankGame
from Codes.UI.AchisGame import AchisGame
from Codes.UI.Pass_Choose import Pass_Choose
from Codes.UI.Display_bg import Display_bg
from Codes.UI.Display_die import Display_die
from Codes.UI.StoreSys import Store
from Codes.UI.Weapon_Advance import Weapon_Advance
from Codes.UI.Change_Ship import Change_Ship
from Codes.UI.logo import logo
from Codes.Logic.GameStates import GStI
from Codes.Base.Animation import Animation
from Codes.Base.Screen import SI
from Codes.Common.DrawBase import DrawOn,DCI
from Codes.Base.GameSettings import GSeI

class UIMgr(pygame.sprite.Sprite,DrawOn):    

    def _init_(self):
        super(UIMgr,self).__init__()
        super(UIMgr,self).definePriority(9)
        
    def init(self):
        self.priority=9
        self.screen=SI.screen
        self.BeginUI=BeginGame()
        self.QuitUI=QuitGame()
        self.HelpUI=HelpGame()
        self.RankUI=RankGame()
        self.AchisUI=AchisGame()
        self.storeUI=Store()
        self.advanceUI=Weapon_Advance()
        self.changeUI=Change_Ship()
        self.logoUI=logo()
        self.dieUI=Display_die()
        self.display_bg=Display_bg()
        self.pass_choose=Pass_Choose()
        self.group =pygame.sprite.Group()#用来存放动画类
        self.animation = Animation(self.screen,500,20,45)#初始化实例化并读取动画,以后用到直接复制即可,免重复读取浪费时间
        self.animation.load("images/logo/animation_logo.png", 200, 200, 5)
        self.group.add(self.animation)
        
    def update_animation(self):#更新group里的动画
        ticks = pygame.time.get_ticks()
        self.group.update(ticks)
        new_group=pygame.sprite.Group()
        for animation in self.group:
            new_group.add(animation)
        self.group=new_group
        self.group.draw(self.screen)
        pygame.display.update()
        
    def update(self,delta):
        if GStI.ui:         
            self.BeginUI.show()
            self.QuitUI.show()
            self.HelpUI.show()
            self.RankUI.show()
            self.AchisUI.show()
            self.preDraw()
            self.update_animation()
        if GStI.store_active:
            self.storeUI.show()
            self.advanceUI.show()
            self.changeUI.show()
        else:
            if GStI.display_active:
                self.logoUI.DisplayUI.show()
            if GStI.rank_active:
                self.RankUI.DisplayUI.show()
            if GStI.help_active:
                self.HelpUI.DisplayUI.show()
            if GStI.achis_active:
                self.AchisUI.DisplayUI.show()
            if GStI.advance_active:
                self.advanceUI.display.show()
            if GStI.change_active:
                self.changeUI.display.show()
            if GStI.background:
                if GStI.first_play:
                    self.display_bg.show()
                else:
                    self.pass_choose.show()
            if GStI.die_active:
                self.dieUI.show()
        
        
    def check_Mouse(self,mouse_x,mouse_y,dispatcher): 
        if GStI.ui:
            if self.BeginUI.rect.collidepoint(mouse_x,mouse_y) :
                self.BeginUI.check()
            if self.QuitUI.rect.collidepoint(mouse_x,mouse_y) :
                self.QuitUI.check()  
            if self.RankUI.rect.collidepoint(mouse_x,mouse_y) :
                self.RankUI.check()            
            if self.HelpUI.rect.collidepoint(mouse_x,mouse_y) :
                self.HelpUI.check()    
            if self.logoUI.rect.collidepoint(mouse_x,mouse_y) :
                self.logoUI.check()  
            if self.AchisUI.rect.collidepoint(mouse_x,mouse_y) :
                self.AchisUI.check()  
        elif  GStI.display_active:
            if self.logoUI.DisplayUI.back_rect.collidepoint(mouse_x,mouse_y) :
                self.logoUI.DisplayUI.check_back()
            elif self.logoUI.DisplayUI.next_rect.collidepoint(mouse_x,mouse_y) :
                self.logoUI.DisplayUI.check_next()
            elif self.logoUI.DisplayUI.pre_rect.collidepoint(mouse_x,mouse_y) :
                self.logoUI.DisplayUI.check_pre()
        elif  GStI.rank_active:
            if self.RankUI.DisplayUI.back_rect.collidepoint(mouse_x,mouse_y) :
                self.RankUI.DisplayUI.check_back()
            elif self.RankUI.DisplayUI.next_rect.collidepoint(mouse_x,mouse_y) :
                self.RankUI.DisplayUI.check_next()
            elif self.RankUI.DisplayUI.pre_rect.collidepoint(mouse_x,mouse_y) :
                self.RankUI.DisplayUI.check_pre()
        elif  GStI.help_active:
            if self.HelpUI.DisplayUI.back_rect.collidepoint(mouse_x,mouse_y) :
                self.HelpUI.DisplayUI.check_back()
            elif self.HelpUI.DisplayUI.next_rect.collidepoint(mouse_x,mouse_y) :
                self.HelpUI.DisplayUI.check_next()
            elif self.HelpUI.DisplayUI.pre_rect.collidepoint(mouse_x,mouse_y) :
                self.HelpUI.DisplayUI.check_pre()
        elif GStI.store_active:
            if self.advanceUI.rect.collidepoint(mouse_x,mouse_y) :
                self.advanceUI.check()
            if self.changeUI.rect.collidepoint(mouse_x,mouse_y) :
                self.changeUI.check()
            for button in self.storeUI.buttons:
                if button.rect.collidepoint(mouse_x,mouse_y) :
                    button.check()
                    break
        elif GStI.advance_active:
            if self.advanceUI.display.back_rect.collidepoint(mouse_x,mouse_y):
                self.advanceUI.display.check_back()
            for i in self.advanceUI.display.button:
                if i.rect.collidepoint(mouse_x,mouse_y) :
                    i.check()
                    break
        elif GStI.change_active:
            if self.changeUI.display.back_rect.collidepoint(mouse_x,mouse_y):
                self.changeUI.display.check_back()
            else:
                for i in range(18):
                    if pygame.Rect(self.changeUI.display.img_rect[i]).collidepoint(mouse_x,mouse_y):
                        GSeI.color_select=i
                        break
        elif GStI.background:
            if GStI.first_play:
                if self.display_bg.i>=800:
                    if self.display_bg.rect_ref.collidepoint(mouse_x,mouse_y):
                        self.display_bg.check_refuse()
                    elif self.display_bg.rect_acc.collidepoint(mouse_x,mouse_y):
                        self.display_bg.check_accept()
                else:
                    self.display_bg.i=800
            else:
                for i in range(4):
                    if self.pass_choose.rect_list[i].collidepoint(mouse_x,mouse_y):
                        self.pass_choose.click_pass(i)
                        break                
        elif GStI.achis_active:
            if self.AchisUI.DisplayUI.back_rect.collidepoint(mouse_x,mouse_y) :
                self.AchisUI.DisplayUI.check_back()
            elif self.AchisUI.DisplayUI.click_rect.collidepoint(mouse_x,mouse_y):
                 self.AchisUI.DisplayUI.check_click()
        elif GStI.die_active:
            if self.dieUI.rect.collidepoint(mouse_x,mouse_y):
                self.dieUI.check()
    
    def draw(self):
        self.update_animation() 
        
    def preDraw(self):
        DCI.add(self) 
        
UMI=UIMgr()

其中,update_animation函数使用精灵组更新动画,用来显示非线性动画效果。preDraw函数用来把当前的显示放入DrawCall类的对象中,在该对象中对各元素的优先级进行排序,依次调用其draw函数绘制,带图形显示的其他Kinds和UI等文件夹下的类同理。

7.绘制基DrawBase.py

在该代码中共声明了两个类:DrawOn类和DrawCall类。DrawOn类是其他图形显示的类的父类,其内只有一个元素,即priority优先级,继承类可以通过使用从DrawOn类继承的definePriority函数来修改优先级的大小。DrawCall类含有列表Elements用来存在待显示的对然,在update函数中根据列表里的对象的优先级大小依次调用每个对象的draw函数完成绘制。


class DrawOn(object):

    def __init__(self):
        self.priority = 0


    def draw(self):
        pass

    def definePriority(self,num):
        self.priority=num

class DrawCall(object):
    def __init__(self):
        self.Elements=[]

    def init(self):
        self.Elements=[]

    def add(self,drawon):
        self.Elements.append(drawon)

    def update(self,delta):
        self.Elements=sorted(self.Elements, key=lambda x: x.priority)
        for drawElement in self.Elements:
            drawElement.draw()
        self.init()

DCI=DrawCall()

8.游戏参数设置GameSettings.py:

进行了参数的初始化,其他类对象通过调用改参数进行各自处理,各参数对应功能在注释。

class GameSettings():
    def __init__(self):
        self.screen_width=1200
        self.screen_height=800
        self.bg_color=(54,195,229)
        self.ship_speed_factor = 150
        self.ship_width=120
        self.ship_height=120
        self.color_select=0 #开始默认设置为红色 

    def achi(self):
        self.achi=[]#成就点亮情况 最后一个默认为点亮
        self.achi_active=[] #用于标记每个成就获取的动态效果 若为1 则在ship绘制 获得成就效果 然后置为0
        for i in range(16):
            self.achi.append(0)
            self.achi_active.append(0)
        self.achi[15]=1
        self.map=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6]#每一个成就对应的等级 (0-6)
        self.achiname=['杀身成仁','石挡碎石','天选之子','混分巨兽','富可敌国','杀敌如麻','小试牛刀','终局之境','???','???','打工皇帝','挥金如土','死亡如风','武器大师','速战速决','守护先驱']
        self.achitext=['单场击落100敌机','单场击碎10个石头','单场吃到10个bonus','单场积分超过10w','单场金币超过1w','累计击落1w敌机','击杀BOSS','到达第五关','???','???','累计金币超过1kw','累计消费100w','开局30秒内死亡','全武器拥有','五分钟内通过第一关','获得内测资格']
        self.achi_click=False # False 时候 显示name  否则显示True
        self.kill_fly=0 #记录总击杀
        self.kill_bonus=0
        self.kill_stone=0
        self.gold_get=0 #总获得金币
        self.gold_consume=0#总消费
        
        
    def init(self):
        self.screen_width = 1200
        self.screen_height = 800
        self.weapon=0 #武器 0-初始武器 1.激光枪 2.散弹枪 3.大炮枪
        self.buy=[1,0,0,0]#四种武器是否购买
        self.aliens_v_min=1
        self.aliens_v_max=2
        self.aliens_size=100
        # 普通bullet
        self.bullet_speed_factor = 3
        self.alien_bullet_speed=300
        self.bullet_width = 7
        self.alien_bullet_width=5
        self.bullet_height = 45
        self.alien_bullet_height = 11
        self.bullets_allowed = 3
        self.item=-1
        #霰弹枪 
        self.shotgun_rank=1 #一阶三发 二阶五发 三阶段七发   范围 45-135  
        #激光
        #当激光为开时,剩余时间随时间减少而减少 直至为0 自动关闭 为关时,自动充能 充能剩余时间减少  直至为0 空格继续开,也可以
        self.light_width=10
        self.light_height=1000
        self.max_time=8 #激光枪最多可释放8s时间
        self.left_time=8 #释放中  剩余可释放时间
        self.charge_time=4#充能需要时间
        self.charge_left=self.charge_time
        
        self.boom_size = 40#榴弹长宽
        self.boom_range=100 #爆炸范围
        # ship
        
        self.ship_limit = 2#生命值
 
        self.vip=0#是否购买vip
        self.use=1 #道具持有(最多一个)  0-没有
        self.pac=0 #Permanent Addition Card 永久加成卡是否持有‘
        self.tac_time=30
        self.wudi_time=10
        self.tac_left=self.tac_time#临时加成卡剩余时间
        self.wudi_left=self.wudi_time#无敌剩余时间
        self.ding=0#当前金身剩余时间
        self.sou=0#当前疾步剩余时间
        self.xuruo=0#当前虚弱剩余时间
        self.shufu=0#当前束缚剩余时间
        self.updown=0#当前颠倒剩余时间
        # scale
        self.speedup_scale = 1.1#加速度
        self.initialize_dynamic_settings()
        self.pass_clear=[1,0,0,0] #已通关关卡 在选择关卡页面中 显示 若已通关 则显示为彩色
        self.pass_choose=[0,0,0,0] #当前选中关卡 在“选择关卡页面中 仅有一个为1 
    
    def update(self):
        self.shotgun_num=self.shotgun_rank*2+1
        
    def setting_reset(self):
        self.init()
        

    def initialize_dynamic_settings(self):
        self.bullet_speed_factor=300


    def increase_speed(self):
        self.ship_speed_factor*=self.speedup_scale
        self.bullet_speed_factor*=self.speedup_scale

GSeI=GameSettings()

9.游戏状态设置GameStates.py:

该文件的类声明了游戏内的所有存在的状态xxx_active,除了最开始的ui为True之外其他置为False,其他对象通过读取当前xxx_avtive是否为True来进行相应操作或改变active的值。各状态对应功能在注释。

from Codes.Base.GameSettings import GSeI
class GameStates():
    def __init__(self):
        self.current_checkpoint=1;#当前关卡 对应1 2 3 4关
    def reset_stats(self):
        self.ships_left = GSeI.ship_limit#left=剩下的生命
        
    def init(self):
        self.game_active=False#游戏进行
        self.display_active=False#显示logo
        self.die_active=False#显示死亡结算
        self.rank_active=False#显示排名
        self.help_active=False#显示帮助
        self.store_active=False#显示商城
        self.change_active=False #更改本机颜色界面
        self.achis_active=False #ui 新按钮 成就系统
        self.shotgun_active=False #按一下 开启  再按 结束
        self.background=False #新加入在第一关前显示背景信息
        self.wudi=False#无敌/屏障道具是否开启
        self.tac=False#临时加成卡是否开启
        self.advance_active=False #进阶系统
        self.revive=False#是否在复活中
        self.ui=True#显示主界面
        self.first_play=True #第一次会显示背景  否则显示选关
GStI= GameStates()

10.基础设置BaseSettings.py:

该文件设置了屏幕的大小、参数、框体标题、图标等。

import pygame
from Codes.Base.Screen import SI
from Codes.Base.GameSettings import GSeI
#窗口结构

class BaseSettings():
    def __init__(self):
        SI=pygame.display.set_mode((0,0))

    def init(self):
        SI.screen = pygame.display.set_mode((GSeI.screen_width, GSeI.screen_height))
        pygame.display.set_caption("1999:BURY v1.0")
        icon = pygame.image.load("icons\logo.jpg")
        pygame.display.set_icon(icon) 

    def update(self,delta):
        SI.preDraw()
        #让最近绘制屏幕可见
        pygame.display.flip()

BSI=BaseSettings()

 11.屏幕Screen.py

进行屏幕的最初始化设置背景颜色等。

from Codes.Common.DrawBase import DrawOn,DCI
from Codes.Base.GameSettings import GSeI
import pygame

class Screen(DrawOn):
    def __init__(self):
        super().__init__()
        super(Screen,self).definePriority(2)
        self.screen = pygame.display.set_mode((0, 0))
        self.screen.fill((0,0,0))

    def draw(self):
        SI.screen.fill(GSeI.bg_color)

    def preDraw(self):
        DCI.add(self)

SI = Screen()

这时候,所有的工具人都介绍完了,接下来介绍子弹、敌机、道具、关卡等的详细说明。