Python游戏编程(十五)飞机大战

我们将用面向对象的思维写一个飞机大战的小游戏 。

分为几个类:

  • 全局设置类:包括一些基本的参数以及音乐、图片文件的路劲。
  • 子弹基础类
  • 玩家子弹
  • 敌人子弹
  • 飞机基类
  • 玩家飞机基类
  • 敌人飞机类

目录

(一)class Settings():

(二)class Bullet:

(三)玩家、敌人子弹类

(四)class Plane:

(五)玩家飞机

(六)敌人飞机

(七)设置方法

扫描二维码关注公众号,回复: 9613981 查看本文章

(八)主函数


(一)class Settings():

import sys, time, random
import pygame
from pygame.locals import *
import threading

SCREENWIDTH = 512
SCREENHEIGHT = 768
Gray = (128, 128, 128)
# 全局的设置类
class Settings():

    def __init__(self):
        # 定义游戏窗口大小,为背景图片的一半
        self.screen_size = (self.screen_width, self.screen_height) = (SCREENWIDTH, SCREENHEIGHT)
        self.bg_color = Gray
        self.bg_image = './images/bg2.jpg'
        # 不能播放 mp3,所以转成 wav
        self.bg_music = './music/01.wav'
        self.gameover_image = './images/gameover.jpg'
        self.title = '飞机大战'

        # 英雄机参数
        self.move_step = 5 # 键盘控制的速度
        self.hero_style = './images/me.png'

        # 敌机参数
        self.enemy_speed = 4 # 敌机飞行速度
        self.enemy_style_list = ['./images/e0.png', './images/e1.png', './images/e2.png']

        # 子弹参数
        self.bullet_style = './images/pd.png'
        self.bullet_hero_v = 10 # 英雄机子弹速度
        self.bullet_enemy_v = 8 # 敌机子弹速度


# 实例化设置对象
settings = Settings()

设置一个全局的类,其中包括一些基本的参数。

最后对于一个类,都需要经过初始化和实例化后才可以发挥作用,类中使用__init__自动初始化,然后创建了一个settings对象,进行类的实例化。

(二)class Bullet:

# 子弹基类
class Bullet:
    def __init__(self, screen, x, y):
        self.x = x
        self.y = y
        self.screen = screen
        self.image = pygame.image.load(settings.bullet_style)
        


    def __del__(self):
        pass
    
    def bulletRect(self):
        bulletrect = self.image.get_rect()
        bulletrect.topleft = (self.x, self.y)
        return bulletrect

    def display(self):
        self.screen.blit(self.image, (self.x, self.y))

    def move(self):
        return True

设置子弹的基础,玩家的子弹和敌机的子弹都要从这个子弹基类继承。

当一个Python程序结束之后,Python解释器会自动调用__del__()方法来释放内存;当然我们也可以根据需要自己调用__del__()方法来删除对象,释放占用的内存。因为我们想要提高计算机的效率,暂时用不到的事情可以先不让计算机做。

pygame.image.load()方法用来加载图片。

用blit()方法在一个Surface对象上面绘制一个Surface对象。

最后建立move()方法,调用这个方法返回布尔值,通过布尔值来确定书否移除这个子弹。

(三)玩家、敌人子弹类

玩家子弹类和英雄子弹类都继承子弹类,实现的功能差不多。

# 英雄子弹类
class HeroBullet(Bullet):
    def __init__(self, screen, x, y):
        super().__init__(screen, x, y)

    def move(self):
        self.y -= settings.bullet_hero_v
        # 判断子弹是否出界
        if self.y <= -20:
            return True


# 敌机子弹类
class EnemyBullet(Bullet):
    def __init__(self, screen, x, y):
        super().__init__(screen, x, y)

    def move(self):
        self.y += settings.bullet_enemy_v
        # 判断子弹是否出界
        if self.y >= settings.screen_height:
            return True

