简介:这是一份面向Python初学者与pygame爱好者的「外星人入侵」游戏完整源代码,基于win10+Python3.7+pygame开发,适合作为《Python编程从入门到实践》项目实战的配套练习素材。代码实现了飞船上下键移动、空格发射子弹、外星人群体控制、记分模式、随难度提升的加速机制、最高分记录显示以及飞船剩余数量图标等核心玩法,能帮助读者理解游戏循环、精灵与碰撞检测等关键概念。压缩包共688个文件,以557个py源码为主,另含pyd、pyc编译文件、exe可执行程序、dll动态库及txt说明等,整体约13.42MB,目录结构清晰,便于按模块查阅与二次修改。目前已有2536人学习下载,配合系列学习笔记使用,可快速上手并完成属于自己的外星人入侵小游戏。
1. 从一份能跑起来的 pygame 源码说起:外星人入侵到底练的是什么
很多新手学完 Python 语法就卡在“下一步写什么”上,对着空编辑器发呆。这份《外星人入侵》pygame 源代码就是来治这个病的——它不是零散片段,而是一个完整可运行的小游戏工程:玩家飞船左右移动、按空格发射子弹、成群外星人阶梯式下压、碰撞检测、计分板、生命值、关卡推进、游戏结束重开,一整套闭环都在里面。技术栈很纯粹:Python + pygame,没有额外框架,不需要数据库,装好解释器和库就能跑。适合谁?刚学完函数和类、想找一个“能看见画面动起来”的练手项目的开发者;也适合想拿它当模板,改成飞机大战、打砖块、弹幕射击的二次开发者。下面我按“能跑 → 看懂 → 改得动”的顺序,把这份源码拆开讲。
2. 环境搭建与首次运行:把这份 pygame 工程跑起来
2.1 依赖安装与版本选择
pygame 对 Python 版本比较宽容,3.8 到 3.12 都能装。我一般会先建一个独立虚拟环境,避免和系统里其他库打架。Windows 下用 venv,macOS 和 Linux 同理:
# 创建虚拟环境,目录名随意,这里叫 venv python -m venv venv # 激活环境:Windows venv\Scripts\activate # 激活环境:macOS / Linux source venv/bin/activate # 安装 pygame,指定较新稳定版 pip install pygame==2.5.2装完用一行命令验证是否成功:
python -c "import pygame; print(pygame.ver)"能打印出版本号就说明环境没问题。如果报ModuleNotFoundError,九成是虚拟环境没激活,或者 pip 装到了另一个 Python 解释器下。常见做法是用python -m pip install pygame而不是直接pip install,这样能保证装进当前解释器。
2.2 目录结构与入口文件
这份源码的目录结构很典型,适合新手对照理解“一个游戏工程该长什么样”:
alien_invasion/ ├── alien_invasion.py # 主入口,游戏循环 ├── settings.py # 所有可调参数集中管理 ├── ship.py # 飞船类 ├── bullet.py # 子弹类 ├── alien.py # 外星人类 ├── game_stats.py # 游戏统计数据(分数、生命、等级) ├── scoreboard.py # 计分板渲染 ├── button.py # 开始按钮 ├── images/ # 图片资源 │ ├── ship.bmp │ └── alien.bmp └── sounds/ # 音效(可选)入口文件alien_invasion.py负责初始化 pygame、创建窗口、实例化各个对象、跑主循环。参数全部抽到settings.py,这是这份源码最值得学的一点:改速度、改屏幕尺寸、改外星人数量,都只动一个文件,不用满工程找数字。
2.3 主循环骨架与运行验证
游戏能不能跑,关键看主循环。下面是从源码里提炼出的核心骨架:
import pygame from settings import Settings from ship import Ship import game_functions as gf def run_game(): pygame.init() ai_settings = 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) # 更新飞船位置 ship.update() # 重绘屏幕 gf.update_screen(ai_settings, screen, ship) run_game()逻辑说明:pygame.init()初始化所有子模块;set_mode返回的screen是一个 Surface,后续所有绘制都往它上面画;while True是游戏心脏,每一帧做三件事——收事件、算状态、重绘。参数方面,screen_width和screen_height在settings.py里定义,默认常见值是 1200×800,改小一点在老机器上更流畅。运行命令就是python alien_invasion.py,窗口弹出、飞船能左右移动,就说明这份源码在你机器上跑通了。
3. 核心模块拆解:飞船、子弹、外星人三套类怎么协作
3.1 Ship 类:位置、移动与边界限制
飞船类是整个交互的起点,它管三件事:加载图片、响应按键、限制不能跑出屏幕。
import pygame class Ship: def __init__(self, ai_settings, screen): 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_factor if self.moving_left and self.rect.left > 0: self.center -= self.ai_settings.ship_speed_factor # 把浮点中心同步回 rect self.rect.centerx = self.center def blitme(self): self.screen.blit(self.image, self.rect)逻辑说明:self.center用 float 存,是因为ship_speed_factor可能是 1.5 这种小数,如果直接改rect.centerx(整数),小数部分会被截断,飞船移动会“卡顿”。这是 pygame 里一个经典细节,很多新手写的飞船一抖一抖,就是栽在这里。参数ship_speed_factor在 settings 里,默认 1.5,调大飞船更跟手,调小更精细。边界判断用self.rect.right < self.screen_rect.right,保证飞船图片完全在屏幕内才继续移动。
3.2 Bullet 类与子弹编组:用 Group 管理批量对象
子弹如果一颗颗手动管理,代码会乱成一团。源码里用pygame.sprite.Group统一收口:
import pygame from pygame.sprite import Sprite class Bullet(Sprite): def __init__(self, ai_settings, screen, ship): super().__init__() self.screen = screen # 在 (0,0) 处创建子弹矩形,再移到飞船顶部 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 # 用浮点存 y 坐标,保证速度是小数时也平滑 self.y = float(self.rect.y) self.color = ai_settings.bullet_color self.speed_factor = ai_settings.bullet_speed_factor def update(self): # 子弹向上飞,y 减小 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画一个矩形,省资源。self.y同样是 float,理由和飞船一样。子弹创建后加入bullets编组,更新时调用bullets.update(),绘制时遍历bullets.sprites()。编组的好处是:删除飞出屏幕的子弹只需bullets.remove(bullet),不用自己维护列表索引。参数bullet_speed_factor默认 3,bullet_width常见 3、bullet_height15,改宽度能做出“激光炮”效果。
3.3 Alien 类与舰队生成:嵌套循环铺出外星人矩阵
外星人舰队是这份源码里最能体现“用循环生成对象”的地方:
import pygame from pygame.sprite import Sprite class Alien(Sprite): def __init__(self, ai_settings, screen): super().__init__() self.screen = screen self.ai_settings = ai_settings self.image = pygame.image.load('images/alien.bmp') self.rect = self.image.get_rect() # 初始放在左上角附近 self.rect.x = self.rect.width self.rect.y = self.rect.height self.x = float(self.rect.x) def update(self): # 水平移动,速度由 settings 控制 self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction) self.rect.x = self.x def check_edges(self): # 碰到屏幕边缘返回 True,供主循环翻转方向 screen_rect = self.screen.get_rect() if self.rect.right >= screen_rect.right: return True elif self.rect.left <= 0: return True舰队生成在game_functions.py里,用两层循环按行列铺开:
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)逻辑说明:fleet_direction为 1 表示向右、-1 表示向左,碰到边缘就乘 -1 翻转。check_edges返回 True 时,主循环把整个舰队下移一段fleet_drop_speed,同时翻转方向。参数alien_speed_factor默认 1,随着等级提升会加快。这里有个容易翻车的点:外星人数量是按图片宽度算的,如果换了更大的 alien 图片,一行放不下会重叠,需要同步调alien.rect.width或屏幕宽度。
4. 游戏逻辑层:事件、碰撞、计分与状态机
4.1 事件处理:键盘按下与松开要分开管
pygame 的键盘事件分KEYDOWN和KEYUP,飞船的连续移动必须靠标志位,不能只在 KEYDOWN 里移动一次:
def check_keydown_events(event, ai_settings, screen, ship, bullets): if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: fire_bullet(ai_settings, screen, ship, bullets) elif event.key == pygame.K_q: sys.exit() def check_keyup_events(event, ship): if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False逻辑说明:按下时把标志置 True,松开置 False,ship.update()每帧根据标志决定是否移动,这样才有“按住持续移动”的手感。空格发射子弹时,源码里会先判断len(bullets) < ai_settings.bullets_allowed,限制屏幕上子弹数量,防止按住空格刷屏。bullets_allowed默认 3,改成 10 就是弹幕模式。
4.2 碰撞检测:spritecollideany 与 groupcollide
碰撞是游戏逻辑的核心,源码用了两个 pygame 内置函数:
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets): # 子弹和外星人碰撞:两个都删 collisions = pygame.sprite.groupcollide( bullets, aliens, True, True) if collisions: for aliens_hit in collisions.values(): stats.score += ai_settings.alien_points * len(aliens_hit) sb.prep_score() def check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens, bullets): screen_rect = screen.get_rect() for alien in aliens.sprites(): if alien.rect.bottom >= screen_rect.bottom: # 外星人到底部,扣一条命 ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets) break逻辑说明:groupcollide第一个参数是主动方,第二个是被动方,第三、四个布尔值分别表示“碰撞后是否删除主动方/被动方”。这里子弹和外星人都删。check_aliens_bottom遍历外星人,只要有任何一个触底就扣命并重置舰队。参数alien_points默认 50,等级越高可以调大,做出“越往后分越高”的激励。
4.3 游戏状态与计分板:GameStats 与 Scoreboard 分工
状态和渲染分开,是这份源码结构清晰的关键。GameStats只存数据:
class GameStats: def __init__(self, ai_settings): self.ai_settings = ai_settings self.reset_stats() self.game_active = False # 开局不激活,等点开始按钮 def reset_stats(self): self.ships_left = self.ai_settings.ship_limit self.score = 0 self.level = 1Scoreboard负责把数据画到屏幕上,包括分数、最高分、等级、剩余飞船。ship_limit默认 3,game_active控制游戏是否进行中——为 False 时主循环只画开始按钮,不更新飞船和外星人。这个状态机设计让“开始游戏”和“游戏结束”复用同一套循环,不用写两个 while。
5. 避坑与常见问题排查:跑不起来、闪退、卡顿怎么查
5.1 现象:窗口一闪而过或直接报错退出
原因:最常见的是图片路径不对。源码里写的是'images/ship.bmp',这是相对路径,必须在你运行python alien_invasion.py的那个目录下存在images文件夹。如果你在别的目录用绝对路径运行,就会找不到图片,pygame 抛FileNotFoundError或直接黑屏退出。解决:要么cd到工程根目录再运行,要么把路径改成基于os.path.dirname(__file__)的绝对路径。
5.2 现象:飞船移动一顿一顿的,不跟手
原因:ship_speed_factor是小数,但rect.centerx是整数,直接改 rect 会截断小数部分。源码里用self.center = float(self.rect.centerx)单独存浮点值,每帧先更新self.center再同步回rect.centerx。如果你自己改代码时把这一步省了,就会卡顿。解决:检查Ship.update()里有没有 float 中转这一步。
5.3 现象:子弹打中外星人但分数不涨
原因:groupcollide返回的是一个字典,key 是子弹,value 是撞到的外星人列表。源码里用collisions.values()遍历,再乘len(aliens_hit)。如果你只写了if collisions:但没遍历 values,分数就只加一次或者不加。解决:确认计分逻辑里对每个碰撞组都做了score += alien_points * len(...)。
5.4 现象:外星人越走越快,最后瞬移
原因:alien_speed_factor在升级时被乘了一个系数,但fleet_direction翻转时如果同时改了速度,可能导致一帧移动距离超过外星人宽度,看起来像瞬移。解决:升级时速度增幅别太大,常见做法是每次乘 1.1 而不是 1.5;同时确保check_edges在移动之后判断,避免一帧内反复翻转。
5.5 现象:游戏结束后点按钮没反应
原因:game_active状态没重置,或者reset_stats()没被调用。源码里点开始按钮会执行stats.reset_stats()、清空 aliens 和 bullets、重新创建舰队、把game_active置 True。如果你改了按钮逻辑但漏了其中一步,就会出现“按钮能点但游戏不开始”。解决:把重置流程写成一个函数,按钮回调里只调这一个函数,避免漏项。
6. 二次开发改造思路:从外星人入侵到你自己玩法的三个切入点
这份源码最大的价值不是让你照着抄一遍,而是给你一个可改的骨架。我一般会从三个地方下手,按难度从低到高排。
第一个切入点是换皮和调参,成本最低。把images/里的ship.bmp和alien.bmp换成你自己的图,注意尺寸别差太多,否则要同步改settings.py里的alien.rect.width相关计算。参数表里这几个值最值得动:
| 参数 | 默认值 | 调大效果 | 调小效果 |
|---|---|---|---|
| ship_speed_factor | 1.5 | 飞船更跟手 | 移动更精细 |
| bullet_speed_factor | 3 | 子弹更快 | 子弹更慢,可做弹幕 |
| alien_speed_factor | 1 | 外星人压迫感强 | 节奏更休闲 |
| fleet_drop_speed | 10 | 下压更狠 | 下压更缓 |
| bullets_allowed | 3 | 屏幕子弹更多 | 更考验节奏 |
第二个切入点是加新玩法。比如加“外星人随机发射子弹”:在Alien类里加一个fire_bullet方法,主循环里按概率调用,再建一个alien_bullets编组,碰撞检测时和飞船做spritecollideany。这里要注意,外星人子弹和玩家子弹要分两个 Group,否则groupcollide会把它们互相抵消。另一个常见改法是加“道具掉落”:外星人被击毁时按概率生成一个PowerUp精灵,飞船碰到就加速或加命,逻辑和子弹碰撞几乎一样,复制一份改参数即可。
第三个切入点是重构状态管理。原版用game_active一个布尔值控制开始和结束,玩法一多就不够用。我一般会引入一个简单的状态枚举:
from enum import Enum class GameState(Enum): MENU = 1 # 主菜单 PLAYING = 2 # 游戏中 PAUSED = 3 # 暂停 GAME_OVER = 4 # 结束 # 主循环里根据状态决定更新哪些对象 if stats.state == GameState.PLAYING: gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) gf.update_aliens(ai_settings, stats, sb, screen, ship, aliens, bullets) elif stats.state == GameState.PAUSED: # 只画暂停文字,不更新逻辑 pass这样加暂停、加菜单、加关卡切换都只是加一个分支,不用把主循环拆得七零八落。验证方法也简单:每加一个状态,先手动把stats.state设成那个值跑一遍,看画面和逻辑是否符合预期,再写触发条件。
最后说个我自己的血泪经验:改这份源码时,每动一个参数或加一个功能,都先跑一遍“开始 → 打几枪 → 撞一次 → 游戏结束 → 重开”的完整流程。我早期图快,只测了开局就提交,结果游戏结束重开时外星人舰队没清空,第二局一开局就满屏外星人直接扣命。从那以后我每次改完碰撞或状态相关代码,都强制走一遍完整闭环再往下写。希望这份拆解能帮你少走点弯路,把这份 pygame 源码真正变成自己的项目。
本文还有配套的精品资源,点击获取