news 2026/8/29 19:35:08

Python游戏开发入门:用Pygame实现《外星人入侵》项目

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python游戏开发入门:用Pygame实现《外星人入侵》项目

1. 从零到一:为什么选择《外星人入侵》作为你的第一个Python游戏项目

如果你刚开始学习Python,或者已经掌握了基础语法但还没做过一个完整的项目,那么《外星人入侵》绝对是一个绝佳的起点。这个项目听起来很酷,像是要搞个大制作,但实际上,它的核心逻辑非常清晰,用到的技术点也恰好是初学者从“写脚本”到“做项目”必须跨越的那道坎。很多人学Python,变量、循环、函数都懂了,但一说到“面向对象”、“事件驱动”、“游戏循环”这些词就发怵,感觉离自己很远。《外星人入侵》这个项目,就是把这些抽象概念,塞进一个你亲手能操控飞船、射击外星人的有趣盒子里,让你在“玩”的过程中,自然而然地掌握它们。

这个项目的魅力在于“麻雀虽小,五脏俱全”。它不需要复杂的3D引擎,用pygame这个经典的2D游戏库就足够了。你需要处理玩家飞船的移动和射击,需要管理一群有规律移动的外星人,需要检测子弹和外星人的碰撞,还需要计分、记录等级和生命。整个过程,你会频繁地和“类”打交道——把飞船、子弹、外星人都设计成独立的类,这是面向对象编程最直观的实践。你会用到“函数”来封装重复的逻辑,比如检查边缘、更新编组。最重要的是,你会深刻理解“编组”的概念,pygame.sprite.Group这个工具能让你优雅地管理屏幕上几十个甚至上百个活动对象,没有它,代码会乱成一团。至于“图像”,那就是给这些逻辑骨架穿上皮肤,让一切可视化。

所以,别被“游戏”二字吓到。我们做的不是一个商业级的游戏,而是一个教学意义大于娱乐意义的编程练习。目标是让你通过构建它,打通Python项目开发的任督二脉。接下来,我会带你一步步拆解,从环境搭建到最终发射子弹击落外星飞碟,把每个环节的原理和“为什么这么做”讲透。

2. 项目基石:搭建开发环境与理解Pygame的核心循环

工欲善其事,必先利其器。我们首先得把舞台搭好。

2.1 创建专属的虚拟环境与安装Pygame

我强烈建议你为这个项目创建一个独立的虚拟环境。这能保证项目依赖的纯净,避免和你电脑上其他Python项目的库版本冲突。打开你的终端(Windows用CMD或PowerShell,macOS/Linux用Terminal),跟着做:

# 1. 为项目创建一个新目录,并进入它 mkdir alien_invasion cd alien_invasion # 2. 创建虚拟环境(这里使用Python内置的venv模块) python -m venv venv # 3. 激活虚拟环境 # 在Windows上: venv\Scripts\activate # 在macOS/Linux上: source venv/bin/activate # 激活后,你的命令行提示符前通常会显示 (venv),表示已进入该环境。 # 4. 安装pygame。使用pip安装,指定一个较新的稳定版本。 pip install pygame==2.5.2

为什么是Pygame 2.5.2?这是一个经过长期测试、文档丰富且稳定的版本。Pygame社区活跃,新版本可能带来API微小变动,对于学习项目,选择一个确定的版本能避免不必要的兼容性问题。

安装成功后,可以写一个超简单的测试脚本test_pygame.py来验证:

import pygame import sys pygame.init() print(“Pygame 初始化成功!版本:”, pygame.version.ver) pygame.quit() sys.exit()

运行它,如果没报错并打印出版本号,恭喜你,环境搞定。

2.2 解剖Pygame游戏的心脏:事件循环与主循环

这是理解任何Pygame项目的关键。一个游戏不是一次性执行完就结束的,它必须持续运行,每秒几十次地做四件事:处理用户输入、更新游戏状态、重新绘制屏幕、控制帧率。这个持续运转的机制就是游戏主循环

我们来创建一个最基础的窗口,并实现这个循环:

