简介面向Python初学者和课程设计学生的飞机大战游戏完整源码包适合用作编程大作业或课程设计的高分参考画面精致、逻辑清晰既易于上手练手也方便二次开发。压缩包共69个文件整体仅1.68MB其中包含15个Python脚本、39张PNG格式游戏图片、12个WAV音效、1个OGG背景音乐以及TTF字体和TXT说明文件素材与代码分类存放结构一目了然。源码按游戏对象拆分模块涵盖飞机、敌机、子弹、补给、音效等完整功能完整实现了从玩家操控、敌机生成到碰撞判定、分数结算的游戏闭环配套高质量美术素材与音效解压后即可直接运行体验也可在此基础上扩展关卡、道具或特效。已有922人次学习下载对正在完成课程设计或希望通过项目实战提升编程能力的初学者是一份很实用的参考资料。1. 飞机大战课程设计源码从入门练手到二次开发如果你在找一份能直接跑起来的 Python 游戏课程设计这份飞机大战源码plane-master 目录下的 plane01.py 到 plane12.py加 enemy.py、bullet.py、supply.py是我见过为数不多“画面质感在线、代码能改着玩”的素材。它没有用控制台当画布而是完整走了 pygame 的显示、音效、碰撞和补给逻辑非常适合交大作业前补课也适合工作后想快速验证一个游戏循环想法时拉出来改。初学者可以按编号读一遍从 animate 到 collide 的递进老手则可以直接从参数表和资源文件反推调整点五分钟换一套手感。2. 拆解 plane-master 工程模块划分与 pygame 渲染机制2.1 文件不是摆设编号顺序就是教学顺序源码里一组 plane01.py 到 plane12.py 的文件名对应的是同一个飞机大战在一步步加功能从控制一架飞机移动到发射子弹、生成敌机、处理爆炸动画、加入补给和音效。这个设计思路和实际项目里的构建顺序几乎一致——不要想着一次写完整个游戏而是先让一个物体能动再让两个物体交互最后再优化画面和音效。readme.txt 里通常会写运行条件但即使没有打开 plane01.py 你也能看到一个最小的 pygame 主循环。其他三个核心模块——enemy.py、bullet.py、supply.py——是解耦后的实现适合对照学习类的划分敌机有自己的速度、生命值和 drop 物概率子弹有移动、离屏回收补给区分子弹强化和炸弹。把 plane12.py 当作完整版再把多余模块拆出来就知道课程设计里“面向对象”分在哪里。2.2 pygame 主循环的四个固定环节任何 pygame 游戏都离不开四个步骤处理事件、更新状态、刷新画面、控制帧率。飞机大战源码中 plane01.py 到 plane12.py 全都在做这件事只是越到后面更新状态这一步处理的对象越多。import pygame pygame.init() screen pygame.display.set_mode((480, 700)) # 经典竖屏尺寸 clock pygame.time.Clock() bg pygame.image.load(images/background.png).convert() while True: # 事件处理每帧检查用户输入和系统事件 for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() # 状态更新移动飞机、子弹、敌机检测碰撞 # 刷新画面 screen.blit(bg, (0, 0)) # 控制帧率默认 60 帧每秒 clock.tick(60) pygame.display.update()pygame.display.set_mode((480, 700))里的宽高可以改但注意 assets 里的图片尺寸是否匹配我一般把背景图和窗口保持一致否则需要用pygame.transform.scale做适配。clock.tick(60)不只是限制帧率更重要的是让游戏在不同性能设备上维持一致的速度因为很多移动逻辑是以“每帧移动多少像素”来计算的。event.type pygame.QUIT是窗口右上角关闭按钮的退出事件如果漏掉窗口会卡死源码里如果使用sys.exit()记得先pygame.quit()。2.3 Surface 与 convert_alpha 的坑源码里大量.png图片比如me1.png、enemy1.png这些透明像素的加载方式决定性能。直接用pygame.image.load返回的 Surface 每帧转色会慢常见做法是立即调用convert_alpha()让像素格式和当前显示模式对齐。hero_img pygame.image.load(images/me1.png).convert_alpha() enemy_img pygame.image.load(images/enemy1.png).convert_alpha() bullet_img pygame.image.load(images/bullet1.png).convert_alpha()调用convert_alpha()后blit时会保留透明通道否则部分 PNG 会出现黑块或描边发灰。如果你二次开发时自己做了素材记得统一处理这一点新手最容易在加载图片后忘了这行然后排查半天找不到问题。3. 主角与敌机的碰撞、音效与补给实现细节3.1 碰撞检测不是精准物理而是矩形相交飞机大战源码里看不到复杂的物理引擎核心碰撞判断全部基于pygame.Rect的矩形相交。每架飞机、每颗子弹都维护一个rect属性主循环里用colliderect判断是否命中。这么做的好处是性能高、代码简单坏处是边界透明的图片也会被算入碰撞区域手感上可能比视觉略宽。从enemy.py和bullet.py的文件命名推断碰撞后通常要触发三个动作播放对应声音enemy1_down.wav、切换爆炸画帧enemy1_down1.png到enemy1_down4.png、从精灵组移除对象。示例逻辑如下def check_collision(hero, bullets, enemies): # 英雄与敌机直接碰撞游戏结束或减命 for enemy in enemies: if hero.rect.colliderect(enemy.rect): return game_over # 子弹击中敌机 hit_list pygame.sprite.groupcollide(bullets, enemies, True, False) for hit_enemies in hit_list.values(): for enemy in hit_enemies: enemy.hp - 1 if enemy.hp 0: enemy.kill() # 播放音效生成爆炸动画 pygame.mixer.Sound(sound/enemy1_down.wav).play() return Nonepygame.sprite.groupcollide(bullets, enemies, True, False)前两个参数是精灵组第三个表示子弹是否消失第四个表示敌机是否消失。这里子弹设True敌机先保留因为要减生命值而不是一击必杀。音效播放是阻塞式还是混音式取决于pygame.mixer初始化参数飞机大战里音效较短直接用Sound.play()不会卡显眼。爆炸画帧通常用一个列表按帧切换比如enemy1_down1.png到enemy1_down4.png可以通过time模块控制每一帧的存活时间否则爆炸会在一瞬间闪完。3.2 补给系统bullet_supply 与 bomb_supplyassets 里有bullet_supply.png、bomb_supply.png、get_bullet.wav、get_bomb.wav说明这是一套“拾取道具”的补给循环。supply.py大概率封装了补给掉落、移动、拾取判定三个方法。掉落时机通常和敌机死亡绑定比如每小时或每波敌机死亡后有概率掉落。补给类型资源文件名效果刷新方式子弹强化bullet_supply.png / get_bullet.wav武器升级或子弹数量翻倍敌机随机掉落存在时间有限炸弹清屏bomb_supply.png / get_bomb.wav清空当前所有敌机敌机随机掉落使用受限class Supply(pygame.sprite.Sprite): def __init__(self, kind): super().__init__() self.kind kind # bullet 或 bomb if self.kind bullet: self.image pygame.image.load(images/bullet_supply.png).convert_alpha() else: self.image pygame.image.load(images/bomb_supply.png).convert_alpha() self.rect self.image.get_rect() self.speed 3 self.life_time 300 # 出现 300 帧后消失 def update(self): self.rect.y self.speed self.life_time - 1 if self.life_time 0: self.kill()life_time是补给在屏幕上存在的帧数按 60 帧/秒算300 帧就是 5 秒超过后还没被吃到就自动消失。这个数值如果调大游戏会变简单因为补给满屏都是调小则需要玩家更主动去接。self.rect是整个游戏世界里所有位置判断的基础移动就是改rect.x和rect.y碰撞就是拿rect去比。4. 运行、调试与常见坑位从 python 环境到资源路径4.1 先让代码跑起来环境安装与验证这份源码依赖 pygame但 py 文件本身不会告诉你版本要求。常见做法是先装一个稳定的 pygame 版本比如最新的pygame-ce分支或者传统pygame2.x。在命令行里分步执行python -m venv plane_env # 建虚拟环境避免污染全局 source plane_env/bin/activate # Windows 下换 plane_env\Scripts\activate pip install pygame-ce # 或者 pip install pygame python plane12.py # 先跑完整版用虚拟环境是因为不同课程设计项目可能依赖不同 pygame 版本直接全局安装会造成版本冲突。这个项目本身不复杂但养成习惯后写爬虫或服务端时同样适用。pip install pygame-ce是社区维护版修复了老版 pygame 在 Python 3.12 上的兼容问题。如果你还在用 Python 3.6 之前的版本则建议换 Python 3.8 以上。如果启动后报pygame.error: Couldnt open ...那不是下载的锅是当前工作目录不对程序找不到images和sound。4.2 资源路径与打包利器源码是明显按“相对路径”写的也就是直接写images/background.png。一旦编译成 exe 或用 IDE 指定了其他工作目录就会立刻找不到素材。解决方式不复杂用os.path.join和当前文件所在路径拼接最稳import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) BG_PATH os.path.join(BASE_DIR, images, background.png) SOUND_DIR os.path.join(BASE_DIR, sound)我一般调试时就在项目根目录开终端这样python plane12.py不会出错但如果用 VSCode 的 Run 按键工作目录可能变成workspace下的其他位置所以还是建议用BASE_DIR做兜底。如果你想交给老师一个单文件或 exe最常见的是用pyinstaller但要把图片和声音文件一起塞进去否则 exe 换个电脑就跑不了。命令行参考pyinstaller -F -w --add-data images;images --add-data sound;sound plane12.py-F打包单文件-w去掉命令行黑框--add-data把 assets 目录带进产物。Windows 上用分号分隔源和目的地Linux 或 mac 上要用冒号这个细节很多人卡得不冤。4.3 帧率不稳与音效卡顿源码里.ogg和.wav混着用pygame 对.ogg支持较好.wav兼容性高。如果你听到音效延迟或破音先检查是不是在游戏循环里反复加载pygame.mixer.Sound。正确做法是初始化阶段一次性加载所有音效sounds { enemy1_down: pygame.mixer.Sound(sound/enemy1_down.wav), bullet: pygame.mixer.Sound(sound/bullet.wav), upgrade: pygame.mixer.Sound(sound/upgrade.wav), }之后每次触发事件只调用sounds[bullet].play()。同理背景音乐game_music.ogg用pygame.mixer.music模块单独播放不要用Sound否则长音频会占用大量内存。帧率方面如果敌机一多就掉帧注意把每张图片都缓存成全局变量不要在update里多次load。5. 二次开发切入点参数调整与功能扩展5.1 改出三种手感的秘密参数每个人对游戏手感的要求不同。有人觉得攻击太顺有人觉得敌机太密。这块源码的核心参数分散在几个类里集中看几个位置就能快速调# 飞机移动速度 class Player: def __init__(self): self.speed 5 # 数值越大移动越快2 可做“重机”手感 # 子弹发射频率 class Bullet: def __init__(self, image, speed12): self.speed speed # 提高则弹道更直更快但操作反馈变硬 # 敌机生成间隔 ENEMY_SPAWN_INTERVAL 30 # 每 30 帧生成一个值越小越密集把玩家速度从 5 改到 7操作会明显变飘适合做“精英”模式改到 3 则是更厚重的操控感配合大尺寸 me1.png 时很带感。敌机生成间隔用帧计数而不是时间计数是因为游戏暂停或掉帧时保持逻辑一致。如果你改成按秒还需要乘以clock.get_time()做换算。当你在源码里搜索speed、interval、life这类英文变量会发现改动成本极低这就是封装成类的好处——你不是在乱翻代码而是在调参数。5.2 在 supply.py 上加一个“护盾”补给既然已有bullet_supply和bomb_supply再扩展一种护盾补给就是常见二开思路。你需要自己做一张shield_supply.png然后在Supply类里加一个self.kind shield的分支再在主循环拾取逻辑里给玩家附加无敌状态。关键代码可以这样扩class Supply(pygame.sprite.Sprite): def __init__(self, kind): super().__init__() self.kind kind self.image_map { bullet: images/bullet_supply.png, bomb: images/bomb_supply.png, shield: images/shield_supply.png, # 新加 } self.image pygame.image.load(self.image_map[self.kind]).convert_alpha() self.rect self.image.get_rect()然后在碰撞检测拿到补给后不是直接加子弹或炸弹而是设置hero.invincible True再配合一个计时器在 300 帧后恢复。这个思路能演示你对类结构新增字段和安全恢复的理解答辩时比只说“我改了大小的分数”高一个层次。5.3 用日志和调试信息替代 print源码是课程设计大概率用了不少print来输出信息。二次开发时建议把这些替换成标准库logging好处是你可以按级别忽略细节只保留错误import logging logging.basicConfig(levellogging.INFO, format%(levelname)s: %(asctime)s %(message)s) enemy_count len(enemies) logging.debug(fcurrent enemies: {enemy_count}) # 改 level 为 DEBUG 才输出这样在调试阶段把level调成DEBUG能看到每帧状态交给老师或跑给朋友玩时再改回INFO避免终端刷屏。最后在plane12.py的入口处加一个main()函数让脚本既支持直接运行也支持被 import 后调用main()这样你自己写测试用例时就不会执行整个游戏循环。本文还有配套的精品资源点击获取