ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python游戏开发入门:从Pygame外星人入侵源码看精灵与碰撞检测

Python游戏开发入门:从Pygame外星人入侵源码看精灵与碰撞检测 简介这是一份基于Pygame开发的《外星人入侵》小游戏完整源码适合刚开始学习Python编程或希望入门游戏开发的学习者。项目完整复原了经典玩法玩家控制飞船左右移动、空格键射击、成群外星人不断向下逼近每消灭一轮外星人后新一波会以更快速度出现当飞船被撞或外星人抵达底部时损失生命三条生命耗尽则游戏结束直接运行alien_invasion.py即可体验完整流程。压缩包共19个文件、约24KB其中包含9个.py源码、8个.pyc编译缓存和2张.bmp图片素材源码按职责拆分为飞船、外星人、子弹、设置、计分板、游戏状态、按钮、游戏函数等多个模块目录结构简洁明了非常便于逐模块分析事件监听、碰撞检测、群体生成、分数更新等机制。该资源已有2335人学习/下载可应用于课程设计、Python实训或个人趣味项目既能熟悉Pygame基础用法也能锻炼面向对象设计和整体游戏逻辑组织能力。1. 外星人入侵为什么每个 Python 学习者都绕不开这个项目在搜索引擎里输入“Python外星人入侵小游戏源代码”每天都有大量开发者翻出这个经典项目。它出自《Python编程从入门到实践》的实战章节几乎成了 Python 游戏开发的“Hello World”。你搜到的源码可能来自 GitHub 仓库、个人博客或网盘分享但无论哪个版本核心永远是 pygame 精灵碰撞 事件循环这套组合。这个项目之所以值得你认真读完源码而不是复制就跑是因为它体量适中——大约 500 到 800 行代码——却完整覆盖了游戏开发的全部核心环节游戏循环、事件处理、精灵组管理、碰撞检测、状态切换。你学到的不是“怎么用 pygame 画图”而是“怎么组织一个会持续运行、实时响应输入、维护多对象状态的程序”。这正是从脚本思维转向工程思维的关键一步。2. 搭建环境用最小命令把 pygame 跑起来2.1 先确认 Python 版本再装 pygame在写任何代码之前先检查当前 Python 版本。我见过太多人卡在第一步python3 和 python 命令不区分装错了包管理器或者 Conda 环境与实际运行的 Python 版本对不上。python --version pip --version如果输出分别是 Python 3.x3.8 及以上都行和 pip 20直接安装 pygamepip install pygame安装完成后用一个两行命令验证是否能正常加载渲染模块python -c import pygame; print(pygame.version.ver)输出版本号就说明环境正常。如果报错 ModuleNotFoundError常见原因是 pip 装到了系统 Python而命令行跑的是虚拟环境。解决办法python -m pip install pygame用python -m pip可以确保安装到当前解释器对应的包目录中。2.2 四个文件的项目布局别把一切塞进 main.py搜索下载的源码通常有两种结构单文件版和多文件版。单文件版适合快速跑通但如果你要改代码练手我建议按多文件方式组织这是原书推荐的架构也是社区里最常见的工程化方式alien_invasion/ ├── alien_invasion.py # 主程序游戏循环入口 ├── settings.py # 所有可调参数集中管理 ├── ship.py # 飞船类 ├── bullet.py # 子弹类 ├── alien.py # 外星人类 └── images/ # 存放位图资源这种拆分的好处是调参数不用在代码里反复搜索数字改玩法逻辑不会误伤显示代码。游戏开发里最难受的不是写新功能而是每次改一个速度数值都要翻三处文件。2.3 初始化窗口理解 pygame.display.set_mode 的返回import sys import pygame from settings import Settings 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) while True: for event in pygame.event.get(): if event.type pygame.QUIT: sys.exit() screen.fill(ai_settings.bg_color) pygame.display.flip() run_game()pygame.init()初始化所有模块set_mode返回一个 Surface 对象所有绘制操作都在这块“画布”上完成。flip()将后台缓冲区的图像更新到屏幕上——pygame 使用双缓冲机制先画完再一次性呈现避免画面闪烁。event.get()从事件队列中拉取用户操作比如点击关闭按钮、按键按下。这段代码虽然只画了一个纯色背景但它是整个游戏循环的骨架事件处理、状态更新、绘制渲染三者缺一不可。3. 从源码读懂外星人入侵的核心结构3.1 Settings 类把屏幕尺寸、颜色、速度全部收口下载的源码文件里settings.py是最值得逐行读的模块。它把所有游戏常量集中在一起方便后期调整class Settings(): 存储《外星人入侵》的所有设置的类 def __init__(self): 初始化游戏的静态设置 # 屏幕设置 self.screen_width 1200 self.screen_height 800 self.bg_color (230, 230, 230) # 飞船的设置 self.ship_speed_factor 1.5 self.ship_limit 3 # 子弹设置 self.bullet_speed_factor 3 self.bullet_width 3 self.bullet_height 15 self.bullet_color 60, 60, 60 self.bullets_allowed 3 # 外星人设置 self.alien_speed_factor 1 self.fleet_drop_speed 10 self.fleet_direction 1fleet_direction是 1 或 -1 的取值1 表示向右移动-1 表示向左。这个设计比你想象的更常见用一个正负号字段替代布尔值切换方向时直接乘以 -1 即可。很多实际游戏代码里都有类似处理比如控制角色水平翻转。我发现一个细节子弹的初始速度是 3飞船速度是 1.5。如果觉得游戏太简单或太难首先调的就是这两个数字。bullets_allowed限制了屏幕上同时存在的子弹数这是性能考量——防止玩家疯狂点击导致子弹对象无限增长拖慢碰撞检测。3.2 Ship 和 Alien 类Pygame 的 surface 与移动逻辑飞船和外星人的核心代码几乎对称先看飞船的移动逻辑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 # 在飞船的属性center中存储小数值 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.center更新rect对象 self.rect.centerx self.center def blitme(self): 在指定位置绘制飞船 self.screen.blit(self.image, self.rect)get_rect()是 pygame 的 Rect 对象它提供 centerx、bottom、left、right 等边界属性碰撞检测全靠它。blitme()里的blit是“块传输”的缩写把图片 Surface 绘制到屏幕 Surface 的指定位置。center用浮点数存储位置这是刻意设计rect.centerx是整数如果直接累加 1.5四舍五入会丢失精度导致飞船速度不均匀。先存浮点数最后再赋给 rect是 pygame 移动平滑的经典处理方式。船速 1.5 配合这个技巧移动体验比整数累加快不少。外星人类代码高度相似只多了属性每个外星人有自己的rect并且要判断是否到达屏幕边缘class Alien(): def __init__(self, ai_settings, screen): 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): self.x (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction) self.rect.x self.x def check_edges(self): screen_rect self.screen.get_rect() if self.rect.right screen_rect.right: return True elif self.rect.left 0: return True return False3.3 精灵类与 collide为什么用 sprite.Group 而不是列表很多新手源码里用list存子弹但原书推荐使用 pygame 的Group原因是性能。看代码from pygame.sprite import Sprite class Bullet(Sprite): 一个对飞船发射的子弹进行管理的类 def __init__(self, ai_settings, screen, ship): super(Bullet, self).__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 # 存储用小数表示的子弹位置 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.sprite.Group内部有优化的迭代和绘制方法bullets Group() bullets.add(bullet) bullets.update() # 自动调用每个成员的update bullets.draw(screen) # 自动绘制每个成员注意子弹用的不是图像而是pygame.draw.rect()直接绘制矩形。键盘按下空格时生成新子弹对象加入Group每帧调用update让所有子弹同步上移再用groupcollide做碰撞检测collisions pygame.sprite.groupcollide(bullets, aliens, True, True)这一行同时完成了两组检测子弹是否撞到外星人。撞到的子弹和外星人同时从 Group 中移除。参数里的True, True表示两个都要移除。有些游戏要子弹穿透就把第一个参数改 False。3.4 事件循环与状态刷新游戏的主循环骨架主循环是全部逻辑的现场while True: gf.check_events(ship, bullets) # 处理输入事件 ship.update() # 根据输入更新飞船 gf.update_bullets(aliens, bullets) # 更新子弹并检测碰撞 gf.update_aliens(ai_settings, ship, aliens) gf.update_screen(ai_settings, screen, ship, aliens, bullets)如果去掉注释这个循环只有五行但它包含了事件处理、精灵更新、碰撞检测、绘制四个层次。check_events要绑定键盘的长按状态而不是按下一次就响应一次——这是移动飞船的关键def check_events(ship, bullets): for event in pygame.event.get(): if event.type pygame.QUIT: sys.exit() elif event.type pygame.KEYDOWN: 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: # 创建一颗子弹并加入编组 if len(bullets) ai_settings.bullets_allowed: new_bullet Bullet(ai_settings, screen, ship) bullets.add(new_bullet) elif event.type pygame.KEYUP: if event.key pygame.K_RIGHT: ship.moving_right False elif event.key pygame.K_LEFT: ship.moving_left FalseKEYDOWN和KEYUP配对使用moving_right是布尔标志ship.update()根据标志位持续修改位置这样就能按住右键连续移动而不是一下一下按。空格键事件直接创建子弹对象加入 Group是对即时响应需求的直接满足。4. 游戏状态管理得分、关卡与外星人波次重置4.1 追踪游戏状态Active 标志与 GameStats游戏需要一个全局状态——当前这局是否还在进行。搜索源码时最常见的变量名是game_active通常在GameStats类里class GameStats(): 跟踪游戏的统计信息 def __init__(self, ai_settings): self.ai_settings ai_settings self.reset_stats() # 游戏刚启动时处于活动状态 self.game_active True def reset_stats(self): 初始化在游戏运行期间可能变化的统计信息 self.ships_left self.ai_settings.ship_limit self.score 0reset_stats处理的是“一局结束时玩家选择重新开始”的场景生命值恢复到 3得分清零。而game_active保持为 True 或改为 False是几局之间的状态开关。在游戏主循环里判断它if game_active: ship.update() gf.update_bullets(aliens, bullets) gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)当外星人碰到飞船或者触底时ship_hit()会递减ships_leftdef ship_hit(ai_settings, stats, screen, ship, aliens, bullets): 响应飞船被外星人撞到 if stats.ships_left 0: stats.ships_left - 1 # 清空外星人列表和子弹列表 aliens.empty() bullets.empty() # 创建一群新的外星人并将飞船放到屏幕底部中央 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship() # 暂停 sleep(0.5) else: stats.game_active False这个函数有个细节它用aliens.empty()清空所有外星人对象和子弹重新生成新一群外星人。这是避免“上一局的残留 obj 影响下一局碰撞”的标准做法。sleep(0.5)给玩家半秒钟看清屏幕变化然后再进入下一轮。4.2 创建外星人编队坐标计算与间距策略继续阅读源码中创建外星人群的逻辑。它通常是一个递归或嵌套循环函数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) 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 get_number_rows(ai_settings, ship_height, alien_height): available_space_y (ai_settings.screen_height - (3 * alien_height) - ship_height) number_rows int(available_space_y / (2 * alien_height)) return number_rows 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)这段代码的精髓在于间距计算每个外星人占据 2 个自身宽度1 个宽度加 1 个空白屏幕上水平方向留出 2 个宽度作为左右边距。int()向下取整确保不超出边界。你可以调整系数 2 为 1.5 或 3获得更拥挤或更稀疏的队列——这是一个极易修改的画面参数。4.3 外星人移动与边界反弹前面看到了check_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注意break的用法只要检测到一个外星人到达边缘立刻停止遍历整群转向下移。这是效率考虑——不需要检查完所有成员再行动。实战中很多新手写的是“全部检查完再统一移动”这样会多出一次遍历而且可能在下一次赋值前状态已被改变。fleet_direction * -1直接切换行进方向前面提到过这个技巧。外星人群整体下移fleet_drop_speed后反向移动造成经典的“蛇形下降”效果。4.4 关卡递增与节奏加速update_bullets除了碰撞检测还要检查外星人是否被全部消灭。一旦 Group 为空就清空子弹、创建新编队并加速游戏def check_aliens_empty(ai_settings, aliens, bullets): if len(aliens) 0: bullets.empty() ai_settings.increase_speed() create_fleet(ai_settings, screen, ship, aliens) def increase_speed(self): 提高速度设置与分数设置 self.ship_speed_factor * self.speedup_scale self.bullet_speed_factor * self.speedup_scale self.alien_speed_factor * self.speedup_scaleincrease_speed把三种速度同时乘以一个基数通常在 Settings 中初始化为 1.1 或 1.2。这意味着每一关都比上一关快 10% 到 20%这是游戏难度曲线最简单有效的实现。如果想要更陡峭的难度把speedup_scale调到 1.5 即可不用改任何逻辑代码。5. 排错与调优把下载的源码改得更顺手5.1 图像资源与路径问题下载源码最容易碰到的坑图片跑丢。报错通常是pygame.error: Couldnt open images/ship.bmp。原因是源码的 images 目录没有跟随下载或者路径大小写不一致。解决方法很简单自己生成一张位图即可。用系统自带画图工具创建一个 50x50 像素的纯色 BMP命名 ship.bmp 和 alien.bmp 放入 images 目录。或者修改代码用pygame.draw.rect()直接绘制矩形代表飞船——把Ship.__init__中的self.image pygame.image.load(...)替换为self.image pygame.Surface((50, 30)) self.image.fill((0, 255, 0))这样代码与资源文件解耦任何平台都能直接跑通。5.2 游戏手感不好优先调这三个参数很多下载版源码默认参数未必符合你的手感。我一般先调整参数默认值调整方向效果ship_speed_factor1.5调大至 2.0 或 2.5飞船响应更快操作跟手bullets_allowed3调大至 5容错率提高不容易因发射间隔卡壳fleet_drop_speed10调小至 5 或 8外星人下降更慢给玩家更多反应时间如果想要“连续发射”而不是“每次点击发射一发”可以把空格键的事件从KEYDOWN改为检查KEYDOWN 且 key K_SPACE时设置ship.shooting True并在update中持续创建子弹直到松开。5.3 帧率对速度的影响初始化 Clock 固定 60 FPS源码里经常省略这步导致游戏速度随机器性能而变。在run_game.py初始化部分加上clock pygame.time.Clock() while True: clock.tick(60) # ... 其余代码Clock.tick(60)最多每帧运行 1/60 秒多余时间用于等待保证在 144Hz 显示器和 60Hz 显示器上运行速度一致。这是 pygame 项目中必须养成的习惯否则换台电脑游戏画面就快一倍。5.4 调试模式用print验证 GameStats 状态变化最后一个实用技巧在ship_hit和check_aliens_empty函数中加入临时的print(stats.ships_left)和print(len(aliens))。运行游戏时观察终端输出可以直观确认状态更新是否按预期触发。如果发现game_active在第一次被击中后变成 False但视觉上仍能移动飞船那说明主循环中if game_active:的判断放在了哪个位置有问题——多数情况下是事件处理没有缩进到判断块里。把调试用的print放在游戏逻辑里比反复猜测更高效。这些输出在正式发布前删掉即可。本文还有配套的精品资源点击获取
返回列表