import sys import pygame class AlienInvasion: """管理游戏资源和行为的类""" def __init__(self): """初始化游戏,创建游戏资源""" pygame.init() # 设置屏幕尺寸。我们用一个固定的窗口,而不是全屏,便于调试。 self.screen = pygame.display.set_mode((1200, 800)) pygame.display.set_caption(“外星人入侵”) # 设置背景色 (R, G, B)。深灰色,比纯黑柔和。 self.bg_color = (230, 230, 230) def run_game(self): """开始游戏主循环""" while True: # 1. 监视键盘和鼠标事件 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() # 2. 更新游戏状态(目前还没有需要更新的对象) # 3. 用背景色填充屏幕,擦除旧帧 self.screen.fill(self.bg_color) # 4. 让最近绘制的屏幕可见(双缓冲切换) pygame.display.flip() if __name__ == ‘__main__’: # 创建游戏实例并运行 ai_game = AlienInvasion() ai_game.run_game()

把这段代码保存为alien_invasion.py并运行。你会看到一个灰色的窗口,点击关闭按钮可以退出。我们来拆解这个循环:

  1. pygame.event.get(): 这是消息泵。它获取所有在上一帧到这一帧之间发生的事件,比如按键、鼠标移动、点击关闭按钮。我们遍历这些事件,并对特定类型做出响应。pygame.QUIT事件就是用户点击了窗口关闭按钮。
  2. 更新游戏状态:目前是空的。后续我们会在这里调用飞船、外星人、子弹的update()方法,让它们根据输入和时间改变位置。
  3. self.screen.fill(): 用背景色清空整个屏幕。因为游戏是逐帧绘制的,如果不擦除上一帧,你会看到物体移动的拖影。
  4. pygame.display.flip(): 这是关键。Pygame使用双缓冲技术。我们在一个“后台”表面(self.screen)上绘制所有图像,当一帧的所有绘制命令完成后,调用flip()将后台表面切换到前台显示。这样能避免屏幕闪烁,让动画更平滑。

这个循环会以你电脑能跑的最快速度运行,这会导致CPU占用率极高,且游戏速度在不同性能电脑上不一致。因此,我们通常需要时钟(Clock)来控制帧率。

__init__方法里添加:self.clock = pygame.time.Clock()。 在run_game循环的末尾添加:self.clock.tick(60)

clock.tick(60)告诉Pygame:“确保这个循环每秒最多运行60次。” 这就将游戏帧率限制在了60FPS,既保证了流畅性,又控制了资源消耗。这是专业游戏开发的标准做法。

3. 构建游戏世界的第一公民:玩家飞船类

游戏有了舞台和循环,现在需要主角——一艘可以由玩家控制的飞船。我们将用面向对象的思想来创建它。

3.1 设计Ship类:属性与初始化

在项目根目录下创建一个新文件ship.py。我们将飞船定义为一个独立的类,这样它的数据(位置、图像)和行为(移动、绘制)都封装在一起。

import pygame class Ship: """管理飞船的类""" def __init__(self, ai_game): """初始化飞船并设置其初始位置""" # 获取游戏屏幕的surface对象和矩形区域,方便后续定位 self.screen = ai_game.screen self.screen_rect = ai_game.screen.get_rect() self.settings = ai_game.settings # 稍后我们会创建游戏设置类 # 加载飞船图像并获取其外接矩形 self.image = pygame.image.load(‘images/ship.bmp’) self.rect = self.image.get_rect() # 将每艘新飞船放在屏幕底部中央 self.rect.midbottom = self.screen_rect.midbottom # 在飞船的属性中存储一个浮点数表示的精确水平位置 self.x = float(self.rect.x) # 移动标志:用布尔值记录按键状态,比在事件循环中直接移动更平滑 self.moving_right = False self.moving_left = False def update(self): """根据移动标志调整飞船的位置""" # 更新飞船的x值,而不是直接修改rect.x if self.moving_right and self.rect.right < self.screen_rect.right: self.x += self.settings.ship_speed if self.moving_left and self.rect.left > 0: self.x -= self.settings.ship_speed # 根据self.x更新rect对象 self.rect.x = int(self.x) def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect)