Python在类中可任意通过__init__方法实现对实例中代码实现自动初始化,但是Python本身不会自动执行初始化操作,但是我们可能要用带继承类的其他方法,所以要调用super().__init__()方法来来实现继承类的初始化。

(四)class Plane:

# 飞机基类
class Plane:
    def __init__(self, screen, style, geo):
        self.screen = screen
        self.image = pygame.image.load(style)
        self.bullet_list = []
        self.x = geo[0]
        self.y = geo[1]
        self.is_dead = False
        self.finished = False
        self.bomb_seq = ['4','4','3','3','2','2','1','1']

    def __del__(self):
        pass
    
    def planeRect(self):
        planerect = self.image.get_rect()
        planerect.topleft = (self.x, self.y)
        return planerect

    def display(self):
        for b in self.bullet_list:
            b.display()
            # 回收子弹
            if b.move():
                self.bullet_list.remove(b)

        # 爆炸效果
        if self.is_dead:
            death_x = self.x
            death_y = self.y
            death_w = self.image.get_width()
            death_h = self.image.get_height()
            try:
                bomb_image = './images/bomb'+self.bomb_seq.pop()+'.png'
                self.image = pygame.image.load(bomb_image)
            except:
                self.image = pygame.image.load('./images/bomb4.png')
                self.finished = True
            finally:
                x = death_x + (death_w - self.image.get_width())/2
                y = death_y + (death_h - self.image.get_height())/2
                self.screen.blit(self.image, (x, y))

        else:
            # 重新绘制飞机
            self.screen.blit(self.image, (self.x, self.y))

    def fire(self):
        self.bullet_list.append(Bullet(self.screen, self.x, self.y))
        print(len(self.bullet_list))

    def over(self):
        #print("Oops: plane over ...")
        #del self
        return self.finished

screen为屏幕的Surface对象,表示一个矩形图像。

pop()方法:用来删除列表中最后一个元素。

>>> bomb_seq = ['4','4','3','3','2','2','1','1']
>>> bomb_seq.pop()
'1'
>>> bomb_seq
['4', '4', '3', '3', '2', '2', '1']

(五)玩家飞机

# 英雄飞机
class HeroPlane(Plane):
    def __init__(self, screen):
        # 英雄机初始位置
        geo = (200, 600)
        super().__init__(screen, settings.hero_style, geo)
        

        self.step = settings.move_step
        # 英雄机移动范围
        self.limit_left = -(self.image.get_width()/2)+10
        self.limit_right = settings.screen_width-self.image.get_width()/2-10
        self.limit_top = 5
        self.limit_bottom = settings.screen_height-self.image.get_height()

    def fire(self):
        self.bullet_list.append(HeroBullet(self.screen, self.x+53, self.y))

    def move_left(self):
        if self.x <= self.limit_left:
            pass
        else:
            self.x -= self.step

    def move_right(self):
        if self.x >= self.limit_right:
            pass
        else:
            self.x += self.step

    def move_up(self):
        if self.y <= self.limit_top:
            pass
        else:
            self.y -= self.step

    def move_down(self):
        if self.y >= self.limit_bottom:
            pass
        else:
            self.y += self.step

其中玩家移动的范围下左右都是由screen_size决定的,但是上方我们直接设置一个值就好了,这里我们设置为5:self.limit_top = 5,意思是玩家飞机最高能飞到5个像素的位置。

然后是英雄飞机左右上下的移动,先判断是否超出边界,如果在边界范围内的话,就进行“移动”,也就是更改相应方向的坐标。

(六)敌人飞机

