python实现飞机大战游戏(pygame版)

目录

 

简介

实现过程

结语


简介

使用python实现pygame版的飞机大战游戏;

  环境:Windows系统+python3.8.0

  游戏规则:

1.点击“PLAY”或者按键“P”开始游戏;

2.敌机根据设置频率从顶部随机位置生成,生成后向下移动;

3.飞船在底部中间生成,玩家使用上下左右键控制飞船移动,敲击空格键发射子弹;

4.子弹打到敌机,该敌机产生爆炸效果并累计分数到右上角;

5.消灭10只飞机后,等级升高,敌机生成频率变快,下落速度也变快;

6.当三条命都消失了,游戏结束。

  游戏运行效果如下:

实现过程

  1. 新建文件“file.py”,用来存储信息到文件和读取文件的信息,本例用来存储和读取最高分;
    import pickle
    # filename = 'file/stats.pkl'
    # 存储信息到文件
    def save_file(obj, filename):
        statsObj = load_file(filename)
        if statsObj == 0:
            # 不存在文件时,直接保存字典
            with open(filename, 'wb') as f:
                pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)
        else:
            # 存在文件时,只修改文件中的最高分
            for key, val in statsObj.items():
                # 获取文件最高分的值(当文件字段不止一个时候使用)
                if key == 'highScore':
                    statsObj[key] = obj['highScore']
            obj = statsObj
        with open(filename, 'wb') as f:
            pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)
        
    # 读取信息
    def load_file(filename):
        try:
            with open(filename, 'rb') as f:
                return pickle.load(f)
        except FileNotFoundError:
            # 不存在文件则输入错误信息
            msg = "Sorry, the file " + filename + " does not exist."
            print(msg)
            return 0
     
    # obj = {'highScore': 20, 'points': 5}
    # obj = {'highScore': 50}
    # save_file(obj, filename)
    # filedata = load_file(filename)
    # print(filedata)
  2. 新建文件settings.py,用来定义一些必须的基本属性和初始值;
    import file as f
    class Settings():
        def __init__(self):
            self.screen_width = 480
            self.screen_height = 660
            self.bg_color = (230, 230, 230)
            # 子弹设置(宽、高、颜色、最大数量)        
            self.bullet_width = 5
            self.bullet_height = 15
            self.bullet_color = 255, 255, 255
            # 敌机移动频率
            self.enemy_frequency = 0
            # 加快游戏节奏的速度
            self.speedup_scale = 1.1
            # 分数的提高速度
            self.score_scale = 1.5
     
            self.initialize_settings()
            # 初始化统计信息
            self.reset_stats()
            # 统计信息文件路径
            self.filename = 'file/stats.pkl'
            # 游戏刚启动时处于非活动状态
            self.game_active = False
            # 读取文件的最高分,在任何情况下都不应重置最高得分
            statsObj = f.load_file(self.filename)
            if statsObj == 0:
                # 不存在文件则显示最高分0
                highScore = 0
            else:
                for key, val in statsObj.items():
                    # 获取文件最高分的值(当文件字段不止一个时候使用)
                    if key == 'highScore':
                        highScore = val
            self.high_score = highScore
             
        def initialize_settings(self):
            """初始化随游戏进行而变化的设置"""
            self.player_move_speed = 2.5
            self.bullet_speed = 3
            self.enemy_move_speed = 1
            # 记分
            self.one_points = 50
        def increase_speed(self):
            """提高速度设置"""
            # self.player_move_speed *= self.speedup_scale
            self.bullet_speed *= self.speedup_scale
            self.enemy_move_speed *= self.speedup_scale
            self.one_points = int(self.one_points * self.score_scale)
        def reset_stats(self):
            """初始化在游戏运行期间可能变化的统计信息"""
            # 可射失的数量
            self.player_limit = 3
            # 射击分数
            self.score = 0
            # 等级
            self.level = 1
            # 打中多少矩形升一级
            self.level_number = 10
            # 生成敌机频率间隔
            self.enemy_frequency_space = 50
  3. 新建文件enemy.py,用来定义敌机类(位置topleft随机生成)和声明方法move;
    import pygame
    import random
    from pygame.sprite import Sprite
    class Enemy(Sprite):
        def __init__(self, enemy_down_imgs, settings):
            super(Enemy, self).__init__()
            self.image = pygame.image.load('images/enemy1.png')
            self.rect = self.image.get_rect() 
            self.rect.topleft = [random.randint(0, settings.screen_width - self.rect.width), 0]
            self.down_imgs = enemy_down_imgs
            self.speed = settings.enemy_move_speed
            self.down_index = 0
    
        # 敌机移动,边界判断及删除在游戏主循环里处理
        def move(self):
            self.rect.top += self.speed
  4. 新建文件player.py,用来定义玩家类(可上下左右移动)和相应的方法;
    import pygame
    from pygame.sprite import Sprite
    class Player(Sprite):
        def __init__(self, settings, screen):
            super(Player, self).__init__()
            self.settings = settings
            self.screen = screen
            self.screen_rect = self.screen.get_rect()
            # 引入飞船图片并定位
            self.image = pygame.image.load('images/player.png')        
            self.rect = self.image.get_rect()
            self.rect.centerx = self.screen_rect.centerx
            self.rect.bottom = self.screen_rect.bottom
     
            # 移动标志
            self.move_left = False
            self.move_right = False
            self.move_down = False
            self.move_up = False
        def rotate(self, angle):
            # 图片旋转
            self.image = pygame.transform.rotate(self.image, angle)
        def scale(self, multiple):
            # 图片缩放
            self.image = pygame.transform.smoothscale(self.image, (multiple, multiple))
        def update(self):
        	if self.move_left and self.rect.left > self.screen_rect.left:
        		self.rect.centerx -= self.settings.player_move_speed
        	if self.move_right and self.rect.right < self.screen_rect.right:
        		self.rect.centerx += self.settings.player_move_speed
        	if self.move_down and self.rect.bottom < self.screen_rect.bottom:
        		self.rect.centery += self.settings.player_move_speed
        	if self.move_up and self.rect.top > 0:
        		self.rect.centery -= self.settings.player_move_speed
        def draw_player(self):
            """绘制飞船到屏幕"""
            self.screen.blit(self.image, self.rect)
  5. 新建文件“bullet.py”,用来定义子弹类(位置在飞船的顶部,并往上移动)和相应的方法;
    import pygame
    from pygame.sprite import Sprite
    class Bullet(Sprite):
        """ 一个对飞船发射的子弹进行管理的类 """
        def __init__(self, settings, screen, player):
            """ 在飞船所处的位置创建一个子弹对象 """
            super(Bullet, self).__init__()
            self.screen = screen
            #  在 (0,0) 处创建一个表示子弹的矩形,再设置正确的位置
            self.rect = pygame.Rect(0, 0, settings.bullet_width, settings.bullet_height)
            self.rect.centerx = player.rect.centerx
                # 飞船顶部
            self.rect.bottom = player.rect.top
            # 存储用小数表示的子弹位置
            self.y = float(self.rect.y)
            self.color = settings.bullet_color
            self.speed = settings.bullet_speed
        def update(self):
            """向上移动子弹"""
            # 更新表示子弹位置的小数值(子弹往右)
            self.y -= self.speed
            # 更新表示子弹的rect的位置
            self.rect.y = self.y
        def draw_bullet(self):
            """在屏幕上绘制子弹"""
            pygame.draw.rect(self.screen, self.color, self.rect)
  6. 新建文件“button.py”,用来定义按钮类和相应方法,本例使用于绘制“PLAY”按钮;
    import pygame.font
    class Button():
        def __init__(self, screen, msg):
            """初始化按钮的属性"""
            self.screen = screen
            self.screen_rect = screen.get_rect()
            # 设置按钮的尺寸和其他属性
            self.width, self.height = 100, 30
            self.button_color = (216, 30, 6)
            self.text_color = (255, 255, 255)
            self.font = pygame.font.SysFont(None, 36)
            # 创建按钮的rect对象,并使其居中
            self.rect = pygame.Rect(0, 0, self.width, self.height)
            self.rect.center = self.screen_rect.center
            # 按钮的标签只需创建一次
            self.prep_msg(msg)
        def prep_msg(self, msg):
            """将msg渲染为图像,并使其在按钮上居中"""
            self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
            self.msg_image_rect = self.msg_image.get_rect()
            self.msg_image_rect.center = self.rect.center
        def draw_button(self):
            # 绘制一个用颜色填充的按钮,再绘制文本
            self.screen.fill(self.button_color, self.rect)
            self.screen.blit(self.msg_image, self.msg_image_rect)
  7. 新建文件“scoreboard.py”,用来定义记分板,本例使用于绘制左上角飞船(生命数)、顶部中间的“最高分”、右上角的“积分”和“等级”;
    import pygame.font
    from pygame.sprite import Group
    from player import Player
    class Scoreboard():
        """显示得分信息的类"""
        def __init__(self, settings, screen):
            """初始化显示得分涉及的属性"""
            self.screen = screen
            self.screen_rect = screen.get_rect()
            self.settings = settings
            # 显示得分信息时使用的字体设置
            self.text_color = (255, 255, 255)
            self.font = pygame.font.SysFont(None, 30)
            # 飞船缩放值
            self.scaleValue = 20
            # 准备初始得分图像\最高得分\等级
            self.prep_score()
            self.prep_high_score()
            self.prep_level()
            self.prep_players()
        def prep_score(self):
            """将得分转换为渲染的图像"""
            rounded_score = int(round(self.settings.score, -1))
            score_str = '{:,}'.format(rounded_score)
            self.score_image = self.font.render(score_str, True, self.text_color)
            # 将得分放在屏幕右上角
            self.score_rect = self.score_image.get_rect()
            self.score_rect.right = self.screen_rect.right -20
            self.score_rect.top = 10
        def prep_high_score(self):
            """ 将最高得分转换为渲染的图像 """
            high_score = int(round(self.settings.high_score, -1))
            high_score_str = "{:,}".format(high_score)
            self.high_score_image = self.font.render(high_score_str, True, self.text_color)
            # 将最高得分放在屏幕顶部中央
            self.high_score_rect = self.high_score_image.get_rect()
            self.high_score_rect.centerx = self.screen_rect.centerx
            self.high_score_rect.top = self.score_rect.top
        def prep_level(self):
            """将等级转换为渲染的图像"""
            self.level_image = self.font.render(str(self.settings.level), True, self.text_color)
            # 将等级放在得分下方
            self.level_rect = self.level_image.get_rect()
            self.level_rect.right = self.score_rect.right
            self.level_rect.top = self.score_rect.bottom + 10
        def prep_players(self):
            """ 显示还余下多少艘飞船 """
            self.players = Group()
            for player_number in range(self.settings.player_limit):
                player = Player(self.settings, self.screen)
     
                # 缩放球大小并赋值位置
                player.scale(self.scaleValue)
                player.rect.x = 10 + player.rect.width * player_number * 0.5
                player.rect.y = self.score_rect.top
                self.players.add(player)
        def show_score(self):
            """在屏幕上显示得分"""
            self.screen.blit(self.score_image, self.score_rect)
            self.screen.blit(self.high_score_image, self.high_score_rect)
            self.screen.blit(self.level_image, self.level_rect)
            #  绘制飞船
            self.players.draw(self.screen)
  8. 新建文件“game_functions.py”,存放跟游戏有关的所有业务逻辑函数(代码有详细的注释信息);
    import sys
    import pygame
    from bullet import Bullet
    from enemy import Enemy
    import file as f
     
    # 事件
    def check_events(settings, screen, player, play_button, scoreboard, bullets, fireSound):
        """ 响应按键和鼠标事件 """
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                save_file(settings)
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                check_keydown_events(event, settings, screen, player, scoreboard, bullets, fireSound)
            elif event.type == pygame.KEYUP:
                check_keyup_events(event, player)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pygame.mouse.get_pos()
                check_play_button(settings, play_button, scoreboard, mouse_x, mouse_y)
    def check_keydown_events(event, settings, screen, player, scoreboard, bullets, fireSound):
        """ 响应按键 """
        if event.key == pygame.K_DOWN:
            player.move_down = True
        elif event.key == pygame.K_UP:
            player.move_up = True
        elif event.key == pygame.K_LEFT:
            player.move_left = True
        elif event.key == pygame.K_RIGHT:
            player.move_right = True
        elif event.key == pygame.K_SPACE:
            fireSound.play()
            # 点击空格键创建一颗子弹
            fire_bullet(settings, screen, player, bullets)
        elif event.key == pygame.K_p:
            start_game(settings, scoreboard)
        elif event.key == pygame.K_q:
            save_file(settings)
            sys.exit()
    def check_keyup_events(event, player):
        """ 响应松开 """
        if event.key == pygame.K_DOWN:
            player.move_down = False
        elif event.key == pygame.K_UP:
            player.move_up = False
        elif event.key == pygame.K_LEFT:
            player.move_left = False
        elif event.key == pygame.K_RIGHT:
            player.move_right = False
    def check_play_button(settings, play_button, scoreboard, mouse_x, mouse_y):
        """在玩家单击Play按钮时开始新游戏"""
        button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
        if button_clicked and not settings.game_active:
            start_game(settings, scoreboard)
    def start_game(settings, scoreboard):
        """开始游戏"""
        # 重置游戏设置
        settings.initialize_settings()
        # 隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        settings.reset_stats()        
        settings.game_active = True
        # 重置记分牌图像
        scoreboard.prep_score()
        scoreboard.prep_high_score()
        scoreboard.prep_level()
        scoreboard.prep_players()
    def save_file(settings):
        # 保持文件
        obj = {'highScore': settings.high_score}
        f.save_file(obj, settings.filename)
     
    # 敌机
    def update_enemies(settings, screen, scoreboard, enemies, enemies_down, enemy_down_imgs, player, bullets, explosiveSound):  
        # 生成敌机,需要控制生成频率
        if settings.enemy_frequency % settings.enemy_frequency_space == 0:        
            enemy1 = Enemy(enemy_down_imgs, settings)
            enemies.add(enemy1)
        settings.enemy_frequency += 1
        if settings.enemy_frequency >= 100:
            settings.enemy_frequency = 0
        for enemy in enemies:
            # 移动敌机
            enemy.move()
            # 敌机与玩家飞机碰撞效果处理 两个精灵之间的圆检测
            if pygame.sprite.collide_circle(enemy, player):
                enemies_down.add(enemy)
                enemies.remove(enemy)
                settings.player_limit -= 1
                scoreboard.prep_players()
                break
            # 移动出屏幕后删除飞机
            if enemy.rect.top < 0:
                enemies.remove(enemy)
        # 敌机被子弹击中效果处理
        # 将被击中的敌机对象添加到击毁敌机 Group 中,用来渲染击毁动画
        # 方法groupcollide()是检测两个精灵组中精灵们的矩形冲突
        enemies1_down = pygame.sprite.groupcollide(enemies, bullets, True, True)
        if enemies1_down:
            explosiveSound.play()
            # 计算分数并渲染
            for enemys in enemies1_down.values():
                settings.score += settings.one_points * len(enemys)
                scoreboard.prep_score()
            # 渲染最高分
            check_high_score(settings, scoreboard)
            # 等达到等级数量升级并渲染新等级
            settings.level_number -= 1
    
            if settings.level_number == 0:
                settings.increase_speed()
                settings.level += 1
                scoreboard.prep_level()
                # 还原为4(同settings一致)
                settings.level_number = 10
                # 加快生成敌机            
                if settings.enemy_frequency_space > 10:
                    settings.enemy_frequency_space -= 10
        # 遍历key值 返回的碰撞敌机
        for enemy_down in enemies1_down:
            # 点击销毁的敌机到列表
            enemies_down.add(enemy_down)
        # 敌机被子弹击中效果显示
        for enemy_down in enemies_down:
            if enemy_down.down_index == 0:
                pass
            if enemy_down.down_index > 7:
                enemies_down.remove(enemy_down)
                continue
            #显示碰撞图片
            screen.blit(enemy_down.down_imgs[enemy_down.down_index // 2], enemy_down.rect)
            enemy_down.down_index += 1
        # 显示精灵
        enemies.draw(screen)    
     
    # 子弹
    def fire_bullet(settings, screen, player, bullets):
        """创建子弹"""
        new_bullet = Bullet(settings, screen, player)
        bullets.add(new_bullet)
    def update_bullets(screen, bullets):
        """更新子弹的位置,并删除已消失的子弹"""
        # 更新子弹的位置
        bullets.update()
     
        # 删除已消失的子弹并同时更新飞船的生命
        for bullet in bullets.copy():
            if bullet.rect.top < screen.get_rect().top:
                bullets.remove(bullet)
     
    # 分数
    def check_high_score(settings, scoreboard):
        """检查是否诞生了新的最高得分"""
        if settings.score > settings.high_score:
            settings.high_score = settings.score
            scoreboard.prep_high_score()
     
    # 屏幕
    def update_screen(settings, screen, player, play_button, scoreboard, enemies, bullets):
        """ 更新屏幕上的图像,并切换到新屏幕 """
        # 绘制飞船到屏幕
        player.draw_player()
        # 绘制子弹到屏幕 
        for bullet in bullets.sprites():
            bullet.draw_bullet()
        # 渲染记分牌信息
        scoreboard.show_score()
        # 
        if settings.player_limit == 0:
            settings.game_active = False
            settings.reset_stats()
            # 清空矩形列表和子弹列表
            enemies.empty()
            bullets.empty()
    
            screen_rect = screen.get_rect()
            player.rect.centerx = screen_rect.centerx
            player.rect.bottom = screen_rect.bottom
        
        # 如果游戏处于非活动状态,就绘制 Play 按钮
        if not settings.game_active:
            play_button.draw_button()
        # 让最近绘制的屏幕可见
        pygame.display.flip()
  9. 新建文件shootingenemy.py,主函数用来初始化程序,并同步更新程序的信息;
    import pygame
    from pygame.sprite import Group
    from settings import Settings
    from button import Button
    from player import Player
    import game_functions as gf
    from scoreboard import Scoreboard
     
    def run_game():
        pygame.init()
        # 初始化全部音频,并加载爆炸声音乐
        pygame.mixer.init()
            # 等待1s
        pygame.time.delay(1000)
        pygame.mixer.music.load('file/bgsound.mp3')
            # -1代表无限循环(背景音乐)
        pygame.mixer.music.play(-1)
            # 爆炸声
        explosiveSound = pygame.mixer.Sound('file/explosiveSound.wav')
            # 枪声
        fireSound = pygame.mixer.Sound('file/fireSound.wav')
        # 游戏循环帧率设置
        clock = pygame.time.Clock()
     
        settings = Settings()
        screen = pygame.display.set_mode((settings.screen_width, settings.screen_height))
        # 全屏显示
        # screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
        pygame.display.set_caption('飞机大战')
        # 左上角图标
        ic_launcher = pygame.image.load('images/ic_launcher.png').convert_alpha()
        pygame.display.set_icon(ic_launcher)
        # 背景图
        background = pygame.image.load('images/background.png').convert_alpha()
    
        # 敌机图片
        enemy_img1= pygame.image.load('images/enemy1.png')
        enemy_img2= pygame.image.load('images/enemy2.png')
        enemy_img3= pygame.image.load('images/enemy3.png')
        enemy_img4= pygame.image.load('images/enemy4.png')
        # 敌机不同状态的图片列表,多张图片展示为动画效果
        enemy_down_imgs = []
        enemy_down_imgs.append(enemy_img1)
        enemy_down_imgs.append(enemy_img2)
        enemy_down_imgs.append(enemy_img3)
        enemy_down_imgs.append(enemy_img4)
        # 储存敌机
        enemies = Group()
        # 存储被击毁的飞机,用来渲染击毁动画
        enemies_down = Group()    
    
        # 创建Play按钮
        play_button = Button(screen, 'Play')
     
        # 创建飞船
        player = Player(settings, screen)
        # 创建子弹的编组
        bullets = Group()
        # 创建记分牌
        scoreboard = Scoreboard(settings, screen)
     
        while True:
            # 绘制背景
            screen.blit(background, (0, 0))
            # 控制游戏最大频率
            clock.tick(60)
    
            # 检查玩家输入(不加会导致一直加载)
            gf.check_events(settings, screen, player, play_button, scoreboard, bullets, fireSound)
            if settings.game_active:
                # 更新飞船位置
                player.update()
                # 更新敌机
                gf.update_enemies(settings, screen, scoreboard, enemies, enemies_down, enemy_down_imgs, player, bullets, explosiveSound)
                # 更新子弹位置
                gf.update_bullets(screen, bullets)
            # 更新屏幕信息
            gf.update_screen(settings, screen, player, play_button, scoreboard, enemies, bullets)
     
    run_game(), 
  10. 在文件shootingenemy.py目录路径下,执行命令“python shootingenemy.py”弹出窗口,即可对其操作游玩。

结语

该游戏加入了背景音乐、射击声、子弹射中敌机的爆炸声和爆炸效果、生命数、积分、等级、最高分和开始按钮等元素,大家也可以自行加入其它好玩的元素。

发布了32 篇原创文章 · 获赞 21 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/king0964/article/details/104170578