关键点解析:

  • 为什么传ai_game参数?飞船需要知道它在哪个屏幕上绘制,需要访问游戏设置(如速度)。通过传入主游戏对象,我们建立了两者间的关联,这是一种常见的依赖注入方式,比使用全局变量更清晰。
  • self.rectself.xrect是Pygame的矩形对象,属性x,y,centerx,bottom等都是整数。直接修改rect.x进行移动没问题,但如果你想做更精细的速度控制(比如速度是1.5像素/帧),整数会丢失精度。所以我们用浮点数self.x来存储精确位置,在最终绘制前再转换为整数赋给rect.x
  • 移动标志(Flag):这是实现连续移动的经典模式。在事件循环中,我们只设置标志位(按下键设为True,松开键设为False)。在update()方法中,根据标志位来真正改变位置。这样做的好处是移动响应平滑,且可以轻松处理多个按键同时按下的情况。
  • 边界检查self.rect.right < self.screen_rect.right确保飞船不会移出屏幕右边界。左边界同理。
  • blitme()blit是位块传输的意思,即将一个图像表面绘制到另一个表面上。这个方法封装了绘制操作。

你需要准备一张飞船图片,命名为ship.bmp,放在项目根目录下的images文件夹中。图片背景最好是透明的(或者与游戏背景色一致),格式BMP是Pygame最直接支持的,当然你也可以用PNG,但加载方式略有不同(pygame.image.load也支持PNG)。

3.2 在主游戏中集成飞船并响应键盘事件

现在回到alien_invasion.py,我们需要做几件事:创建一个设置类来管理配置、实例化飞船、在循环中更新和绘制飞船,并处理键盘事件。

首先,创建settings.py

class Settings: """存储游戏《外星人入侵》中所有设置的类""" def __init__(self): """初始化游戏的静态设置""" # 屏幕设置 self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) # 飞船设置 self.ship_speed = 5.0

然后,修改alien_invasion.py

import sys import pygame from settings import Settings from ship import Ship class AlienInvasion: def __init__(self): pygame.init() self.settings = Settings() # 创建设置实例 self.screen = pygame.display.set_mode( (self.settings.screen_width, self.settings.screen_height)) pygame.display.set_caption(“外星人入侵”) self.clock = pygame.time.Clock() # 创建一艘飞船。注意将self(当前游戏实例)传递进去 self.ship = Ship(self) def run_game(self): while True: self._check_events() # 处理事件 self.ship.update() # 更新飞船位置 self._update_screen() # 绘制新屏幕 self.clock.tick(60) # 控制帧率 def _check_events(self): """响应按键和鼠标事件""" for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == pygame.KEYDOWN: # 按键按下 self._check_keydown_events(event) elif event.type == pygame.KEYUP: # 按键松开 self._check_keyup_events(event) def _check_keydown_events(self, event): """响应按下""" if event.key == pygame.K_RIGHT: self.ship.moving_right = True elif event.key == pygame.K_LEFT: self.ship.moving_left = True elif event.key == pygame.K_q: # 按Q键也可以退出游戏,方便调试 pygame.quit() sys.exit() def _check_keyup_events(self, event): """响应松开""" if event.key == pygame.K_RIGHT: self.ship.moving_right = False elif event.key == pygame.K_LEFT: self.ship.moving_left = False def _update_screen(self): """更新屏幕上的图像,并切换到新屏幕""" self.screen.fill(self.settings.bg_color) self.ship.blitme() # 绘制飞船 pygame.display.flip() if __name__ == ‘__main__’: ai_game = AlienInvasion() ai_game.run_game()

现在运行游戏,你应该可以用左右方向键控制飞船在屏幕底部水平移动了!这里我们把事件处理抽成了独立的方法_check_events,并进一步细分为按下和松开的事件处理,让代码结构更清晰。开头的下划线_是一种约定,暗示这些是类内部的“私有”方法,虽然Python不强制,但这是一个好的编码习惯。

4. 发射子弹:理解精灵编组与对象管理

飞船能动了,接下来要赋予它攻击能力。子弹是游戏中典型的“批量产生、批量销毁、批量更新”的对象,这正是Pygame中Sprite(精灵)和Group(编组)大显身手的地方。

4.1 创建Bullet类并继承Sprite

bullet.py中,我们让Bullet类继承pygame.sprite.Sprite。继承后,我们的子弹对象就能享受到编组管理的便利。