# 敌机
class EnemyPlane(Plane):
    def __init__(self, screen):
        geo = (random.choice(range(408)), -75)
        enemy_style = settings.enemy_style_list[random.choice(range(3))]
        super().__init__(screen, enemy_style, geo)

        self.pipe_x = self.image.get_width()/2-1 # 1 for the width of bullet
        self.pipe_y = self.image.get_height()
        #self.planeRect.topleft = geo
    #是否要删除敌机,返回布尔值
    def move(self, hero):
        self.y += settings.enemy_speed
        if self.y > settings.screen_height:
            return True

        # 飞机的碰撞检测
        #使用位置检测碰撞:
        #if self.x > hero.x-50 and self.x < hero.x+50 and self.y > hero.y-40 and self.y < hero.y+40:
        #使用Rect对象的colliderect()方法:
        if self.planeRect().colliderect(hero.planeRect()):
            
            self.is_dead = True
            hero.is_dead = True

        # 看看我中弹了没
        for bo in hero.bullet_list:
            
            if self.planeRect().colliderect(bo.bulletRect()):
            #if bo.x > self.x+12 and bo.x < self.x+92 and bo.y < self.y+60 and bo.y > self.y:
                hero.bullet_list.remove(bo)
                # 爆炸
                self.is_dead = True

        # 看看英雄机中弹了没
        for bo in self.bullet_list:
            if hero.planeRect().colliderect(bo.bulletRect()):
            #if bo.x > hero.x+25 and bo.x < hero.x+75 and bo.y > hero.y+5 and bo.y < hero.y+50:
                self.bullet_list.remove(bo)
                hero.is_dead = True

    def fire(self):
        self.bullet_list.append(EnemyBullet(self.screen, self.x+self.pipe_x, self.y+self.pipe_y))

(七)设置方法

这里写一个事件检测和绘制文字的函数,把功能放到这个函数里面,选哟这两个功能的时候就调用相应的函数就可以了。

def check_event(hero, usedbullet):

        #Key event capture and key_control

    which = pygame.key.get_pressed()
    

    if which[K_SPACE]:
        hero.fire()
        usedbullet += 1
        print("Space...")

    if which[K_LEFT] or which[K_a]:
        hero.move_left()
        print("Left...")
    elif which[K_RIGHT] or which[K_d]:
        hero.move_right()
        print("Right...")
    elif which[K_UP] or which[K_w]:
        hero.move_up()
        print("Up...")
    elif which[K_DOWN] or which[K_s]:
        hero.move_down()
        print("Down...")      
    
    for event in pygame.event.get():
        
             
        if event.type == MOUSEMOTION:
            hero.x = event.pos[0]
            hero.y = event.pos[1]
            
        if event.type == QUIT:
            print("exit...")
            sys.exit()
     
    return usedbullet
        
        
#draw score
def drawText(text, font, screen, x, y):
    TEXTCOLOR = (0, 0, 0)
    textobj = font.render(text, 1, TEXTCOLOR)
    textrect = textobj.get_rect()
    textrect.topleft = (x, y)
    screen.blit(textobj, textrect)

事件检测这里使用的是pygame.key.get_pressed()这个方法,在前面的游戏中有使用pygame.event.get()这个方法,二者之间有一些不同,在这个游戏里面,用这两种事件调用的方法游戏的操作会有显著的区别。

大家可以在上面代码中修改一下,在游戏操作里面体验一下有神么不同。

看看官网中的介绍:

get()方法中:

This will get all the messages and remove them from the queue. If a type or sequence of types is given only those messages will be removed from the queue.

get_pressed()方法中:

Returns a sequence of boolean values representing the state of every key on the keyboard. Use the key constant values to index the array. A True value means the that button is pressed.

       """ 
        event = pygame.event.get()

        if event.type == KEYDOWN:
            
            if event.key == K_SPACE:
                hero.fire()
                print("Space...")
                
            if event.key == K_LEFT or K_a:
                hero.move_left()
                
            if event.key == K_RIGHT or K_d:
                hero.move_right()
                
            if event.key == K_UP or K_w:
                hero.move_up()
                
            if event.key == K_DOWN or K_s:
                hero.move_down()
                """

(八)主函数

