添加Play按钮
首先在Setting
中添加如下代码
1 2 3 4 5 6 7
| self.button_color = (0, 255, 0) self.button_width = 200 self.button_height = 50 self.button_text_color = (255, 255, 255) self.button_font = 48 self.play_button_text = "Play"
|
将按钮的相关属性加入到设置相关的类中,然后创建button.py文件,添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import pygame.font
class Button: def __init__(self, game, msg): self.game = game self.font = pygame.font.SysFont(None, self.game.settings.button_font)
self.rect = pygame.Rect(0, 0, self.game.settings.button_width, self.game.settings.button_height) self.rect.center = self.game.screen.get_rect().center
self._pre_msg(msg)
def _pre_msg(self, msg): """将msg渲染为图像""" self.msg_image = self.font.render(msg, True, self.game.settings.button_text_color, self.game.settings.button_color) self.msg_image_rect = self.msg_image.get_rect() self.msg_image_rect.center = self.rect.center
def draw(self): """绘制按钮""" self.game.screen.fill(self.game.settings.button_color, self.rect) self.game.screen.blit(self.msg_image, self.msg_image_rect)
|
__init__()
函数用于按钮的初始化,如按钮的大小、字体、位置等,_pre_msg()
函数用于将字符串消息转换为图像并填充到矩形中,draw()
函数用于绘制按钮
接下来我们在主程序中将game_active
设为False
,从而为了显示按钮,然后添加按钮属性self.play_button = Button(self, self.settings.play_button_text)
,接着在更新屏幕的地方判断如果游戏处于非活跃状态就显示按钮
1 2
| if not self.game_active: self.play_button.draw()
|
现在运行游戏

开始游戏
此时点击按钮并没有做出反应,因为我们还没有添加鼠标点击响应的事件,首先在_check_events
中添加如下代码
1 2 3
| elif event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() self._check_play_button(mouse_pos)
|
上述代码用于检测是否有鼠标按下,如果有则获取鼠标点击的位置,然后将变量传递给_check_play_button
,现在我们添加该函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def _check_play_button(self, mouse_pos): """响应Play按钮的点击事件""" if self.play_button.rect.collidepoint(mouse_pos): self.status.reset_status() self.game_active = True
self.bullets.empty() self.aliens.empty()
self._create_fleet()
self.ship.center_ship()
|
一旦有点击事件的发生,我们就判断点击位置是否位于按钮中,如果是,则重置游戏状态并清空所有子弹和外星人,创建新的外星人和飞船,从而表明我们开启了新游戏,运行如下

游戏优化
当前游戏中还存在Bug,如果我们点击按钮所在区域,即便是按钮已经消失但仍会做出响应并重置游戏,因为我们的鼠标点击事件还会判断其点击位置与按钮位置重合,因此我们需要对判断做出优化
1
| if self.play_button.rect.collidepoint(mouse_pos) and not self.game_active:
|
将_check_play_button
函数中的if
判断语句改为上述代码,从而防止其在游戏运行时仍会执行重置代码
另外,玩游戏时我们应该将光标隐藏,在游戏结束时显示光标
在_check_play_button
的末尾添加pygame.mouse.set_visible(False)
,游戏开始将光标隐藏,在_ship_hit
的else
块的末尾添加pygame.mouse.set_visible(True)
,游戏结束时将光标显示
提升趣味性
在Settings
中添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| self.speed_scale = 1.1 self.init_settings()
def init_settings(self): """初始化游戏的动态设置""" self.ship_speed = 30 self.bullet_speed = 10 self.alien_speed = 10 self.alien_drop_speed = 10 self.alien_direction = 1
def increase_speed(self): """提高游戏速度设置""" self.ship_speed *= self.speed_scale self.bullet_speed *= self.speed_scale self.alien_speed *= self.speed_scale self.alien_drop_speed *= self.speed_scale
|
speed_scale
用于控制游戏增长的速度,init_settings
用于初始化设置,increase_speed
用于将相关参数增长从而提高游戏难度
在_check_bullet_alien_collisions
中,如果外星人全部被击落,我们就调用self.settings.increase_speed()
增加游戏速度。当然每次开启新游戏时,应当将所有设置还原,在_check_play_button
的if
语块的第一行添加self.settings.init_settings()
,这将使得新游戏的属性为初始化值
记录分数
在GameStatus
中添加属性self.score = 0
用于重置分数,在Settings
中添加分数相关设置
1 2 3
| self.score_text_color = (30, 30, 30) self.score_font = 48
|
然后创建gamescore.py文件添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import pygame.font
class GameScore: def __init__(self, game): self.game = game self.font = pygame.font.SysFont(None, self.game.settings.score_font) self.prep_score()
def prep_score(self): """将得分渲染为图像""" score_str = str(self.game.status.score) self.score_image = self.font.render(score_str, True, self.game.settings.score_text_color, self.game.settings.bg_color)
self.score_rect = self.score_image.get_rect() self.score_rect.right = self.game.screen.get_rect().right - 20 self.score_rect.top = 20
def show_score(self): """在屏幕上显示得分""" self.game.screen.blit(self.score_image, self.score_rect)
|
在主程序中添加成员self.score = GameScore(self)
,随后在更新屏幕函数中显示分数面板self.score.show_score()
,此时运行游戏就能显示分数面板了
更新得分
在Settings
的init_settings
中添加self.alien_points = 50
,用于表示每个外星人击落的初始得分,然后在主程序的_check_bullet_alien_collisions
中添加
1 2 3 4
| if collisions: for aliens in collisions.values(): self.status.score += self.settings.alien_points * len(aliens) self.score.prep_score()
|
表示每次击中一个外星人则对其进行加分,同时击落多个外星人也能同时记分,当然每次开始游戏我们也要重置分数,在_check_play_button
中添加self.score.prep_score()
,用新的分数来初始化记分面板
对于不同速度的外星人,我们也应该增加其击落分数,在Settings
中添加self.score_scale = 1.5
表示外星人的分数增长速度,然后在increase_speed
中添加self.alien_points = int(self.alien_points * self.score_scale)
,更新新的外星人分数,最后再对分数进行取舍,在GameScore
的prep_score
中修改代码如下
1 2 3 4 5 6 7 8 9
| def prep_score(self): """将得分渲染为图像""" rounded_score = round(self.game.status.score, -1) score_str = f"{rounded_score:,}" self.score_image = self.font.render(score_str, True, self.game.settings.score_text_color, self.game.settings.bg_color)
self.score_rect = self.score_image.get_rect() self.score_rect.right = self.game.screen.get_rect().right - 20 self.score_rect.top = 20
|
表示我们通过round
函数将分数取为10的整数倍,这样每个外星人的分数都是10的倍数了
获取最高分
在GameStatus
添加新属性self.max_score = 0
用来记录最高分,随后在GameScore
中添加新函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def prep_high_score(self): """将最高得分渲染为图像""" high_score = round(self.game.status.max_score, -1) high_score_str = f"{high_score:,}" self.high_score_image = self.font.render(high_score_str, True, self.game.settings.score_text_color, self.game.settings.bg_color)
self.high_score_rect = self.high_score_image.get_rect() self.high_score_rect.centerx = self.game.screen.get_rect().centerx self.high_score_rect.top = self.score_rect.top def check_high_score(self): """检查是否达到了新的最高得分""" if self.game.status.score > self.game.status.max_score: self.game.status.max_score = self.game.status.score self.prep_high_score()
|
这里将最高分渲染为图像并放置在屏幕正中央的上方,在__init__
中调用prep_high_score
,然后在show_score
中将high_score_image
显示出来,并在主程序中每当碰撞发生时就调用check_high_score
判断当前分数是否超过了最高分,运行如下