import pygame from pygame.sprite import Sprite class Bullet(Sprite): """管理飞船发射的子弹的类""" def __init__(self, ai_game): """在飞船当前位置创建一个子弹对象""" super().__init__() # 初始化父类Sprite self.screen = ai_game.screen self.settings = ai_game.settings self.color = self.settings.bullet_color # 在(0,0)处创建一个表示子弹的矩形,再设置正确的位置。 # 我们不用图像,而是用绘制矩形来代表子弹,更简单。 self.rect = pygame.Rect(0, 0, self.settings.bullet_width, self.settings.bullet_height) # 将子弹的初始位置设置为飞船的顶部中央 self.rect.midtop = ai_game.ship.rect.midtop # 用浮点数存储子弹的精确垂直位置 self.y = float(self.rect.y) def update(self): """向上移动子弹""" # 更新表示子弹位置的浮点数 self.y -= self.settings.bullet_speed # 更新表示子弹位置的rect对象 self.rect.y = int(self.y) def draw_bullet(self): """在屏幕上绘制子弹""" pygame.draw.rect(self.screen, self.color, self.rect)

同时,在settings.py__init__方法中添加子弹设置:

# 子弹设置 self.bullet_speed = 10.0 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = (255, 60, 60) # 亮红色

关键点解析:

  • 继承Sprite:这允许我们将子弹对象添加到pygame.sprite.Group()中。编组可以自动调用组内所有精灵的update()方法,并提供了强大的碰撞检测功能。
  • 使用Rect而非图像:对于子弹这种简单的几何形状,直接用pygame.Rect创建并绘制,比加载图片文件更高效、更简单。
  • self.y浮点数:和飞船一样,为了精确控制速度。
  • update()方法:编组会为每个精灵调用此方法。这里让子弹的y坐标不断减小,实现向上飞行的效果。

4.2 在游戏中管理子弹编组

现在需要在主游戏AlienInvasion类中创建子弹编组,并修改事件处理和主循环来支持发射。

首先,在alien_invasion.py顶部导入Bullet类:from bullet import Bullet

然后,在AlienInvasion.__init__中初始化一个用于存放子弹的编组:

# 创建一个用于存储子弹的编组 self.bullets = pygame.sprite.Group()

接下来,修改事件处理,让空格键触发发射。在_check_keydown_events方法中添加:

elif event.key == pygame.K_SPACE: self._fire_bullet()

并实现_fire_bullet方法:

def _fire_bullet(self): """创建一颗新子弹,并将其加入子弹编组""" # 限制屏幕上子弹数量,避免过多导致游戏变卡 if len(self.bullets) < self.settings.bullets_allowed: new_bullet = Bullet(self) self.bullets.add(new_bullet)

这里我们引入了一个新的设置bullets_allowed,用于限制屏幕上同时存在的子弹数量,防止玩家疯狂按空格键导致游戏性能下降。记得在settings.py中添加:self.bullets_allowed = 5

最后,也是最关键的一步,修改主循环中的更新和绘制逻辑。在run_game方法中,我们已经在更新飞船self.ship.update(),现在需要加上更新子弹编组。同时,在_update_screen方法中绘制子弹。

修改run_game中的更新部分:

self._check_events() self.ship.update() self.bullets.update() # 更新所有子弹的位置 self._update_screen()

子弹的update()方法会被编组自动调用。但编组不会自动删除屏幕外的子弹,我们需要在更新后清理。在self.bullets.update()后面添加:

# 删除已消失的子弹 for bullet in self.bullets.copy(): if bullet.rect.bottom <= 0: self.bullets.remove(bullet)

这里为什么用self.bullets.copy()来遍历?因为我们在遍历列表(编组本质上是一个列表)的同时要修改它(删除元素),直接遍历原列表会导致错误。复制一份副本进行遍历是安全的做法。

修改_update_screen方法,在绘制飞船后绘制所有子弹:

def _update_screen(self): self.screen.fill(self.settings.bg_color) self.ship.blitme() for bullet in self.bullets.sprites(): bullet.draw_bullet() pygame.display.flip()

现在运行游戏,用空格键发射子弹吧!你会看到一排排红色矩形向上飞去,飞出屏幕后自动消失。你已经成功运用了“编组”来管理大量相似的游戏对象,这是游戏编程中的一个核心模式。