def main():

    # 初始化 Pygame
    pygame.init()

    # 创建一个游戏窗口
    screen = pygame.display.set_mode(settings.screen_size, 0, 0)

    # 设置窗口标题
    pygame.display.set_caption(settings.title)

    # 在窗口中加载游戏背景
    background = pygame.image.load(settings.bg_image)
    
    #设置积分器
    font = pygame.font.SysFont(None, 48)

    # 背景音乐
    pygame.mixer.init()
    pygame.mixer.music.load(settings.bg_music)
    #pygame.mixer.music.play()

    # 创建英雄机
    hero = HeroPlane(screen)

    play = True
    enemylist = []
    bg_y = -(settings.screen_height)
    
    usedbullet = 0                    
    score = 0
    
    while True:
        

        #检查退出
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit() 
                sys.exit()
        if not pygame.mixer.music.get_busy():
            print("play music")
            pygame.mixer.music.play()

        # 从坐标(0, -768)开始绘图,所以看到的是背景图片的下半部
        screen.blit(background, (0, bg_y))
        bg_y += 2
        if bg_y >= 0:
            bg_y = -(settings.screen_height)

        if play:
            drawText('Score:{}'.format(score),font,screen,256,256)
            drawText('Used Bullet:{}'.format(usedbullet),font,screen,256,300)
            # 绘制英雄机
            hero.display()

            # 随机绘制敌机
            if random.choice(range(50)) == 10:
                enemylist.append(EnemyPlane(screen))

            # 遍历敌机并绘制移动
            for em in enemylist:
                em.display()

                # 敌机随机发炮弹
                if random.choice(range(50)) == 10:
                    em.fire()

                # 判断敌机是否出界
                if em.move(hero):
                    enemylist.remove(em)
                # 判断敌机是否炸毁
                if em.over():
                    score += 1
                    enemylist.remove(em)

            # 英雄机炸毁,游戏结束
            if hero.over():
                play = False

            #pygame.display.flip()
            pygame.display.update()

        else:
            gameover = pygame.image.load(settings.gameover_image)
            screen.blit(gameover, ((settings.screen_width-gameover.get_width())/2,
                        (settings.screen_height-gameover.get_height())/2))
            pygame.display.update()
            #print("Game Over")
            #continue

        # 检查按键事件
        usedbullet = check_event(hero,usedbullet)

        time.sleep(0.04)



# 判断是否为主运行程序,是则调用 main()
if __name__ == '__main__':
    main()

对于全局变量报错:UnboundLocalError: local variable ‘usedbullet’ referenced before assignment的解释:

在程序中设置的 sum 属于全局变量,而在函数中没有 sum 的定义,根据python访问局部变量和全局变量的规则:当搜索一个变量的时候,python先从局部作用域开始搜索,如果在局部作用域没有找到那个变量,那样python就在全局变量中找这个变量,如果找不到抛出异常(NAMEERROR或者Unbound-LocalError,这取决于python版本。)

如果内部函数有引用外部函数的同名变量或者全局变量,并且对这个变量有修改.那么python会认为它是一个局部变量,又因为函数中没有sum的定义和赋值,所以报错。

其中if __name__ == '__main__':是只作为脚本执行时,执行main()函数,如果作为包导入其他脚本中时,则不执行。

这个游戏修改了很多天,对于初学者,需要理解面向对象思维的编程特点。当然如果按照之前的面向过程的思路来写的话,代码可能会短一些,但是修改起来就会有很大的问题,这也是面向对象和面型过程二者之间的区别,我会在下一个专栏中介绍这些,期待自己学习的进步吧!

这个飞机大战的游戏还是未完成的状态,我们可以在以上代码中进行一些修改,为这个游戏增加一些功能,让这个游戏变得更好玩,当然,在我们增加功能的过程中,也是对Python编程和pygame模块的更深入的理解。

参考:

  1. http://c.biancheng.net/view/2371.html
  2. https://www.runoob.com/w3cnote/python-unboundlocalerror.html
  3. https://gitee.com/luhuadong/Python_Learning/repository/archive/master.zip
发布了19 篇原创文章 · 获赞 10 · 访问量 4686

猜你喜欢

转载自blog.csdn.net/weixin_45755966/article/details/104498512