
简介面向Python初学者与游戏开发爱好者这份外星人入侵游戏Pygame源码包完整覆盖了飞船上下移动、空格发射子弹、外星人生成与碰撞、计分、速度升级、最高分记录及剩余飞船显示等核心玩法是学习Pygame框架和游戏逻辑的实用范例也适合作为课程设计或毕业设计的参考项目。源码基于win10Python3.7pygame开发压缩包共688个文件、13.42MB其中以557个Python源文件为主便于逐行阅读和修改另有27个pyd、24个pyc等编译文件及20个exe可执行文件、少量dll依赖库和txt说明文档可直接运行体验也可查看配置与环境依赖降低部署门槛。目前已有2533人浏览学习整个项目结构和模块划分清晰可搭配《Python编程从入门到实践》外星人入侵学习笔记系列进一步理解代码设计从而掌握游戏循环、事件处理、精灵组与碰撞检测等关键机制快速上手自定义角色、关卡和界面延展出更多玩法。1. 项目概述为什么我还在推荐这个“老掉牙”的游戏项目如果你学过Python大概率听说过《外星人入侵》这个项目。它出自《Python编程从入门到实践》这本书几乎成了Python学习者绕不开的练手项目。说实话我第一次写完这个项目的时候游戏简陋得只能算是个“能动的小船打方块”但正是这个项目让我真正理解了面向对象编程、事件驱动模型和游戏主循环这三个概念到底是怎么回事。很多人问我现在AI编程都这么强了还有必要手动写一个pygame游戏吗我的看法是正因为有了AI辅助你才更需要理解游戏代码的底层逻辑。你不可能靠提示词生成一个满足需求的外星人入侵游戏而不去改任何代码但如果你懂游戏循环、精灵组、碰撞检测这些基本概念你就能让AI帮你干活你来掌舵。这篇文章就以“外星人入侵游戏pygame源代码”为线索把整个项目的拆解思路、核心代码、踩坑经历一次说清楚。这个项目适合谁看刚学完Python基础语法、想找一个综合性项目练手的同学以及对类、继承、模块化设计还没有完整概念的人。我会把整个项目按模块拆开讲每个模块对应一个pygame的核心机制这样你一边抄代码一边就懂了原理。2. 整体设计与思路拆解2.1 游戏机制到底怎么设计的外星人入侵的玩法一句话就能概括玩家操控一艘飞船左右移动按空格射击子弹抵御一波又一波从屏幕顶部向下移动的外星人。听起来简单但里面牵涉到的编程概念一点都不简单。我先把整个游戏的实体对象理一遍飞船、子弹、外星人、外星人舰队、按钮、记分牌。每个实体在代码里都是一个类这些类各管各的事互不干扰。飞船负责响应键盘事件子弹负责移动和碰撞检测外星人负责在屏幕上移动和向下推进。这种设计思路的核心是职责分离。游戏主程序只负责初始化、事件处理和画面刷新具体的业务逻辑都封装在各自的对象里。比如飞船只知道自己怎么移动它不需要知道子弹是怎么发射的外星人只知道自己怎么移动它不需要知道飞船在哪。这种解耦的好处是改其中一个模块不会把其他模块搞崩。游戏的状态管理也值得一提。整个游戏只有三种状态active游戏进行中、inactive游戏未开始或结束、paused暂停。我用一个标志位game_active来控制游戏是否运行标志位为True时才更新飞船、子弹和外星人的位置。这个标志位看着简单但它决定了空闲画面和游戏画面之间的切换逻辑也是新手最容易搞混的地方。2.2 为什么选择pygame而不是其他游戏框架你可能会问现在做游戏不是都用Unity、Godot吗为什么Python教程里还用pygame这里面有两个层面的原因。第一pygame是纯Python扩展库它的核心价值在于帮助你理解游戏开发的最基本原理不需要你接触游戏引擎的复杂编辑器。Unity和Godot可以让你拖拽式地做游戏但你拖出来的行为背后封装掉的恰恰是最有价值的编程逻辑。而pygame的一切都摆在明面上窗口是你创建的事件是你轮询的画面是你逐帧绘制的。这种“透明感”恰恰是学习阶段最需要的。第二pygame的生态比较成熟跨平台支持好。Windows、macOS、Linux都能跑文档和教程数量庞大遇到问题基本都能搜到答案。而且它对2D游戏的支持足够强大自带Surface画布、Sprite精灵、Group精灵组等模块处理2D碰撞检测更是拿手好戏。下面是我的建议如果你想非常快速地做一个游戏DEMO或者验证一个游戏创意pygame绝对够用如果你想做商业级3D游戏就别在pygame上花时间了直接去学引擎。坐标系、事件循环、碰撞检测这些底层逻辑其实引擎和pygame是相通的先在一个简单环境里打好基础再去学引擎会更轻松。2.3 代码架构的模块化划分很多初学者写pygame代码喜欢把所有东西塞进一个文件里我不建议这样写。外星人入侵项目的完整代码量在300行左右全部塞在一个文件里虽然能运行但后期改起来非常痛苦。我的做法是分模块组织代码alien_invasion.py主程序负责游戏初始化、主循环和事件处理settings.py游戏配置类集中管理所有参数屏幕尺寸、速度、颜色等ship.py飞船类管理飞船的显示和移动bullet.py子弹类管理子弹的生成、移动和销毁alien.py外星人类管理外星人的显示和移动game_functions.py事件处理函数集把键盘事件和游戏状态对应起来button.py按钮类处理开始按钮等交互元素scoreboard.py记分牌类显示得分、等级和最高分这种模块化划分让每个文件都聚焦一件事后面我加音效、加爆炸动画、加难度递增都只需要改对应的文件主程序基本不用动。3. 核心代码实现与原理详解3.1 第一步主循环与窗口初始化pygame程序的核心是while主循环。这个循环做的事情就是三件事处理事件、更新状态、重绘画面。我来解释一下为什么是这个顺序。事件处理放在最前面是合理的因为用户的键盘输入是驱动游戏变化的起点。按下左箭头飞船向左移动松开左箭头飞船停止移动。这些输入如果不在每一帧最开始处理就会产生延迟感。更新状态放在中间是指根据事件处理的结果修改游戏实体的位置和状态。比如飞船移动了子弹飞出了屏幕外星人撞到了飞船。这些逻辑是按帧执行的每一帧都在计算实体当前应该在哪。重绘画面放在最后是把上面计算好的状态画到屏幕上。pygame用pygame.display.flip()做双缓冲刷新——先在一个后台画布上画好一整帧内容再一次性提交到屏幕显示。这样能最大程度避免画面闪烁。注意别用pygame.display.update()来刷新全屏update()更适合刷新局部区域全屏刷新用flip()更高效。窗口初始化的代码大概是这样的import pygame from settings import Settings from ship import Ship import game_functions as gf def run_game(): pygame.init() ai_settings Settings() screen pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption(Alien Invasion) ship Ship(ai_settings, screen) while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(ai_settings, screen, ship, aliens, bullets) gf.update_aliens(ai_settings, screen, ship, aliens, bullets) gf.update_screen(ai_settings, screen, ship, aliens, bullets)这里有一个重要的点为什么设置类要单独写因为你后续肯定要调参——飞船速度太慢了子弹太快了外星人太多了。如果这些参数硬编码在游戏代码里每次调参都要去翻代码、改数字很容易改出bug。把参数全部集中在settings.py里调起来就是一分钟的事这才是正确的做法。3.2 飞船控制事件驱动是怎么运作的飞船类是我介绍“类”概念的第一站。它继承自pygame的Sprite类这样后面才能用精灵组管理它。import pygame class Ship(Sprite): def __init__(self, ai_settings, screen): super().__init__() self.screen screen self.ai_settings ai_settings self.image pygame.image.load(images/ship.bmp) self.rect self.image.get_rect() self.screen_rect screen.get_rect() self.rect.centerx self.screen_rect.centerx self.rect.bottom self.screen_rect.bottom self.center float(self.rect.centerx) self.moving_right False self.moving_left False def update(self): if self.moving_right and self.rect.right self.screen_rect.right: self.center self.ai_settings.ship_speed if self.moving_left and self.rect.left 0: self.center - self.ai_settings.ship_speed self.rect.centerx self.center def blitme(self): self.screen.blit(self.image, self.rect)这里最值得琢磨的是移动标志位moving_right和moving_left的用法。pygame不像编程比赛里那样监听“按键按下”就立刻移动一次而是靠标志位持续移动——按下方向键时设置标志位为True松开时设置为False。这样飞船的移动是“按住就走松开即停”的连续行为而不是一下一下地蹦。判断边界条件时有一个细节我吃过亏如果self.rect.right self.screen_rect.right写成飞船到边界时会有大概一像素的缝隙看起来就像贴不到底改成或者像我示例里那样判断再加上碰撞检测效果更自然。这里的self.center float(self.rect.centerx)是防止位置精度丢失——rect的坐标是整数直接加速度再强制转换会导致计算误差用浮点数保存位置最后再赋回rect移动才精准。按键事件的处理放在game_functions.py里核心是这段def check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets): for event in pygame.event.get(): if event.type pygame.QUIT: sys.exit() elif event.type pygame.KEYDOWN: check_keydown_events(event, ai_settings, screen, stats, ship, aliens, bullets) elif event.type pygame.KEYUP: check_keyup_events(event, ship)注意pygame.event.get()会取出队列里的所有事件所以要用for循环遍历。有人会问如果事件太多循环一遍性能会不会差实际上pygame的事件队列很小玩家一秒钟产生的键盘事件数量级是几千条这个开销完全可以忽略。3.3 子弹机制与精灵组管理子弹的实现最能体现pygame的Group类的威力。游戏的子弹是不断生成、不断飞出屏幕的如果用列表来管理你得手动遍历删除飞出去的子弹代码写起来又繁琐又容易漏。pygame的Group类帮我们解决这个问题。它是一个容器可以往里面不断添加Sprite对象也可以把不满足条件的Sprite一次性清掉。我用pygame.sprite.Group()创建了一个bullets分组每按一次空格就往组里添加一颗子弹。子弹类的核心代码如下class Bullet(Sprite): def __init__(self, ai_settings, screen, ship): super().__init__() self.screen screen self.rect pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height) self.rect.centerx ship.rect.centerx self.rect.top ship.rect.top self.y float(self.rect.y) self.color ai_settings.bullet_color self.speed_factor ai_settings.bullet_speed def update(self): self.y - self.speed_factor self.rect.y self.y def draw_bullet(self): pygame.draw.rect(self.screen, self.color, self.rect)这里我用pygame.Rect创建了一个矩形当子弹没有用到图片文件。这样做的好处是省资源而且子弹的外观可以通过draw.rect直接绘制调颜色改尺寸都很方便。你完全可以用图片做子弹但性能上没有优势开发效率反而低。子弹生成之后主循环里通过bullets.update()更新所有子弹的位置。接着要删除已经飞出屏幕的子弹否则子弹对象会越来越多内存被白白占用def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets): bullets.update() for bullet in bullets.copy(): if bullet.rect.bottom 0: bullets.remove(bullet)这里的关键是遍历副本。你不能在遍历bullets的同时修改它这在Python里会报运行时错误。bullets.copy()创建了一个新的引用集合遍历副本删除原集合中的元素这样安全又高效。3.4 外星人的生成、移动与碰撞外星人的生成逻辑比飞船和子弹复杂因为你不是生成一个而是生成一整排、一整片。我的做法是先计算一行能放多少个外星人再通过两层循环铺满整个屏幕宽度。核心部分在game_functions.py里def get_number_aliens_x(ai_settings, alien_width): available_space_x ai_settings.screen_width - 2 * alien_width number_aliens_x int(available_space_x / (2 * alien_width)) return number_aliens_x def create_alien(ai_settings, screen, aliens, alien_number, row_number): alien Alien(ai_settings, screen) alien_width alien.rect.width alien.x alien_width 2 * alien_width * alien_number alien.rect.x alien.x alien.rect.y alien.rect.height 2 * alien.rect.height * row_number aliens.add(alien) def create_fleet(ai_settings, screen, ship, aliens): alien Alien(ai_settings, screen) number_aliens_x get_number_aliens_x(ai_settings, alien.rect.width) number_rows get_number_rows(ai_settings, ship.rect.height, alien.rect.height) for row_number in range(number_rows): for alien_number in range(number_aliens_x): create_alien(ai_settings, screen, aliens, alien_number, row_number)为什么每个外星人之间留出“一个外星人宽度”的间隔因为如果贴得太紧视觉上就是一团色块玩家根本分不清是几个外星人。每个外星人占2 * alien_width的宽度所以一行能放的外星人数量就是可用宽度除以这个值。外星人的移动是整体移动——整个舰队的左上角坐标决定了所有外星人的显示位置。我通过一个fleet_direction标志配合fleet_drop_speed控制方向碰到右边缘就向下移动一段距离然后反向向左。这个逻辑实现了外星人经典的“之”字形下压走位。当外星人到达屏幕底部或者撞到飞船游戏结束。这一步的检测用pygame的spritecollideany可以一行搞定if pygame.sprite.spritecollideany(ship, aliens): ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets)spritecollideany会检测一个精灵是否和指定组里的任意精灵碰撞返回第一个碰到的对象没有碰撞返回None。这是pygame最常用的碰撞检测手段比手动遍历矩形相交判断简单太多了。子弹打中外星人的检测类似不过要注意这里使用的是组与组的碰撞collisions pygame.sprite.groupcollide(bullets, aliens, True, True)groupcollide返回一个字典键是被打中的子弹值是该子弹打中的外星人列表。这里两个参数True, True表示碰撞后两个对象都消失。如果子弹穿透力强想一次打多个可以把第一个参数改成False。3.5 记分系统与难度递增游戏完整了之后我给它加上计分和难度升级逻辑。计分逻辑本身不复杂但有几个细节值得说说。分数的存储我用了一个GameStats类它同时保存了剩余飞船数和当前分数。为什么不用全局变量因为分数在游戏结束后要重置重新开始时要从0加载放在类里更便于管理生命周期。class GameStats(): def __init__(self, ai_settings): self.ai_settings ai_settings self.reset_stats() self.game_active False self.high_score 0 def reset_stats(self): self.ships_left self.ai_settings.ship_limit self.score 0 self.level 1计分的核心逻辑在于不同颜色的外星人分值应该不同这样玩家才有优先打高分目标的策略选择。外星人每次移动到底部后我让它们缩小一点并加快速度形成难度梯度。等级每提升一级外星人速度增加1.1倍这个倍率影响很微妙——太小了玩家觉得无聊太大了玩家觉得愤怒1.1倍是我反复调试后觉得比较合适的节奏。显示分数时把数字转成字符串后在屏幕右上角渲染。这里我踩过一个坑中文字体在pygame里默认渲染不出来会出现方块。如果你在记分牌上写“分数100”默认字体不支持中文必须加载系统自带的中文字体font pygame.font.SysFont(simhei, 36)或者用英文标签“Score: 100”省事不少。这个坑几乎每个做pygame中文显示的人都会遇到提前给你提个醒。4. 常见问题与排查技巧实录4.1 pygame安装失败的终极解决办法很多新手在第一步装pygame就卡住了。最典型的问题是error: failed to build pygame when getting requirements to build wheel这个报错一看就是pip在尝试从源代码编译pygame而不是直接下载预编译包。原因通常是操作系统的Python版本太新或太老或者pip版本过低导致找不到对应的轮子wheel文件。我的经验是把pip先升级到位python -m pip install --upgrade pip pip install pygame --pre加上--pre参数的意思是允许安装预发行版有时候最新版的pygame正式版还没发布但预发行版已经兼容新版本的Python。如果你在Windows上一直装不上还有一个更稳的方案直接去pygame的官网下载对应你Python版本的.whl文件然后本地安装pip install pygame-2.5.2-cp311-cp311-win_amd64.whl如果装了Python 3.12以上pygame官方版本支持可能滞后我的建议是装一个Python 3.10或3.11的虚拟环境来跑这个项目省心非常多。虚拟环境的创建也很简单python -m venv alien_venv alien_venv\Scripts\activate # Windows source alien_venv/bin/activate # macOS/Linux4.2 游戏卡顿和帧率问题的排查思路有段时间我的游戏跑起来特别卡外星人一多就掉帧。排查之后发现罪魁祸首是每条子弹的update里都加载了一份图片资源。子弹加载一次图片还好但如果你的代码里每生成一个子弹就pygame.image.load()一次图片那么几十颗子弹就要加载几十次图片性能直接崩了。正确的做法是图片资源在类的__init__里一次性加载放到类属性里所有实例共用一份。如果你写的代码里update方法中也出现pygame.image.load()赶紧把它挪到__init__里。另一个卡顿原因是碰撞检测的频率。groupcollide对每组几百个精灵做矩形相交检测运行效率并没有想象中高。如果外星人和子弹特别多可以考虑先用简单的距离判断过滤掉明显不可能碰撞的物体再做精确检测。但在大多数情况下几百个精灵的碰撞检测不至于明显掉帧优先排查图片加载问题更有效。还有一个容易忽略的点主循环里如果没有控制帧率游戏会以你电脑能跑的最高速度运行快的时候几千帧都有这时不仅飞船快到失控CPU也被白白吃满了。解决方法是加一个时钟控制clock pygame.time.Clock() # 在主循环末尾加上 clock.tick(60) # 限制最大帧率为60这样游戏逻辑就以固定的时间步长运行不同性能的电脑上的表现保持一致。4.3 “我按方向键没反应”的排查手册按键没反应是一个高频问题。我第一次遇到这个问题的时候代码看着完全没问题后来才发现是事件处理分支顺序写错了。pygame.event.get()在每一帧循环中只会返回这一帧内产生的事件。如果你在处理事件的循环里又调用了pygame.event.get()第二次获取到的就是一个空列表所有按键丢失了。所以整个主循环里只能有一个获取事件的入口其他逻辑全部围绕这个入口分发。还有一个常见原因被其他窗口抢走了焦点。pygame窗口没有焦点时键盘事件不会发送给游戏窗口此时按什么都没反应。排查方法是打印当前事件列表for event in pygame.event.get(): print(event)如果打印出一堆MouseMotion但就是没有KEYDOWN基本可以确定是窗口焦点问题。4.4 代码写完了运行没画面黑屏是为什么黑屏问题通常有两个原因。一个是背景色没设置pygame.display.set_mode()创建出来的窗口默认是黑色的如果你没有在update_screen里调用screen.fill(ai_settings.bg_color)填充背景色那窗口就一直黑着看起来就像游戏崩了。第二是主循环里的pygame.display.flip()被跳过或者写在了条件分支里导致画面永远不会刷新。另外还要确认你的窗口大小设置没有参数错误比如写下(1000,)而不是(1000, 800)这种元组错误set_mode()会直接抛异常。这类问题虽然细碎但报错日志都会指向具体的行号和错误信息顺着提示改就行。4.5 常见问题速查表问题现象可能原因解决方案pip安装时报编译错误pip版本过旧找不到预编译wheel升级pip或用--pre参数安装预发行版中文显示为方块pygame默认字体不支持中文用pygame.font.SysFont(simhei, 36)加载中文字体游戏运行极快控制不住没有限制帧率用Clock.tick(60)控制帧率按键有时失灵重复调用event.get()确保主循环只有一个事件轮询入口子弹打到外星人但没消失groupcollide参数配置错误碰撞后两组都需销毁时两个参数都设为True窗口黑屏没画面忘记填充背景色在update_screen中调用screen.fill()外星人不下移fleet_direction未正确翻转检查边缘检测逻辑到达边缘时翻转方向5. 扩展思路这个项目还能变成什么样子外星人入侵完成后你可以往不同方向扩功能扩展的过程也是练代码的过程。我个人尝试过几个方向效果都还不错。第一给游戏加一个“Boss关”。每过5关生成一个巨大的外星人血条厚攻击方式多打死了有额外加分。实现思路其实不复杂就是定义一个Boss类和BossBullet类Boss每隔几秒发射一次子弹玩家需要躲子弹、打Boss。碰撞检测和普通外星人是一样的只是逻辑多一点。第二改成双人模式。一个玩家用键盘方向键控制飞船另一个玩家用WASD控制另一艘飞船各自计分。这个改动量不大主要是事件处理里需要分辨两个玩家的按键以及飞船、子弹的实例都各建一套。第三增加道具系统。外星人被打死后有概率掉落“加速”“三连发”“护盾”等道具玩家吃到后获得不同的增益效果。实现方式是新增一个PowerUp类它的update逻辑和子弹差不多只是方向向下和飞船做碰撞检测。我记得有一次我给自己限时48小时强行给游戏加了音效和粒子特效结果音效容易弄粒子特效反而费了不少功夫。用pygame做粒子特效的常规办法是生成一堆小矩形赋予不同的速度和透明度逐帧更新位置和生命周期。这部分代码量不大但看起来效果很酷——至少比我预期中酷。6. 写在最后的实操心得做外星人入侵这个项目我前后重写了三遍。第一遍跟着书抄什么也没记住第二遍不看书自己写卡在碰撞检测卡了一整天第三遍从头设计整体架构才算真正把这个项目的精髓吃透了。我自己的体会是这个项目最好的打开方式不是一次写完而是分三次写第一次照着参考代码敲通体验完整的开发流程第二次不要看代码只靠需求描述从零写出一个能玩的版本第三次加入自己的设计改动比如新的武器、新的敌人、新的计分规则。三遍下来你对pygame的掌握深度会远超只抄一遍代码的人。最后分享一个小技巧调试pygame游戏时别依赖print视觉信息比文本信息直观得多。你可以在画面里临时绘制调试矩形把碰撞盒显示出来一眼就能看出精灵之间的碰撞区域是否和视觉位置一致。# 在update_screen末尾临时加一段调试代码 pygame.draw.rect(screen, (255, 0, 0), ship.rect, 2) pygame.draw.rect(screen, (0, 255, 0), alien.rect, 2)画出红色和绿色的矩形框如果可见画面位置和碰撞框对不上问题就一目了然。调试完把这行删掉就行。这个习惯我保留到现在做任何游戏项目都会先用可视化方式检查碰撞区域。外星人入侵只是一个起点把它的代码吃透了你再用pygame做其他2D游戏时会发现大多数机制不过是这个项目里各组件的排列组合而已。本文还有配套的精品资源点击获取