5. 创造敌人:生成外星人舰队并实现编队移动

敌人是游戏的另一半灵魂。外星人舰队比子弹更复杂,它们需要整齐地排成网格,整体移动,到达屏幕边缘后下移并反向。

5.1 设计Alien类

创建alien.py

import pygame from pygame.sprite import Sprite class Alien(Sprite): """表示单个外星人的类""" def __init__(self, ai_game): """初始化外星人并设置其起始位置""" super().__init__() self.screen = ai_game.screen self.settings = ai_game.settings # 加载外星人图像并设置其rect属性 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.settings.alien_speed * self.settings.fleet_direction self.rect.x = int(self.x) def check_edges(self): """如果外星人位于屏幕边缘,就返回True""" screen_rect = self.screen.get_rect() return (self.rect.right >= screen_rect.right) or (self.rect.left <= 0)

关键点解析:

  • fleet_direction:这是一个新设置,值为1或-1,代表舰队当前的移动方向(右或左)。在update()中,速度乘以方向,实现了左右移动。
  • check_edges():这个方法检查当前外星人是否碰到了屏幕左边缘或右边缘。主逻辑将利用这个方法来决定整个舰队是否需要改变方向并下移。

settings.py中添加外星人相关设置:

# 外星人设置 self.alien_speed = 2.0 self.fleet_drop_speed = 10 # 撞边后向下移动的速度 # fleet_direction为1表示向右移,为-1表示向左移 self.fleet_direction = 1

5.2 创建外星人舰队

我们不直接在AlienInvasion中创建外星人,而是创建一个专门的方法_create_fleet。在AlienInvasion.__init__中初始化一个外星人编组:self.aliens = pygame.sprite.Group(),并在初始化后调用self._create_fleet()

_create_fleet方法是本项目的逻辑小难点。我们需要计算一行能放多少个外星人,以及屏幕垂直空间能容纳多少行。

def _create_fleet(self): """创建外星人群""" # 创建一个外星人,并计算一行可容纳多少个外星人 # 外星人的间距为外星人宽度 alien = Alien(self) alien_width, alien_height = alien.rect.size available_space_x = self.settings.screen_width - (2 * alien_width) number_aliens_x = available_space_x // (2 * alien_width) # 计算屏幕可容纳多少行外星人 ship_height = self.ship.rect.height available_space_y = (self.settings.screen_height - (3 * alien_height) - ship_height) number_rows = available_space_y // (2 * alien_height) # 创建外星人群 for row_number in range(number_rows): for alien_number in range(number_aliens_x): self._create_alien(alien_number, row_number) def _create_alien(self, alien_number, row_number): """创建一个外星人并将其放在当前行""" alien = Alien(self) alien_width, alien_height = alien.rect.size alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = int(alien.x) alien.rect.y = alien_height + 2 * alien_height * row_number self.aliens.add(alien)

计算逻辑拆解:

  1. 水平空间:屏幕总宽度减去左右两边的边距(各一个外星人宽度),得到可用空间。每个外星人占据的横向空间是其宽度加上一个间隔(这里间隔也设为一个外星人宽度)。所以available_space_x // (2 * alien_width)得到每行的外星人数量。
  2. 垂直空间:屏幕总高度减去顶部边距(三个外星人高度)、减去底部留给飞船的空间(一个飞船高度),得到可用垂直空间。行间距同样设为一个外星人高度。available_space_y // (2 * alien_height)得到行数。
  3. 定位:在_create_alien中,根据当前外星人在行中的序号alien_number和行号row_number,计算出其精确的x, y坐标并赋值。

这样,一个整齐的外星人网格舰队就创建好了。别忘了在_update_screen方法中绘制外星人编组:self.aliens.draw(self.screen)Group.draw()方法会自动将组内每个精灵的image绘制到其rect指定的位置,非常方便。

5.3 实现舰队移动与转向逻辑

舰队移动的逻辑是:所有外星人一起横向移动,当任何一个外星人碰到屏幕边缘,整个舰队就向下移动一段距离,然后横向移动方向取反。

