公司动态
Python游戏开发入门:Pygame实战飞机大战项目全解析
1. 项目概述从零到一用Pygame复刻经典飞机大战如果你刚学完Python基础语法正愁找不到一个能串联起所有知识点的实战项目那“飞机大战”绝对是个完美的选择。这不仅仅是因为它经典更因为它麻雀虽小五脏俱全。从事件处理、图形绘制、碰撞检测到游戏状态管理一个完整的游戏开发闭环都能在这里体验到。我当年就是用这个项目彻底搞懂了面向对象编程和游戏循环那种看着自己写的代码让飞机动起来、子弹飞出去、敌机爆炸的感觉成就感直接拉满。这个项目我们选择Pygame库。为什么是Pygame对于初学者而言它足够轻量没有Unity、Unreal那种重型引擎的学习曲线让你能专注于游戏逻辑本身而不是被复杂的编辑器界面和物理引擎搞晕。它的API设计直观画个矩形、加载张图片、播放个音效几行代码就能搞定反馈非常及时。通过完成这个项目你不仅能巩固Python的类、列表、循环等核心语法更能建立起对游戏开发最基础也最重要的概念——游戏主循环Game Loop的深刻理解。接下来我会带你一步步拆解把每个模块的“为什么”和“怎么做”都讲透。2. 核心思路与架构设计如何组织你的代码在动手写第一行代码之前花十分钟想清楚架构能省下后面几小时调试的功夫。飞机大战的核心玩法很简单玩家控制一架飞机发射子弹消灭不断出现的敌机同时躲避敌机的撞击和子弹。基于这个玩法我们可以抽象出几个核心的“角色”或“对象”。2.1 面向对象设计识别游戏中的“演员”最直观的设计方式就是面向对象。我们把游戏中会动、有状态的东西都看作对象玩家飞机 (Player): 这是由我们控制的角色。它需要属性位置坐标x, y、生命值、移动速度、拥有的子弹类型等。它需要方法上下左右移动、发射子弹、绘制自身、判断是否被击中。敌机 (Enemy): 自动从屏幕上方出现并向下移动。属性包括位置、类型区分普通敌机和Boss、生命值、移动速度、得分价值等。方法包括移动、绘制、判断是否被击中或飞出屏幕。子弹 (Bullet): 由玩家或敌机发射。属性包括位置、速度、方向、伤害值。方法就是移动和绘制。爆炸效果 (Explosion): 这是一个视觉反馈对象。当飞机被击毁时不应该立刻消失而应该播放一个由多帧图片组成的爆炸动画。属性包括当前位置、当前动画帧索引、动画是否播放完毕。注意很多新手会忽略爆炸效果直接用kill()方法删除敌机精灵。这会导致视觉体验很突兀。一个好的爆炸动画是游戏质感的体现。2.2 游戏主循环一切逻辑的发动机这是游戏开发中最核心的概念。你可以把它想象成一个永不停止的“心跳”。每一次心跳游戏都做三件事处理事件 (Event Handling): 检查用户有没有按键盘、点鼠标或者点了窗口的关闭按钮。这是游戏与玩家交互的入口。更新游戏状态 (Update): 根据输入和游戏规则更新所有对象的状态。比如根据按键更新玩家位置让所有子弹向上移动让所有敌机向下移动检查子弹和敌机有没有相撞碰撞检测。绘制画面 (Draw/Render): 把最新的游戏状态画到屏幕上。先画背景再按顺序画飞机、子弹、敌机、爆炸效果、UI文字如分数、生命值。这个循环以极快的速度比如每秒60次重复执行就形成了我们看到的连续动态画面。在Pygame中我们通过一个while True循环配合clock.tick(60)来控制这个频率。2.3 精灵与精灵组Pygame的高效管理利器Pygame提供了pygame.sprite.Sprite精灵和pygame.sprite.Group精灵组这两个强大的类。上面我们设计的Player、Enemy、Bullet都应该继承自Sprite类。这样做的好处是内置常用属性Sprite有image表面和rect矩形区域属性绘图和碰撞检测直接用它非常方便。批量操作我们可以创建“玩家子弹组”、“敌机组”、“所有精灵组”。更新时一句bullet_group.update()就能让组里所有子弹执行各自的update方法。绘制时一句all_sprites_group.draw(screen)就能画出所有精灵。这比用普通列表自己管理要高效、清晰得多。高效的碰撞检测pygame.sprite.groupcollide()可以快速检测两个精灵组之间的碰撞并返回发生碰撞的精灵字典这是实现子弹打中敌机功能的关键。3. 开发环境搭建与核心工具详解工欲善其事必先利其器。一个顺手的开发环境能极大提升效率减少不必要的麻烦。3.1 Python与Pygame安装避坑指南首先确保你安装了Python建议版本3.8或以上。打开命令行CMD或终端输入python --version检查。安装Pygame最常用的命令是pip install pygame但这里往往是新手遇到的第一个坑。如果你看到类似error: Microsoft Visual C 14.0 or greater is required或failed to build wheel for pygame的错误这是因为Pygame在某些Windows系统上需要编译而你的电脑缺少C编译环境。解决方案实测有效优先使用预编译的轮子wheel访问 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame 这个非官方但极其可靠的Windows二进制包网站。根据你的Python版本和系统位数比如cp38代表Python 3.8win_amd64代表64位系统下载对应的.whl文件例如pygame‑2.5.1‑cp38‑cp38‑win_amd64.whl。下载后在命令行进入该文件所在目录执行pip install pygame‑2.5.1‑cp38‑cp38‑win_amd64.whl更新pip和setuptools有时旧版本的安装工具也会导致问题。可以尝试python -m pip install --upgrade pip setuptools wheel然后再重试pip install pygame。安装成功后可以写一个简单的测试脚本验证import pygame pygame.init() screen pygame.display.set_mode((800, 600)) print(Pygame安装成功) pygame.quit()能运行并弹出窗口就说明成功了。3.2 代码编辑器与资源管理编辑器选择VS Code Python插件 或 PyCharm社区版免费都是极好的选择。它们有代码提示、调试功能能帮你快速定位语法错误。资源文件管理在项目根目录下建议建立清晰的文件夹结构例如plane_game/ ├── main.py # 主程序入口 ├── sprites/ # 存放精灵类代码player.py, enemy.py... ├── images/ # 存放所有图片player.png, enemy1.png, bullet.png... ├── sounds/ # 存放音效shoot.wav, explosion.wav... └── fonts/ # 存放字体文件这样做的好处是路径清晰。加载资源时使用os.path.join来构建跨平台的路径避免硬编码import os player_image pygame.image.load(os.path.join(images, player.png)).convert_alpha().convert_alpha()方法在加载带透明通道的PNG图片时特别重要它能优化绘制速度并保留透明度。4. 核心模块实现与代码逐行解析接下来我们进入核心编码环节。我会把关键代码拆开揉碎了讲让你明白每一行的意图。4.1 游戏初始化与主循环骨架这是游戏的起点框架搭得好后面添砖加瓦就轻松。import pygame import sys import random # 初始化 pygame.init() pygame.mixer.init() # 初始化音效模块 # 游戏窗口设置 SCREEN_WIDTH 480 SCREEN_HEIGHT 700 screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(小甲鱼飞机大战 - Pygame实战) clock pygame.time.Clock() # 创建时钟对象用于控制帧率 # 颜色定义RGB BLACK (0, 0, 0) WHITE (255, 255, 255) GREEN (0, 255, 0) # 游戏状态变量 running True score 0 # 主循环 while running: # 1. 控制帧率每秒60帧 clock.tick(60) # 2. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 键盘按下事件处理后续会移到Player类中 elif event.type pygame.KEYDOWN: if event.key pygame.K_ESCAPE: running False # 3. 更新游戏状态这里暂时为空后续填充 # all_sprites.update() # 4. 绘制/渲染 screen.fill(BLACK) # 用黑色填充整个屏幕相当于清屏 # all_sprites.draw(screen) # 绘制分数简单演示 font pygame.font.SysFont(None, 36) score_text font.render(fScore: {score}, True, WHITE) screen.blit(score_text, (10, 10)) # 5. 刷新显示 pygame.display.flip() # 退出游戏 pygame.quit() sys.exit()关键点解析clock.tick(60)这是控制游戏速度的生命线。参数60代表我们希望循环每秒执行最多60次。它通过计算两次调用之间的时间差并主动延迟来稳定帧率。帧率太高电脑负荷大太低游戏卡顿60是流畅动作游戏的甜点。screen.fill(BLACK)在绘制新一帧前必须用背景色清除上一帧的画面否则你会看到物体移动的拖影。pygame.display.flip()将我们在内存中绘制的这一帧画面真正更新到显示器上。双缓冲技术使得这个切换过程无缝避免屏幕撕裂。4.2 玩家飞机类响应与控制的核心现在我们来创建玩家类这是交互的核心。class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() # 加载图片并转换格式以优化速度 self.image pygame.image.load(images/player.png).convert_alpha() # 获取图片的矩形区域并设置初始位置屏幕底部居中 self.rect self.image.get_rect() self.rect.centerx SCREEN_WIDTH // 2 self.rect.bottom SCREEN_HEIGHT - 10 # 玩家属性 self.speed 5 self.health 3 self.shoot_delay 250 # 发射子弹的延迟毫秒 self.last_shot pygame.time.get_ticks() # 记录上次射击时间 def update(self): # 获取当前所有按键状态持续按下检测 keys pygame.key.get_pressed() # 左右移动 if keys[pygame.K_LEFT] and self.rect.left 0: self.rect.x - self.speed if keys[pygame.K_RIGHT] and self.rect.right SCREEN_WIDTH: self.rect.x self.speed # 上下移动可选 if keys[pygame.K_UP] and self.rect.top 0: self.rect.y - self.speed if keys[pygame.K_DOWN] and self.rect.bottom SCREEN_HEIGHT: self.rect.y self.speed # 空格键射击放在update里用时间延迟控制射速 if keys[pygame.K_SPACE]: self.shoot() def shoot(self): now pygame.time.get_ticks() # 检查是否过了射击冷却时间 if now - self.last_shot self.shoot_delay: self.last_shot now # 创建子弹位置在飞机顶部中央 bullet Bullet(self.rect.centerx, self.rect.top) # 将子弹添加到所有精灵组和子弹组需要在主程序中定义这些组 all_sprites.add(bullet) bullets.add(bullet) # 播放射击音效可选 # shoot_sound.play()设计心得为什么用pygame.key.get_pressed()而不用KEYDOWN事件对于移动这种需要持续响应的操作get_pressed()更合适因为它每帧都会检测按键是否被按住。而KEYDOWN事件只在按键被按下的那一瞬间触发一次。射击延迟 (shoot_delay)如果不加延迟玩家按住空格键一帧内就会生成上百颗子弹游戏会卡死。通过记录上次射击时间 (last_shot) 并与当前时间对比我们可以轻松控制射速。边界检查移动逻辑中if self.rect.right SCREEN_WIDTH这样的判断至关重要它防止飞机飞出屏幕外。4.3 敌机类随机性与多样性的来源敌机是游戏挑战性的来源我们需要让它随机出现、有不同的类型。class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() # 随机选择敌机类型 enemy_type random.choice([small, medium, large]) if enemy_type small: self.image pygame.image.load(images/enemy_small.png).convert_alpha() self.speed random.randrange(2, 4) self.health 1 self.score_value 100 elif enemy_type medium: self.image pygame.image.load(images/enemy_medium.png).convert_alpha() self.speed random.randrange(1, 3) self.health 2 self.score_value 250 else: # large self.image pygame.image.load(images/enemy_large.png).convert_alpha() self.speed random.randrange(1, 2) self.health 3 self.score_value 500 self.rect self.image.get_rect() # 随机设置初始位置x坐标随机y坐标从屏幕顶部稍上方开始 self.rect.x random.randrange(0, SCREEN_WIDTH - self.rect.width) self.rect.y random.randrange(-100, -40) def update(self): self.rect.y self.speed # 如果敌机移出屏幕底部则将其删除以释放内存 if self.rect.top SCREEN_HEIGHT: self.kill()关键技巧随机性使用random.randrange来随机生成敌机的速度、出现位置。这能让每一波敌机的行为都略有不同避免玩法单调。内存管理敌机在update中检查是否飞出屏幕底部如果飞出就调用self.kill()。这个方法会将精灵从它所属的所有精灵组中移除Python的垃圾回收机制随后会清理它。不及时kill()掉无用的精灵是导致游戏越来越卡顿的常见原因。4.4 子弹类简单的运动体子弹的逻辑相对简单主要是沿直线运动。class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image pygame.image.load(images/bullet.png).convert_alpha() self.rect self.image.get_rect() # 子弹的初始位置设置为传入的坐标通常是飞机枪口 self.rect.centerx x self.rect.bottom y self.speed -10 # 负值表示向上飞屏幕坐标系y轴向下为正 def update(self): self.rect.y self.speed # 如果子弹飞出屏幕顶部则删除 if self.rect.bottom 0: self.kill()4.5 碰撞检测游戏逻辑的交互枢纽这是游戏最核心的逻辑之一判断子弹是否击中敌机以及敌机是否撞到玩家。# 在主循环的“更新游戏状态”部分添加以下代码 # 假设已有精灵组all_sprites, enemies, bullets, players # 1. 子弹与敌机的碰撞 hits pygame.sprite.groupcollide(enemies, bullets, False, True) for enemy, bullet_list in hits.items(): # 每颗子弹造成1点伤害 enemy.health - len(bullet_list) if enemy.health 0: enemy.kill() score enemy.score_value # 这里可以添加爆炸效果 # explosion Explosion(enemy.rect.center) # all_sprites.add(explosion) # 2. 敌机与玩家的碰撞 player_hits pygame.sprite.spritecollide(player, enemies, True) for hit in player_hits: player.health - 1 # 播放被击中音效或显示受击特效 if player.health 0: # 游戏结束逻辑 running Falsepygame.sprite.groupcollide详解参数groupcollide(group1, group2, dokill1, dokill2)它检测group1和group2中所有精灵之间的碰撞基于rect属性。dokill1和dokill2是布尔值。如果为True则发生碰撞的精灵会被自动从其所属组中移除即kill()。在我们的代码中pygame.sprite.groupcollide(enemies, bullets, False, True)意味着检测enemies组和bullets组。敌机被击中后不会立刻消失False因为我们要计算伤害。子弹击中后立即消失True很合理。返回值hits是一个字典{敌机精灵: [击中它的子弹精灵列表], ...}。这样我们就能知道哪架敌机被哪些子弹击中了方便计算累计伤害。重要提示碰撞检测的顺序很重要。应该先检测子弹和敌机让敌机有可能被消灭再检测敌机和玩家。否则可能敌机在撞到玩家前就被子弹击毁了但碰撞检测却先判定玩家被撞这不符合逻辑。5. 游戏性增强与优化实战基础功能完成后我们可以让游戏变得更丰满、更好玩。5.1 添加视觉反馈爆炸动画静态的消失很无趣一个逐帧播放的爆炸动画能极大提升打击感。class Explosion(pygame.sprite.Sprite): def __init__(self, center): super().__init__() # 加载爆炸动画序列图假设是一张包含多帧的长图 self.explosion_sheet pygame.image.load(images/explosion.png).convert_alpha() self.frames [] # 假设爆炸图是128x128每帧共6帧排成一行 frame_width 128 frame_height 128 for i in range(6): frame self.explosion_sheet.subsurface(pygame.Rect(i * frame_width, 0, frame_width, frame_height)) self.frames.append(frame) self.image self.frames[0] self.rect self.image.get_rect() self.rect.center center self.frame_index 0 self.last_update pygame.time.get_ticks() self.frame_rate 50 # 每帧显示50毫秒 def update(self): now pygame.time.get_ticks() if now - self.last_update self.frame_rate: self.last_update now self.frame_index 1 if self.frame_index len(self.frames): self.kill() # 动画播放完毕删除自身 else: center self.rect.center self.image self.frames[self.frame_index] self.rect self.image.get_rect() self.rect.center center实现要点精灵表 (Sprite Sheet)将多帧动画放在一张大图里是游戏开发中节省资源和加载时间的常用技巧。使用subsurface方法从中截取每一帧。时间控制和射击延迟类似通过对比当前时间和上一帧更新时间来控制动画播放速度。生命周期管理动画播放到最后一帧后调用self.kill()自动清理。5.2 添加音效与背景音乐声音是游戏沉浸感的一半。Pygame处理声音很简单。# 在初始化部分加载音效 shoot_sound pygame.mixer.Sound(sounds/laser1.wav) explosion_sound pygame.mixer.Sound(sounds/explosion.wav) # 设置音量 shoot_sound.set_volume(0.3) explosion_sound.set_volume(0.6) # 播放背景音乐音乐文件通常较大用Music模块 pygame.mixer.music.load(sounds/background.ogg) pygame.mixer.music.set_volume(0.5) pygame.mixer.music.play(-1) # -1代表无限循环 # 在需要的地方播放音效 def shoot(self): # ... 创建子弹的代码 ... shoot_sound.play() # 在敌机被摧毁时 if enemy.health 0: enemy.kill() score enemy.score_value explosion_sound.play() # ... 创建爆炸效果 ...注意音效格式Sound对象适合短促的音效WAV格式mixer.music适合长的背景音乐OGG、MP3格式。OGG格式在Pygame中兼容性好且压缩率高推荐使用。5.3 游戏状态管理与UI界面一个完整的游戏需要有开始界面、游戏进行中、暂停、结束等状态。# 定义游戏状态常量 GAME_STATE_MENU 0 GAME_STATE_PLAYING 1 GAME_STATE_GAME_OVER 2 game_state GAME_STATE_MENU # 在主循环中根据状态执行不同逻辑 while running: clock.tick(60) if game_state GAME_STATE_MENU: # 处理菜单事件如鼠标点击开始按钮 for event in pygame.event.get(): if event.type pygame.QUIT: running False if event.type pygame.MOUSEBUTTONDOWN: # 简单判断鼠标是否点击在“开始游戏”文字区域 mouse_pos pygame.mouse.get_pos() if start_button_rect.collidepoint(mouse_pos): game_state GAME_STATE_PLAYING reset_game() # 重置游戏数据 # 绘制菜单界面 screen.fill(BLACK) draw_text(screen, 飞机大战, 64, SCREEN_WIDTH//2, SCREEN_HEIGHT//4) draw_text(screen, 点击屏幕开始游戏, 36, SCREEN_WIDTH//2, SCREEN_HEIGHT//2) pygame.display.flip() elif game_state GAME_STATE_PLAYING: # 原有的游戏主循环逻辑... # 处理事件、更新、绘制... # 如果玩家生命值耗尽切换状态 if player.health 0: game_state GAME_STATE_GAME_OVER elif game_state GAME_STATE_GAME_OVER: # 绘制游戏结束界面显示最终分数提供重玩选项 # ...绘制文字的工具函数def draw_text(surf, text, size, x, y, colorWHITE): font pygame.font.Font(None, size) # 使用默认字体或加载自定义字体文件 text_surface font.render(text, True, color) text_rect text_surface.get_rect() text_rect.midtop (x, y) surf.blit(text_surface, text_rect)6. 性能优化与常见问题排查当你的游戏精灵越来越多可能会感到帧率下降。这里有几个立竿见影的优化技巧。6.1 图像与渲染优化始终使用convert()或convert_alpha()# 对于不透明图片 image pygame.image.load(image.png).convert() # 对于带透明通道的图片如PNG image pygame.image.load(image.png).convert_alpha()这个方法将图片转换成与当前显示模式最匹配的格式后续的blit绘制操作会快得多。这是提升Pygame性能最有效、最简单的一步务必养成习惯。避免在循环中加载资源所有图片、音效、字体都应在游戏初始化阶段加载好存为变量。在主循环里反复加载会卡死游戏。脏矩形更新如果你的游戏只有局部区域变化比如UI固定只有主角在动可以使用pygame.display.update(rect_list)只更新指定的矩形区域列表而不是用pygame.display.flip()更新整个屏幕。但对于像飞机大战这种全屏元素都在动的游戏flip更简单直接。6.2 精灵组管理优化分层绘制如果背景是静态的不要每一帧都重新绘制。可以先将背景画在一个Surface上每帧直接blit这个Surface而不是重画所有背景元素。控制精灵数量及时kill()掉屏幕外的子弹和敌机。可以设置一个最大敌机数量防止因生成过快导致精灵组膨胀。6.3 常见问题与解决方案实录问题1游戏窗口一闪而过或者打开后无响应。排查99%的情况是主循环while running里没有正确调用pygame.event.get()。Pygame必须持续处理事件队列否则操作系统会认为程序无响应。解决确保你的主循环中包含了for event in pygame.event.get():并且正确处理了pygame.QUIT事件。问题2图片显示出来有黑色或白色背景透明区域不透明。排查加载PNG图片时没有使用.convert_alpha()或者原图本身就没有透明通道。解决使用.convert_alpha()。确保你的图片编辑软件如Photoshop、Aseprite保存时选择了支持透明的格式如PNG-24。问题3碰撞检测不准确感觉子弹“擦边”就打中了。排查默认的碰撞检测是基于精灵的rect矩形。如果你的飞机图片不是矩形而是不规则形状矩形检测就会不精确。解决精细矩形调整可以设置一个比图片实际显示区域更小的rect比如self.rect self.image.get_rect().inflate(-10, -10)让碰撞框“缩进去”一点手感更好。使用遮罩碰撞检测对于形状复杂的精灵可以使用pygame.sprite.collide_mask。这需要为精灵设置self.mask pygame.mask.from_surface(self.image)然后使用pygame.sprite.spritecollide(player, enemies, False, pygame.sprite.collide_mask)。注意遮罩检测计算量更大精灵多时会明显影响性能。问题4音效播放有延迟或卡顿。排查可能是同时播放的音效实例太多。Pygame的Sound对象每次.play()都会创建一个新的播放实例。解决对于频繁播放的音效如子弹音效可以创建多个相同的Sound对象组成一个列表轮流播放。shoot_sounds [pygame.mixer.Sound(shoot.wav) for _ in range(5)] sound_index 0 def play_shoot_sound(): global sound_index shoot_sounds[sound_index].play() sound_index (sound_index 1) % len(shoot_sounds)确保音效文件本身不要太长且格式为低延迟的WAV未压缩或ADPCM压缩。问题5游戏在不同电脑上速度不一样有的快有的慢。排查所有运动逻辑都直接与帧率挂钩。比如self.rect.y self.speed如果一台电脑帧率是60FPS另一台是30FPS那么后者的物体移动速度看起来就只有一半。解决使用基于时间的运动。这是专业游戏开发的必备技巧。class Enemy(pygame.sprite.Sprite): def __init__(self): # ... 其他初始化 ... self.speed 100 # 现在代表每秒移动100像素 def update(self): # 计算上一帧到这一帧经过的时间秒 delta_time clock.get_time() / 1000.0 # 移动距离 速度 * 时间 self.rect.y self.speed * delta_time这样无论帧率高低物体每秒移动的像素数都是恒定的。clock.get_time()返回上次调用clock.tick()后经过的毫秒数。7. 项目打包与分发当你完成游戏后可能想分享给没有安装Python的朋友。这就需要打包成独立的可执行文件.exe。目前最流行的工具是PyInstaller。安装pip install pyinstaller基础打包在项目根目录打开命令行执行pyinstaller --onefile --windowed --name PlaneGame main.py--onefile: 打包成单个exe文件。--windowed: 运行时不显示命令行黑窗口。--name: 指定生成exe的名称。处理资源文件这是最大的坑PyInstaller默认只打包.py文件。你的图片、声音文件夹不会自动包含进去。直接运行打包后的exe会找不到资源而崩溃。解决方案在代码中我们需要修改资源加载路径使其在开发环境和打包后都能工作。import sys import os # 判断是否是打包后的环境 def resource_path(relative_path): 获取资源的绝对路径。在开发环境和PyInstaller打包后都能工作 try: # PyInstaller创建的临时文件夹路径 base_path sys._MEIPASS except Exception: # 正常开发环境 base_path os.path.abspath(.) return os.path.join(base_path, relative_path) # 加载资源时使用这个函数 player_image pygame.image.load(resource_path(os.path.join(images, player.png))).convert_alpha()然后你需要告诉PyInstaller哪些资源文件需要被打包。创建一个.spec文件或直接在命令行添加--add-data参数。更简单的方法是先运行一次基础打包命令生成.spec文件然后编辑它# 在生成的 plane_game.spec 文件中的 Analysis 部分添加 a Analysis([main.py], pathex[], binaries[], datas[(images/*.png, images), (sounds/*.wav, sounds)], # 添加这行 hiddenimports[], hookspath[], ...)这行datas的意思是把当前目录下的images/*.png打包后放在exe同级的images文件夹里。最后使用这个spec文件重新打包pyinstaller plane_game.spec打包过程可能会遇到各种依赖问题需要耐心调试。一个干净的虚拟环境virtual environment进行打包可以避免很多奇怪的依赖冲突。