基于Pygame的飞机大战游戏|“朝闻道”知识分享大赛”

i oy 2023-12-27 21:30:33

这是我参加“朝闻道”知识分享大赛的第3篇文章。

飞机大战游戏的设计与实现

目录

一  目的

二  需求分析

1 问题描述

2 实现功能

3 开发平台及工具介绍

三  概要设计

1. 系统设计思想

四 详细设计

1 主要流程图

2 基本类的设计

3 功能模块设计

五 调试与分析

 1 调试

2 调试中遇到的问题

六 测试结果

1.界面显示测试与分析

2.飞机与子弹碰撞测试分析

3 历史记录保存测试与分析

六 总结

七、其他


一  目的

这次课程旨在使读者学会分析研究面向对象编程的特性,学会Python编程方法,以便选择合适的数据结构和逻辑结构,以及相应的运算,把现实世界中的问题转化为计算机内部的表示和处理。

《面向对象程序设计(Python)》课程的主要目的是使学生一步理解和掌握课堂上所学各种基本类型的变量、数据类型、类、用户输入、文件操作等,以及它们在程序中的使用方法;掌握软件设计的基本内容和设计方法,并培养学生进行规范化软件设计的能力;掌握使用各种计算机资料和有关参考资料,提高学生进行程序设计的基本能力。


  需求分析

1 问题描述

本次课程设计题目是 “飞机大战游戏设计”,实现基于面向对象程序设计的方法,使用Python编程语言,以Pygame模块实时对游戏进行研发,提出飞机大战小游戏开发方案。通过各种优化调整,实现飞机的飞行移动、击落,计分等功能,实现飞机大战的开发,增加游戏体验。

2 实现功能

  1. 实现飞机移动、子弹发射;
  2. 飞机持续按键移动和飞机碰撞自毁检测;
  3. 对基类的抽取,是代码更具有层次性和简化重复代码;
  4. 对判断以及删除越界子弹、判断敌机与子弹碰撞、本机与敌机碰撞。

3 开发平台及工具介绍

开发软件:Python 3.7

使用系统:Windows 10


三  概要设计

1. 系统设计思想

本次设计基于面向对象的程序设计方法,使用Python编程语言提供资源结构,以Pygame模块实时对游戏进行研发,提出了飞机大战小游戏二次开发方案。通过各种优化调整,实现了飞机的飞行移动、击落,计分等功能,而且拓展了游戏的等级提升功能、界面打印功能、游戏奖励机制等等,实现了飞机大战的二次开发,丰富了游戏体验。

Pygame是Python的一个第三方库, 搭载了基于OpenGL的图形库和优质的音频库, 可以快速上手制作2D游戏的原型。Pygame的API比较偏底层, 开发人员在编程时具有很大的自由度, 同时具有了很强的可定制性。

按照游戏工业市场的飞机游戏开发需求,分析游戏的基本功能,在此基础上增加游戏的各类功能模块完善游戏。本设计所用到的编程语言是Python,版本是Python3.7,开发工具为PyCharm,调用Pygame模块,对游戏进行开发、运行及测试。游戏程序编辑、编译、调试、发布所有过程都是基于PC机平台运行。

2. 模块划分

经过对题目的分析,本游戏包括主要两大模块,本机模块、敌机模块和子弹模块。本机模块与敌机模块通过子弹模块进行连接,若本机子弹击中敌机,则敌机坠毁,玩家得分,若本机与敌机碰撞,则本机与敌机一同坠毁且游戏结束。玩家得分实时记录显示在游戏界面中,若玩家当前得分超过历史记录最高分,则将当前得分记录到.txt文件中。综上,本游戏设计共有三个模块,如图3-1所示

图3-1 系统功能模块


详细设计

1 主要流程图

图4-1 主函数流程图

 

2 基本类的设计

2.1 敌机类

    敌机,向我方基地进攻,不断向我方靠近。敌机类应该包括属性:图片、坐标和移动速度,考虑到其移动是固定的,所以不需要设计敌机移动的方法,只需要不断更新其位置即可。其框图如下图:

图4-2 敌机类

 2.2 本机类

    本机需要保卫基地,所以配以射击功能,可以射出子弹,且可以上下左右移动。其框图如下:

图4-3 本机类

 2.3 子弹类

    本机保卫基地时,需要射出子弹,每一个子弹都是一个实例,有其属性(图片、坐标、速度),会一直更新坐标位置。其框图如下:

图4-4 子弹类

 

3 功能模块设计

3.1 初始化

本游戏初始化包括利用pygame库初始化屏幕界面大小,屏幕界面背景图加载,音频文件加载,敌机和我方飞机以及子弹图片加载。

3.2 游戏难度设置

本游戏会随着玩家得分的增加,敌机的飞行速度会相应提高,每得分20,敌机速度上升一个档次。在设计的时候,没考虑到玩家得分的计算和敌机速度的计算都是在一个循环内的,用“if判断分数是否能被20整除,则执行敌机速度加一”的方式,会出现“敌机速度一直加,出现敌机向我方猛冲”的bug。在我不断尝试之后,我想到了利用标志位来防止速度档次一直改变。效果虽然能实现,但是失去了python作为简洁的优势,代码比较混乱。经过指导老师的指点后,用分数除20取整的数值作为速度档次,代码简洁优雅。

3.3 子弹碰撞、飞机碰撞检测模块

玩家飞机保卫基地,向敌机发起防抗,射出子弹。子弹怎么样才算射中敌机,敌机与本机如何才算是碰撞,都是需要思考的问题。刚开始我想到的方法是,以两个实例之间的距离作为依据,来判断两个实例是否碰撞。但需要不断调试,找到最佳距离作为依据。其后看到pygame库里面直接由检测碰撞的函数groupcollide检测是否碰撞。

3.4 玩家得分计算及分数显示模块

在检测到子弹与敌机碰撞之后,记录groupcollide的返回值到列表中,作为已坠毁的敌机元素,根据列表当前长度,计算当前分数。并利用函数screen.blit,将分数显示到游戏界面上。


调试与分析

 1 调试

       经过反复测试,飞机大战游戏最终可以流畅运行,而且容错能力较好,稳定性很强。下面是一些简单说明:

      1 print方法

在调试过程中,可以直接用print方法打印出想要查看的变量的值,在本次游戏设计调试中,它在我调试坐标的时候发挥了无可比拟的作用。

       2 try……except

有很多次游戏运行的时候因为突然程序代码突然报错,导致游戏运行不流畅,总是卡顿。在百度搜索了很多防止报错的方法,其中的try……except语句是最好用的。

2 调试中遇到的问题

2.1 子弹和敌机多次撞击

由于撞击时没有及时移除精灵组,下一次循环对象还没有kill(),kill就不能进行轮播。解决办法:撞毁的时候,直接移出精灵组,然后进行更新的方法。实验证明可行。

2.2 进行了精灵的update却不显示。

调试发现,显示精灵的blit语句出现在显示背景的语句前面,导致每次更新的精灵图片都被背景图给覆盖了。

       2.3 没注意全局变量的使用

Python的全局变量的使用与C语言有很大差别,C语言只要在函数之外定义的变量,可以在函数内部直接使用,Python全局变量的使用全局变量进行赋值时,需要用关键字global声明。

2.4 为什么英雄死掉了,还是会发生碰撞

    这是因为,虽然我们把英雄加入到了英雄组,当时队伍中的英雄很少,为了方便我们碰撞的时候不是从队伍中取,而是直接就该英雄对象进行检测,这时候,尽管英雄kill()了,但是rect并没有丢失。对象没有被立即收回,它的rect还在,还能继续被碰撞检测。


测试结果

1.界面显示测试与分析

代码运行后,游戏界面显示如图5-1。经测试,各个功能均能跳入对应的运行模块中,系统可以成功退出,运行速度良好,能够实现飞机移动,子弹设计,运行界面如图5-1。

图5-1 游戏界面

 

2.飞机与子弹碰撞测试分析

当子弹与敌机发生接触时,敌机与子弹同时回收,且玩家得分加1。下图测试结果显示,当子弹与敌机碰撞时,子弹与敌机销毁,同时玩家的分数从零变成了1。