我们在AlienInvasion的主循环中更新外星人:self.aliens.update()。但更新后需要检查是否需要改变舰队方向。我们添加一个方法_check_fleet_edges_change_fleet_direction

def _check_fleet_edges(self): """有外星人到达边缘时采取相应的措施""" for alien in self.aliens.sprites(): if alien.check_edges(): self._change_fleet_direction() break # 只要有一个碰到边缘,整个舰队就转向,所以找到后就跳出循环 def _change_fleet_direction(self): """将整群外星人下移,并改变它们的移动方向""" for alien in self.aliens.sprites(): alien.rect.y += self.settings.fleet_drop_speed self.settings.fleet_direction *= -1

然后,在主循环的更新部分,在self.aliens.update()之后调用self._check_fleet_edges()

现在运行游戏,你会看到一支外星人舰队在屏幕上方左右移动,碰到边缘就整体下移并反向,压迫感逐渐就来了。

6. 碰撞检测与游戏逻辑:射击、得分与结束

游戏的核心互动来了:子弹击中外星人,以及外星人撞到飞船或屏幕底部。

6.1 子弹与外星人的碰撞检测

Pygame的精灵编组为我们提供了极其简便的碰撞检测方法。我们使用pygame.sprite.groupcollide()

AlienInvasion.run_game的主循环中,更新子弹和外星人之后,添加碰撞检测:

# 检查是否有子弹击中了外星人 # 如果是,就删除相应的子弹和外星人 collisions = pygame.sprite.groupcollide( self.bullets, self.aliens, True, True)

groupcollide()接受四个参数:第一个编组,第二个编组,以及两个布尔值,分别表示碰撞后是否删除第一个编组和第二个编组中的精灵。这里我们设置True, True,意味着子弹和外星人相撞后双双消失。

这个方法返回一个字典,其中键是发生碰撞的子弹,值是与该子弹发生碰撞的外星人列表。在这个简单版本中,我们暂时不需要使用这个返回值,但知道它的存在对后续扩展(比如根据击中的外星人类型加分)很有用。

6.2 外星人与飞船的碰撞及到达屏幕底部

游戏失败有两种情况:外星人撞到飞船,或者任何外星人到达了屏幕底部。

首先,检查外星人与飞船的碰撞。Pygame有pygame.sprite.spritecollideany()方法,检查一个精灵是否与一个编组中的任何精灵发生碰撞。

AlienInvasion类中添加方法_check_ship_alien_collisions,并在主循环中调用:

def _check_ship_alien_collisions(self): """响应外星人和飞船之间的碰撞""" if pygame.sprite.spritecollideany(self.ship, self.aliens): self._ship_hit()

_ship_hit()是处理飞船被撞后的逻辑,我们稍后实现。

其次,检查是否有外星人到达屏幕底部。遍历外星人编组,检查每个外星人的rect.bottom是否大于等于屏幕的rect.bottom

def _check_aliens_bottom(self): """检查是否有外星人到达了屏幕底端""" screen_rect = self.screen.get_rect() for alien in self.aliens.sprites(): if alien.rect.bottom >= screen_rect.bottom: # 像飞船被撞到一样处理 self._ship_hit() break # 只要有一个触底,就触发事件并跳出循环

同样,在主循环中调用self._check_aliens_bottom()

6.3 实现_ship_hit()与游戏重置逻辑

_ship_hit()是游戏逻辑的核心响应函数。当飞船被撞或外星人触底时,我们需要:清空子弹和外星人、创建新的舰队、将飞船重置到中央底部、然后暂停一小会儿让玩家有反应时间,最后如果生命用尽则结束游戏。

首先,在Settings类中添加游戏动态设置和统计信息。我们修改Settings,将静态设置和动态设置分开:

def initialize_dynamic_settings(self): """初始化随游戏进行而变化的设置""" self.ship_speed = 5.0 self.bullet_speed = 10.0 self.alien_speed = 2.0 # fleet_direction为1表示向右,-1表示向左 self.fleet_direction = 1

AlienInvasion.__init__中,初始化设置后调用:self.settings.initialize_dynamic_settings()。同时,我们需要一个记录游戏状态的类,创建game_stats.py

