简介这份资源是面向Python初学者与游戏开发爱好者的pygame实战项目源码对应经典教材《Python编程从入门到实践》中的「外星人入侵」案例。包内包含飞船移动、子弹发射、外星人舰队生成与碰撞检测、记分与最高分记录、速度随难度升级、剩余飞船图标显示等完整逻辑开发环境为Win10搭配Python3.7与pygame适合边学边练、巩固面向对象与事件循环等核心概念。压缩包共688个文件以557个py源码为主辅以pyd、pyc、dll等运行依赖以及exe、txt、xml、bat等配置与说明文件整体约13.42MB目录结构清晰便于按模块查阅与二次修改。目前已有2536人学习下载配合系列学习笔记使用可帮助读者快速理解游戏主循环、精灵组管理与状态更新流程并在此基础上扩展关卡、音效与难度曲线等自定义玩法。1. 从一份能跑起来的 pygame 源码说起外星人入侵到底能帮你练什么很多人学 Python 卡在“语法都会项目不会写”这一步。看完了列表推导式、类与继承、文件读写真让动手做一个能交互的东西脑子里还是空的。这份“外星人入侵”pygame 源代码就是冲着这个缺口来的——它把一个小型 2D 射击游戏完整拆开摆在你面前玩家飞船左右移动、按空格发射子弹、外星人舰队整体横移下压、碰撞检测判定命中、计分板实时刷新、生命值耗尽后游戏结束。整套逻辑跑在一个主循环里没有黑盒每一行都能改。它适合两类人一是刚学完 Python 基础、想找一个“代码量可控、结构完整”的练手项目二是已经会写脚本、但没碰过游戏循环和事件驱动模型的开发者想借这个项目理解帧率控制、精灵组管理和状态切换。技术栈就是 pygame 加标准库不需要额外引擎装完依赖就能跑。2. 环境搭建与首次运行从零把窗口点亮2.1 依赖安装与版本选择pygame 的安装本身不复杂但版本选错会直接导致窗口闪退或音频初始化失败。常见做法是锁定一个稳定版本不要盲目追新。# 建议使用 Python 3.8 到 3.11 之间的版本 python -m pip install pygame2.5.2 # 验证安装是否成功 python -m pygame --version这里指定2.5.2是因为它在 Windows 和 macOS 上对 SDL2 的封装比较成熟音频和显示模块的兼容性经过大量项目验证。如果你用的是 Apple Silicon 芯片的机器低于 2.1 的版本会出现窗口无法置顶的问题所以不要用太老的版本。安装完成后建议单独跑一个最小窗口测试确认显示模块正常import pygame import sys pygame.init() screen pygame.display.set_mode((800, 600)) pygame.display.set_caption(窗口测试) while True: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() screen.fill((20, 20, 20)) pygame.display.flip()这段代码只做三件事初始化 pygame、创建 800x600 的窗口、进入事件循环监听关闭按钮。pygame.display.flip()负责把绘制内容刷到屏幕上少了它窗口会一片黑。如果这一步能正常弹出窗口并关闭说明环境没问题可以进入项目源码。2.2 项目目录结构与文件职责拿到源码后先别急着运行花两分钟看清目录。一个组织良好的 pygame 项目通常长这样alien_invasion/ ├── alien_invasion.py # 主入口游戏循环和状态调度 ├── settings.py # 所有可调参数集中管理 ├── ship.py # 飞船类处理移动和绘制 ├── bullet.py # 子弹类处理发射和位置更新 ├── alien.py # 外星人类处理舰队个体行为 ├── game_stats.py # 游戏统计数据如剩余生命、最高分 ├── scoreboard.py # 计分板渲染 ├── button.py # 开始按钮 └── images/ # 飞船、外星人、子弹的贴图资源这个结构的好处是职责分离settings.py里放窗口尺寸、速度、颜色、子弹上限这些常量改数值不用翻遍所有文件alien_invasion.py只负责主循环和事件分发每个精灵类自己管自己的update()和blitme()。我一般会先打开settings.py把所有速度参数调慢一半方便观察运动逻辑。2.3 主循环的启动流程主循环是整个游戏的骨架理解它就理解了 pygame 项目的运行方式。下面是从源码中提炼的核心结构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) ship.update() gf.update_screen(ai_settings, screen, ship) run_game()check_events负责监听键盘和退出事件ship.update()根据按键状态更新飞船位置update_screen重绘背景和飞船。这个顺序不能乱先处理输入再更新状态最后渲染。如果先渲染再更新画面会延迟一帧快速移动时会有拖影感。ai_settings作为参数层层传递是为了让所有模块共享同一份配置避免全局变量满天飞。3. 核心模块拆解飞船、子弹与外星人舰队的实现细节3.1 飞船移动与边界限制飞船类继承自pygame.sprite.Sprite这是 pygame 推荐的精灵管理方式。核心逻辑在update()里import pygame from pygame.sprite import Sprite 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_factor if self.moving_left and self.rect.left 0: self.center - self.ai_settings.ship_speed_factor self.rect.centerx self.center这里有个关键设计self.center用浮点数存储飞船的水平中心而self.rect.centerx只接受整数。如果直接用rect.centerx做加减速度小于 1 时会被截断成 0飞船根本动不了。用浮点中间变量再赋值给rect就能支持 0.5、1.5 这种小数速度。边界判断分别检查左右两侧防止飞船滑出窗口。moving_right和moving_left由事件循环里的KEYDOWN和KEYUP控制按住不放会持续移动这是通过每帧检查标志位实现的不是靠重复触发事件。3.2 子弹发射与精灵组管理子弹的逻辑比飞船多一层它需要被批量管理。源码里用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 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_factor 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画一个矩形省去加载贴图的开销。self.y同样是浮点中间变量保证子弹速度可以小于 1。发射逻辑在主循环里def check_keydown_events(event, ai_settings, screen, ship, bullets): if event.key pygame.K_SPACE: if len(bullets) ai_settings.bullets_allowed: new_bullet Bullet(ai_settings, screen, ship) bullets.add(new_bullet)bullets_allowed限制屏幕上同时存在的子弹数量通常是 3 到 5 发。没有这个限制按住空格会瞬间生成几百颗子弹帧率直接崩掉。每帧更新时遍历精灵组把飞出屏幕的子弹删掉def update_bullets(bullets): bullets.update() for bullet in bullets.copy(): if bullet.rect.bottom 0: bullets.remove(bullet)注意这里用bullets.copy()遍历因为不能在遍历中直接修改原组否则会抛RuntimeError。这是 pygame 精灵组操作里最常见的坑之一。3.3 外星人舰队的生成与移动外星人舰队不是一个个手动摆放的而是用嵌套循环批量生成def create_fleet(ai_settings, screen, 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, 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) def get_number_aliens_x(ai_settings, alien_width): available_space_x ai_settings.screen_width - 2 * alien_width return int(available_space_x / (2 * alien_width))get_number_aliens_x根据屏幕宽度和单个外星人宽度算出每行能放几个留出左右边距。get_number_rows类似根据屏幕高度和飞船位置算出能放几行。这种动态计算的好处是改窗口尺寸不用手动调数量。舰队移动逻辑在Alien.update()和主循环的check_fleet_edges里配合完成def check_fleet_edges(ai_settings, aliens): for alien in aliens.sprites(): if alien.check_edges(): change_fleet_direction(ai_settings, aliens) break def change_fleet_direction(ai_settings, aliens): for alien in aliens.sprites(): alien.rect.y ai_settings.fleet_drop_speed ai_settings.fleet_direction * -1只要有一个外星人碰到边缘整队下移并反向。fleet_direction在 1 和 -1 之间切换控制左右移动方向。这里用break是因为一旦触发反向其他外星人的边缘判断就不需要了提前退出循环省性能。3.4 碰撞检测与游戏状态切换碰撞检测用 pygame 内置的sprite.groupcollide和sprite.spritecollideanydef update_bullets(ai_settings, screen, ship, aliens, bullets): bullets.update() for bullet in bullets.copy(): if bullet.rect.bottom 0: bullets.remove(bullet) 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() if len(aliens) 0: bullets.empty() ai_settings.increase_speed() create_fleet(ai_settings, screen, aliens)groupcollide的两个布尔参数分别表示“子弹碰撞后是否删除”和“外星人碰撞后是否删除”都设为True。返回值是一个字典键是子弹值是碰到的外星人列表。用len(aliens_hit)可以处理一颗子弹同时打中多个外星人的情况分数按数量累加。游戏状态切换靠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 0game_active初始为False点击开始按钮后变True。主循环里根据这个标志决定是否更新游戏逻辑if stats.game_active: ship.update() update_bullets(ai_settings, screen, ship, aliens, bullets) update_aliens(ai_settings, stats, screen, ship, aliens, bullets)飞船被撞后ships_left减一减到零时game_active置回False显示结束界面。这套状态机虽然简单但足够支撑一个完整的游戏流程。4. 避坑与排查跑这个项目时最容易翻车的几个地方4.1 窗口一闪而过或直接卡死现象运行alien_invasion.py后窗口闪一下就消失或者一直黑屏无响应。原因最常见的是主循环里缺少pygame.event.get()的事件处理导致系统认为程序未响应另一种是pygame.display.flip()被放在了错误的位置或者根本没有调用。解决确认主循环里有完整的事件遍历并且每帧都调用了flip()或update()。如果窗口黑屏但没卡死检查screen.fill()的颜色值是否和背景图冲突以及blitme()是否在fill()之后调用。4.2 飞船移动速度异常或完全不动现象按左右键飞船没反应或者移动速度极快直接飞出屏幕。原因ship_speed_factor设置过大或过小更隐蔽的是self.center没有用浮点数导致小数值被截断。解决打开settings.py把ship_speed_factor先设为 1.5 测试。如果还是不动检查Ship.update()里是否用了self.rect.centerx 而不是self.center 。前者在速度小于 1 时永远加不上。4.3 子弹无法发射或发射后不消失现象按空格没子弹或者子弹飞出屏幕后仍然占用内存越积越多。原因bullets_allowed设得太小或为 0子弹删除逻辑里遍历时直接修改了原精灵组。解决确认bullets_allowed至少为 1。删除子弹时必须用bullets.copy()创建副本再遍历否则 pygame 会抛异常或静默失败。另外检查Bullet.update()里self.y是否在递减如果写成递增子弹会往下飞。4.4 外星人舰队移动方向错乱或卡在边缘现象舰队碰到边缘后不反向或者反向逻辑触发多次导致来回抖动。原因check_fleet_edges里没有break多个外星人同时触发反向fleet_direction被连续取反两次等于没变。解决在change_fleet_direction调用后立即break确保一帧只反向一次。同时检查Alien.check_edges()的判断条件是否用了self.rect.right self.screen_rect.right和self.rect.left 0两个条件都要有。4.5 碰撞检测失效或分数不更新现象子弹穿过外星人但没有消失或者外星人被打掉但分数不变。原因groupcollide的参数顺序写反或者碰撞后没有正确处理返回值字典。解决确认pygame.sprite.groupcollide(bullets, aliens, True, True)的第一个参数是子弹组第二个是外星人组。返回值是字典用collisions.values()取被击中的外星人列表再乘以单个分值累加到stats.score。如果分数不刷新检查sb.prep_score()是否在碰撞后调用了。5. 二次开发改造从能跑到好用几个我常做的调整5.1 把参数外置成配置文件源码里settings.py是硬编码的 Python 常量。如果想在不改代码的情况下调难度可以改成读取 JSONimport json class Settings: def __init__(self): with open(config.json, r) as f: config json.load(f) self.screen_width config[screen_width] self.screen_height config[screen_height] self.ship_speed_factor config[ship_speed] self.bullet_speed_factor config[bullet_speed] self.alien_speed_factor config[alien_speed] self.bullets_allowed config[bullets_allowed]对应的config.json{ screen_width: 1200, screen_height: 800, ship_speed: 2.0, bullet_speed: 3.0, alien_speed: 1.0, bullets_allowed: 5 }这样改难度不用碰 Python 文件测试不同参数组合时效率高很多。我一般会准备三份配置简单、普通、困难运行时用命令行参数选择。5.2 增加音效和粒子效果pygame 的mixer模块可以加载音效在子弹发射和碰撞时触发# 初始化时加载 pygame.mixer.init() bullet_sound pygame.mixer.Sound(sounds/laser.wav) explosion_sound pygame.mixer.Sound(sounds/explosion.wav) # 发射子弹时 bullet_sound.play() # 碰撞时 explosion_sound.play()粒子效果可以用简单的矩形扩散模拟碰撞位置生成 5 到 8 个小方块每帧向外移动并缩小持续 0.3 秒后删除。不需要引入额外库用精灵组管理就行。5.3 用状态模式重构游戏流程当前用game_active布尔值控制状态扩展性有限。想加暂停、设置菜单、关卡选择时可以改成状态字典class GameState: def __init__(self): self.states { menu: MenuState(), playing: PlayingState(), paused: PausedState(), game_over: GameOverState() } self.current self.states[menu] def handle_event(self, event): self.current.handle_event(event) def update(self): self.current.update() def draw(self, screen): self.current.draw(screen)每个状态类实现自己的handle_event、update和draw。切换状态时只改self.current的引用。这样加新状态不用动主循环符合开闭原则。5.4 验证改造是否成功的几个检查点改完之后别急着说“能跑就行”按下面几条过一遍检查项预期结果失败时先看窗口尺寸改为 1200x800飞船和外星人位置自适应get_number_aliens_x的计算子弹上限改为 5按住空格最多 5 发bullets_allowed读取路径音效文件缺失游戏不崩溃静默跳过try/except包裹加载暂停后恢复飞船位置和分数不变状态切换时是否重置数据连续打掉三波外星人速度递增但不超过上限increase_speed的边界判断我一般会在改完参数后故意把alien_speed_factor调到 10 倍看游戏会不会因为帧率下降而卡死。如果卡死说明主循环里有性能瓶颈通常是碰撞检测的精灵组太大需要加空间分区或限制同屏数量。从那以后我每次拿到一份游戏源码都强制先跑一遍最小窗口测试再逐模块替换参数验证边界最后才动核心逻辑。这套习惯帮我省下了大量“改完不知道哪里崩了”的时间。希望这份拆解能帮你把 pygame 真正用起来而不是停在“看过等于会了”的阶段。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?