图5-2 子弹与敌机碰撞检测

 

3 历史记录保存测试与分析

当玩家的分数大于历史最优记录时,会更新存放在文档中的历史记录数据,并在下一次游戏开始时显示在游戏界面中。如,当我把历史最高分改为3后,在突破记录后文档中的数据变为了20,同时游戏结束界面也会显示为,“新纪录”。同时文档存放中的数据也会更新为20,如下图所示

图5-3 打破历史记录游戏结束界面

 


总结

通过本次课程设计,是我更加扎实地掌握了pycharm、python、pygame方面的知识,在设计过程中虽然遇到了很多问题,但通过一次又一次的思考、一遍又一遍的检查终于找到了问题所在,也暴露了前期我在这方面的知识欠缺和经验不足。实践出真知,只有通过亲自动手制作,是我们掌握的知识不再是纸上谈兵。

Python是一种简单易学、功能强大的编程语言,他有高效率的高层数据结构,能简单而高效的实现面向对象程序设计。它的简洁语法和对动态输入的支持,再加上解释性语言的本质,使得它在大多数平台上的很多领域都是一个理想的脚本语言,特别适用于快速的应用程序开发。

通过这次课程设计使我懂得了理论与实际相结合是很重要的,只有理论知识是远远不够的,只有把所学的理论知识与实践相结合起来,从理论中得出结论,才能真正为社会服务,从而提升自己的实际动手水平和独立思考的水平。在设计的过程中遇到问题,能够说得是困难重重,但可喜的是最终都得到了解决。


七、其他

 主程序代码