class GameStats: """跟踪游戏统计信息""" def __init__(self, ai_game): """初始化统计信息""" self.settings = ai_game.settings self.reset_stats() # 游戏刚启动时处于非活动状态,等玩家点击才开始 self.game_active = True # 我们先设为True方便测试,后续可改为False def reset_stats(self): """初始化在游戏运行期间可能变化的统计信息""" self.ships_left = self.settings.ship_limit

settings.py中添加:self.ship_limit = 3,表示玩家有3条命。

回到AlienInvasion.__init__,初始化统计信息:self.stats = GameStats(self)

现在,可以实现_ship_hit()了:

def _ship_hit(self): """响应飞船被外星人撞到""" if self.stats.ships_left > 0: # 将ships_left减1 self.stats.ships_left -= 1 # 清空余下的外星人和子弹 self.aliens.empty() self.bullets.empty() # 创建一群新的外星人,并将飞船放到屏幕底端中央 self._create_fleet() self.ship.center_ship() # 暂停0.5秒,让玩家有喘息之机 pygame.time.delay(500) else: self.stats.game_active = False

我们需要在Ship类中添加center_ship方法:

def center_ship(self): """将飞船放在屏幕底部中央""" self.rect.midbottom = self.screen_rect.midbottom self.x = float(self.rect.x)

同时,在主循环开始更新和绘制前,我们需要检查游戏是否仍处于活动状态:

def run_game(self): while True: self._check_events() if self.stats.game_active: self.ship.update() self._update_bullets() self._update_aliens() self._update_screen() self.clock.tick(60)

我们把更新子弹和外星人的逻辑也封装成方法_update_bullets_update_aliens,让run_game更简洁。_update_bullets包含子弹更新、删除屏幕外子弹、碰撞检测。_update_aliens包含外星人更新、检查边缘、检查碰撞和触底。

现在,一个基本的游戏循环就完成了:射击外星人,被撞或让外星人触底会损失一条命,三条命用完游戏结束(game_active变为False,停止更新游戏对象)。

7. 打磨与优化:计分、难度提升与游戏体验

基础功能完成后,我们可以添加一些“糖霜”,让游戏更像一个完整的作品。

7.1 添加计分系统

GameStats类中添加得分属性,并在reset_stats中初始化:self.score = 0

当子弹击中外星人时,我们应该加分。修改_update_bullets方法中的碰撞检测部分,遍历碰撞字典来加分:

collisions = pygame.sprite.groupcollide( self.bullets, self.aliens, True, True) if collisions: for aliens in collisions.values(): self.stats.score += self.settings.alien_points * len(aliens)

这里假设每个外星人分值相同。我们可以在Settingsinitialize_dynamic_settings中设置:self.alien_points = 50

为了在屏幕上显示分数,我们需要创建一个记分牌类Scoreboard。这涉及到Pygame的字体渲染。由于篇幅所限,这里简述步骤:

  1. Scoreboard__init__中,设置字体、颜色。
  2. 创建一个方法prep_score,将整数分数转换为渲染好的图像。
  3. 创建一个方法show_score,在_update_screen中调用,将分数图像blit到屏幕上。
  4. _update_bullets中加分后,调用记分牌的prep_score更新图像。

7.2 实现难度递增

游戏不应该一成不变。当玩家清空一波外星人后,下一波应该更快、更猛。我们可以在_update_bullets中,检测如果外星人编组为空(if not self.aliens:),就调用一个方法_start_new_level

_start_new_level需要做:

  1. 清空现有的子弹。
  2. 调用_create_fleet生成新舰队。
  3. 提高游戏速度设置(如self.settings.increase_speed())。

Settings类中添加increase_speed方法:

def increase_speed(self): """提高速度设置""" self.ship_speed *= self.speedup_scale self.bullet_speed *= self.speedup_scale self.alien_speed *= self.speedup_scale

并在__init__中初始化速度缩放因子:self.speedup_scale = 1.1,即每升一级,速度提高10%。

7.3 添加游戏开始与结束界面

目前游戏启动即开始。我们可以改进为:游戏启动时显示一个“Play”按钮,游戏结束后显示“Game Over”并可以重新开始。