显示等级和飞船数
在GameStatus
的reset_status
中添加self.level = 1
,在GameScore
的__init__
添加函数self.prep_level()
,代码如下
1 2 3 4 5 6 7 8
| def prep_level(self): """将当前关卡渲染为图像""" level_str = str(self.game.status.level) self.level_image = self.font.render(level_str, True, self.game.settings.score_text_color, self.game.settings.bg_color)
self.level_rect = self.level_image.get_rect() self.level_rect.right = self.score_rect.right self.level_rect.top = self.score_rect.bottom + 10
|
将等级渲染为图片并显示在得分的正下方,然后在show_score
中添加self.game.screen.blit(self.level_image, self.level_rect)
来显示图像,在主程序中清除所有外星人之后提升等级,代码如下
1 2 3 4 5 6 7 8 9
| if not self.aliens: self._create_fleet() self.bullets.empty() self.settings.increase_speed()
self.status.level += 1 self.score.prep_level()
|
最后在_check_play_button
中重置等级,调用self.score.prep_level()
即可,运行如下

接下来我们要显示剩余飞船数,这里我们对飞船也要使用Sprite
,更改Ship
类如下
1 2 3
| class Ship(Sprite): def __init__(self, game): super().__init__()
|
将Ship
继承自Sprite
,在GameScore
中添加函数prep_ships
如下
1 2 3 4 5 6 7 8
| def prep_ships(self): """显示剩余飞船数量""" self.ships = Group() for ship_number in range(self.game.status.ships_left): ship = Ship(self.game) ship.rect.x = 10 + ship_number * ship.rect.width ship.rect.y = 10 self.ships.add(ship)
|
这个函数用于根据剩余的飞船数量来创建飞船对象并调整位置,然后在初始化的函数中添加self.prep_ships()
。下一步我们就要绘制飞船了,在show_score
中添加self.ships.draw(self.game.screen)
即可,然后在主程序的游戏开始和飞船被撞击的地方调用self.score.prep_ships()
就能够正常加载和显示剩余飞船了,如下

游戏Bug更改
到上述部分我们就完成了书中的所有内容,但经过我的测试,这款游戏存在一定的Bug,看图

是不是很奇怪?游戏还没有开始怎么就出现了一条横线?其实这是因为我们通过循环来侦听不同事件的发生,如果在游戏开始之前点击空格按钮,那么就会执行发射子弹的函数。但此时游戏状态并没有被激活,因此这条横线会停在这里,而且由于我们的子弹比较大,我们的飞船图像并不能完全遮盖子弹,所以就出现了这种现象,但从逻辑上来讲,如果我们不停按空格,程序应该每次都会执行空格所对应的响应,所以这里我们对其进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13
| def _check_events(self): """响应按键和鼠标事件""" for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == pygame.KEYDOWN and self.game_active: self._check_keydown_events(event) elif event.type == pygame.KEYUP and self.game_active: self._check_keyup_events(event) elif event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() self._check_play_button(mouse_pos)
|
上述代码主要增加了当键盘响应时游戏是否处于激活状态,如果游戏未激活则不响应任何按键,此时我们无论按键盘上的哪里,都不会再次出现上述的情况了,至此,该项目就完全结束了。源码访问点击这里