while True:
    # 初始化游戏
    # 定义画面帧率
    FRAME_RATE = 100
    # 定义动画周期
    ANIMATE_CYCLE = 30

    ticks = 0
    clock = pygame.time.Clock()
    offset = {pygame.K_LEFT: 0, pygame.K_RIGHT: 0, pygame.K_UP: 0, pygame.K_DOWN: 0}
    pause = 0
    pygame.init()  # 初始化pygame
    pygame.mixer.init()
    pygame.mixer.music.load('./resources/music/background.mp3')
    pygame.mixer.music.play(10000)
    os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (440, 70)
    screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT], 0, 32)  # 初始化窗口
    pygame.display.set_caption('飞机大战')  # 设置窗口标题

    background = pygame.image.load('./resources/image/background.jpg')  # 载入背景图
    shoot_img = pygame.image.load('./resources/image/shoot.jpg')  # 载入飞机图片
    sprite_img = pygame.image.load('./resources/image/sprite.png')  # 载入子弹图片
    enemy_img = pygame.image.load('./resources/image/enemy.jpg')  # 载入敌军图片
    gameover_img = pygame.image.load('./resources/image/gameover.jpg')  # 游戏结束图
    font1 = pygame.font.Font('./resources/font/yh.ttf', 60)  # 文本输入方法
    font2 = pygame.font.Font('./resources/font/yh.ttf', 30)  # 文本输入方法
    font3 = pygame.font.Font('./resources/font/yh2.ttf', 15)  # 文本输入方法

    # 用subsurface剪切读入的图片
    hero_surface = []
    hero_surface.append(shoot_img.subsurface(pygame.Rect(0, 0, PLANE_WIDTH, PLANE_HEIGHT - 20)))
    hero_surface.append(shoot_img.subsurface(pygame.Rect(0, 0, PLANE_WIDTH, PLANE_HEIGHT)))
    hero_pos = [200, 500]

    # bullet1图片
    bullet1_surface = sprite_img.subsurface(pygame.Rect(0, 0, 7, 11))
    # 创建玩家
    hero = Hero(hero_surface[0], hero_pos)
    enemy1_surface = enemy_img.subsurface(pygame.Rect(0, 0, 42, 30))
    enemy_group = pygame.sprite.Group()
    enemy1_down_group = pygame.sprite.Group()

    highest_score, score = 0, 0
    recordbreaking = False
    dir = '.\plane_score.txt'
    try:
        f = open(dir, mode='r', encoding='utf-8')
        highest_score = int(f.read())
    except FileNotFoundError:
        f = open(dir, mode='w', encoding='utf-8')
    f.close()

    # 事件循环(main loop)
    while True:
        # 控制游戏最大帧率
        clock.tick(FRAME_RATE)
        # 绘制背景
        screen.blit(background, (0, 0))

        # 改变飞机图片制造动画
        if ticks >= ANIMATE_CYCLE:
            ticks = 0

        if hero.is_hit:
            break
        else:
            hero.image = hero_surface[ticks // (ANIMATE_CYCLE // 2)]
            # print(f"aa={ticks // (ANIMATE_CYCLE // 2)}\n")

        # 绘制飞机

        screen.blit(hero.image, hero.rect)
        ticks += 1

        # 射击
        if ticks % 10 == 0:  # 每秒子弹射出数量
            hero.single_shoot(bullet1_surface)
        # 控制子弹
        hero.bullets1.update()
        # 绘制子弹
        hero.bullets1.draw(screen)

        # 产生敌机
        if ticks % 30 == 0:
            enemy = Enemy(enemy1_surface,
                          [randint(0, SCREEN_WIDTH - enemy1_surface.get_width()), -enemy1_surface.get_height()],
                          enemy_speed)
            enemy_group.add(enemy)
        # 绘制敌机
        enemy_group.draw(screen)
        value = enemy_group.update()
        # 检测敌机与子弹的碰撞
        enemy1_down_group.add(
            pygame.sprite.groupcollide(enemy_group, hero.bullets1, True, True))  # 检测敌机组根子弹组是否发生碰撞 碰撞就销毁掉
        score = len(enemy1_down_group.spritedict)

        if score > highest_score and recordbreaking == False:
            pygame.mixer.music.load('./resources/music/recordbreaking.mp3')
            pygame.mixer.music.play()
            recordbreaking = True
        #     20 40 60 80 100

        # 积分除以20取整
        # if old_score != int(score):
        #     print("20%==0")
        #     score_flag = True
        # if int(score) in [i * 10 for i in range(0, 1000) if i % 2 == 0 and i != 0] and score_flag:
        #     score_flag = False
        #     old_score = int(score)
        #     enemy_speed += 1
        enemy_speed = score//20+1
        print(enemy_speed)

        # 显示分数
        text2 = "分数:" + str(score)
        score_text = font3.render(text2, True, [0, 0, 0])
        left = (300, 15)  # 获取坐标
        textpos2 = score_text.get_rect(center=left)  # 获取设置后新的坐标区域
        screen.blit(score_text, textpos2)
        text3 = "最高分:" + str(highest_score)
        highest_score_text = font3.render(text3, True, [0, 0, 0])
        left = (100, 15)  # 获取坐标
        textpos3 = highest_score_text.get_rect(center=left)  # 获取设置后新的坐标区域
        screen.blit(highest_score_text, textpos3)

        # 检测敌机与玩家的碰撞
        # 如果敌军越过玩家飞机,则游戏结束
        enemy1_down_list = pygame.sprite.spritecollide(hero, enemy_group, True)
        if len(enemy1_down_list) > 0:  # 不空
            enemy1_down_group.add(enemy1_down_list)
            hero.is_hit = True

        for enemy in enemy_group.spritedict:
            if enemy.rect[1] >= SCREEN_HEIGHT:
                hero.is_hit = True
        # 更新屏幕
        pygame.display.update()

        # 处理游戏退出
        # 从消息队列中循环取
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()

            #  Python中没有 switch-case 所以大多用字典类型替代
            # 控制方向
            if event.type == pygame.KEYDOWN:
                # 如果空格键按下
                if event.key == pygame.K_SPACE:
                    pause = not pause
                    # 游戏暂停
                    if pause == True:
                        pygame.mixer.music.pause()
                    # 如果空格键再次按下
                    while pause:
                        for event in pygame.event.get():
                            if event.type == pygame.QUIT:
                                pygame.quit()
                                exit()
                            elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                                pause = not pause
                                pygame.mixer.music.unpause()
                # 方向键按下
                elif event.key in offset:
                    offset[event.key] = hero.speed
            # 方向键抬起 停止移动
            elif event.type == pygame.KEYUP:
                if event.key in offset:
                    offset[event.key] = 0

        # 移动飞机
        hero.move(offset)

    pygame.mixer.music.stop()  # 音乐停止
    pygame.mixer.music.load('./resources/music/gameover.mp3')
    pygame.mixer.music.play()

    text1 = "游戏结束"
    gameover_text = font1.render(text1, True, [255, 0, 0])
    center = (background.get_width() / 2, background.get_height() / 4)  # 获取坐标
    textpos1 = gameover_text.get_rect(center=center)  # 获取设置后新的坐标区域
    screen.blit(gameover_text, textpos1)

    # 显示分数
    text2 = "分数:"
    score_text = font2.render(text2, True, [0, 0, 0])
    left = (150, background.get_height() / 4 + 100)  # 获取坐标
    textpos2 = score_text.get_rect(center=left)  # 获取设置后新的坐标区域
    screen.blit(score_text, textpos2)
    text2_2 = str(score)
    score_text = font2.render(text2_2, True, [0, 0, 0])
    left = (background.get_width() / 2, background.get_height() / 4 + 100)  # 获取坐标
    textpos2 = score_text.get_rect(center=left)  # 获取设置后新的坐标区域
    screen.blit(score_text, textpos2)

    # 显示最高分
    if score > highest_score:
        highest_score = score
        text4 = "新纪录"
        text = font1.render(text4, True, [255, 0, 0])
        center = (background.get_width() / 2, background.get_height() / 4 + 250)  # 获取坐标
        textpos4 = text.get_rect(center=center)  # 获取设置后新的坐标区域
        screen.blit(text, textpos4)
        f = open(dir, "w")
        f.write(str(highest_score))
        f.close()

    text3 = "最高分:"
    highest_score_text = font2.render(text3, True, [0, 0, 0])
    left = (150, background.get_height() / 4 + 140)  # 获取坐标
    textpos3 = highest_score_text.get_rect(center=left)  # 获取设置后新的坐标区域
    screen.blit(highest_score_text, textpos3)
    text3 = str(highest_score)
    highest_score_text = font2.render(text3, True, [0, 0, 0])
    left = (background.get_width() / 2, background.get_height() / 4 + 140)  # 获取坐标
    textpos3 = highest_score_text.get_rect(center=left)  # 获取设置后新的坐标区域
    screen.blit(highest_score_text, textpos3)

    # 玩家坠毁后是否重新游戏
    while True:

        pygame.display.update()
        flag_continue = False
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            elif event.type == pygame.KEYDOWN:
                print("pygame.KEYDOWN")
                if event.key == pygame.K_RETURN:
                    print("pygame.KEYDOWN")
                    flag_continue = True
                    break
        if flag_continue == True:
            enemy_speed = 1
            score_flag = True
            old_score = 0
            break

本次全部分享就这些啦,希望能对你有所帮助,欢迎指正哈!

...全文
53 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

1,040

社区成员

发帖
与我相关
我的任务
社区描述
中南民族大学CSDN高校俱乐部聚焦校内IT技术爱好者,通过构建系统化的内容和运营体系,旨在将中南民族大学CSDN社区变成校内最大的技术交流沟通平台。
经验分享 高校 湖北省·武汉市
社区管理员
  • c_university_1575
  • WhiteGlint666
  • wzh_scuec
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

欢迎各位加入中南民族大学&&CSDN高校俱乐部社区(官方QQ群:908527260),成为CSDN高校俱乐部的成员具体步骤(必填),填写如下表单,表单链接如下:
人才储备数据库及线上礼品发放表单邀请人吴钟昊:https://ddz.red/CSDN
CSDN高校俱乐部是给大家提供技术分享交流的平台,会不定期的给大家分享CSDN方面的相关比赛以及活动或实习报名链接,希望大家一起努力加油!共同建设中南民族大学良好的技术知识分享社区。

注意:

1.社区成员不得在社区发布违反社会主义核心价值观的言论。

2.社区成员不得在社区内谈及政治敏感话题。

3.该社区为知识分享的平台,可以相互探讨、交流学习经验,尽量不在社区谈论其他无关话题。

试试用AI创作助手写篇文章吧