这需要:

  1. 创建一个Button类,用于绘制矩形和文字。
  2. GameStats中将game_active初始值设为False
  3. _update_screen中,根据game_active的状态决定是绘制游戏元素还是绘制按钮。
  4. 修改事件检测,当游戏非活动状态且用户点击了“Play”按钮区域时,重置游戏统计信息、设置和状态,将game_active设为True

这些优化步骤将大大提升项目的完整度和可玩性,也让你接触到更丰富的Pygame功能,如字体渲染、UI交互和游戏状态管理。

从搭建窗口到实现移动、射击、碰撞、计分与难度提升,这个《外星人入侵》项目几乎涵盖了一个简单2D游戏的所有核心要素。通过亲手实现它,你不仅学会了Pygame的基本用法,更重要的是理解了“游戏循环”、“事件驱动”、“面向对象设计”、“精灵编组管理”这些贯穿游戏开发始终的核心概念。下次当你看到更复杂的游戏时,你会明白,它们大多是在这个基础骨架上,添加了更精美的皮肤、更复杂的规则和更丰富的交互而已。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 19:30:35

仿网易云音乐静态页:纯CSS实现高分前端教学范本

简介&#xff1a;静态网页是Web开发的基石概念&#xff0c;其核心在于HTML语义化结构、CSS布局原理与视觉表现力的深度协同。理解盒模型、选择器优先级、Flex/Grid布局机制&#xff0c;是掌握现代前端工程实践的前提。本文以高分‘仿网易云音乐’静态项目为案例&#xff0c;解析…

作者头像 李华
网站建设 2026/8/29 19:29:44

T-DFNN:基于增量学习的入侵检测系统如何克服灾难性遗忘

1. 项目概述&#xff1a;当入侵检测遇上“活到老学到老”的神经网络 最近在复现和消化一篇挺有意思的论文&#xff0c;标题是《T-DFNN: An Incremental Learning Algorithm for Intrusion Detection Systems》。简单来说&#xff0c;它解决的是网络安全领域一个经典的老大难问题…

作者头像 李华
网站建设 2026/8/29 19:28:07

图论最短路径算法实战:从Dijkstra到Floyd,数学建模竞赛核心应用解析

1. 项目概述&#xff1a;从“找路”到“寻优”的思维跃迁“清风数模课”这个名字&#xff0c;听起来就带着一股子清爽的实操味儿。它瞄准的不是高深莫测的纯理论&#xff0c;而是数学建模竞赛中那些最实用、最高频的“硬骨头”。今天要拆解的这块骨头&#xff0c;就是图论最短路…

作者头像 李华
网站建设 2026/8/29 19:24:28

YOLOv11工业视觉实战:从数据标注到模型部署的针织品瑕疵检测全流程

简介&#xff1a;目标检测是计算机视觉的核心任务之一&#xff0c;旨在识别图像中的物体并定位其位置。其原理通常基于深度学习模型&#xff0c;通过卷积神经网络提取特征&#xff0c;并预测边界框和类别。这项技术的核心价值在于将传统依赖人眼的检测工作自动化&#xff0c;实…

作者头像 李华
网站建设 2026/8/29 19:19:31

华为MetaERP # Oracle EBS R12 AP:业务对象 (BO) 与逻辑实体 (LE)【聚合关系】深度解析## 前置概念界定(UML 标准 + EBS 落地口径,区分组合 / 聚合

Oracle EBS R12 AP&#xff1a;业务对象 (BO) 与逻辑实体 (LE)【聚合关系】深度解析前置概念界定&#xff08;UML 标准 EBS 落地口径&#xff0c;区分组合 / 聚合 / 关联&#xff09;1. 聚合关系 Aggregation&#xff08;共享聚合&#xff0c;弱包含&#xff09;语义&#xff…

作者头像 李华
网站建设 2026/8/29 19:15:25

MATLAB三维海浪仿真:从谱分析到FFT加速的流体动力学建模实践

1. 项目概述&#xff1a;从海浪到模型海浪&#xff0c;这个我们既熟悉又陌生的自然现象&#xff0c;背后蕴藏着复杂的流体动力学原理。对于海洋工程、海岸防护、船舶设计乃至影视特效领域而言&#xff0c;能够精确地模拟和预测海浪的三维形态与运动&#xff0c;是一项极具价值的…

作者头像 李华