外星人入侵_计分
- 1添加Play按钮
- 1.1创建Button类
- 1.2在屏幕上绘制按钮
- 1.3开始游戏
- 1.4 重置游戏
- 1.5 将Play按钮切换到非活动状态
- 1.6隐藏光标
- 2提高等级
- 2.1修改速度设置
- 2.2重置速度
- 3计分
- 3.1显示得分
- 3.2创建记分牌
- 3.3在外星人被消灭时更新得分
- 3.4将消灭的每个外星人的点数都计入得分
- 3.5提高点数
- 3.6将得分圆整
- 3.7最高得分
- 3.8显示等级
- 3.9显示余下的飞船数
1添加Play按钮
当前游戏在玩家alien_invasion.py运行时就开始了,下面让游戏一开始处于非活动状态,并提示玩家单击play按钮开始游戏。
game_stats.py
: 修改初始状态为非活跃状态
def __init__(self, ai_settings):self.game_active = False #游戏刚启动处于非活跃状态
1.1创建Button类
button 按钮
Pygame中没有内置创建按钮的方法,这里创建一个Button类,用于创建带标签的是实心矩形。
button.py
:
import pygame.fontclass Button:def __init__(self, screen, ai_settings, msg): #msg为需要渲染的文本形参"""初始化按钮的属性"""self.screen = screenself.screen_rect = screen.get_rect()#self.ai_settings = ai_settings# 设置按钮的尺寸和其他属性self.width, self.height = 200, 50self.button_color = (0, 255, 0) # 按钮颜色:绿self.text_color = (255, 255, 255) # 文本颜色:白self.font = pygame.font.SysFont(None, 48) #指定只用默认字体,48号字体渲染文本# 创建按钮的rect对象,并使其居中self.rect = pygame.Rect(0, 0, self.width, self.height)self.rect.center = self.screen_rect.center# 按钮标签只需要创建一次self.prep_msg(msg)
调用prep_msg()处理渲染:
button.py
:
def prep_msg(self, msg):"""将msg渲染为图像,并使其在按钮上居中"""self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)self.msg_image_rect = self.msg_image.get_rect()self.msg_image_rect.center = self.rect.center # 将其center属性设置为按钮的center属性
font.render(msg, True, self.text_color, self.button_color)
将能存储在msg中的文本转换为图像,然后存储在msg_image中,并接受一个不二实参,该实参指定开启还是关闭反锯齿功能,余下两个实参分别时文本颜色和背景颜色。
最后使用方法draw_button()
将按钮显示在屏幕上:
def draw_button(self):"""绘制一个用颜色填充的按钮,再绘制文本"""self.screen.fill(self.button_color, self.rect) # 绘制按钮的举行self.screen.blit(self.msg_image, self.msg_image_rect)
1.2在屏幕上绘制按钮
直接在alien_incasion.py
中创建Play按钮:
from button import Button# 创建Play按钮play_button = Button(screen, ai_setting, "Play") while True:--snip--#更新屏幕上的图像,并切换到新屏幕gf.update_screen(ai_setting, screen, ship, bullets, aliens, play_button, stats)
接下来在game_function.py
中修改update_screen()
以便在游戏处于非活跃状态时显示Play按钮:
def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats):--snip--# 如果游戏处于非活跃状态,绘制Play按钮if stats.game_active == False:play_button.draw_button()# 让最近绘制的屏幕可见pygame.display.flip()
1.3开始游戏
玩家单击Play按钮可以开始游戏,需要在game_function.py
中添加如下代码:
def check_events(ai_settings, screen, ship, bullets, play_button, stats):"""响应按键和鼠标事件"""for event in pygame.event.get(): # 所有键盘和鼠标事件都将促使for循环运行--snip--elif event.type == pygame.MOUSEBUTTONDOWN:mouse_x, mouse_y = pygame.mouse.get_pos()check_play_button(play_button, mouse_x, mouse_y, stats)def check_play_button(play_button, mouse_x, mouse_y, stats):"""在玩家单击Play按钮时开启新游戏"""if play_button.rect.collidepoint(mouse_x, mouse_y):stats.game_active = True
pygame.mouse.get_pos()
方法返回一个元组,其中包含玩家单击鼠标时的x坐标和y坐标。
并在alien_invasion.py
中调用check_events(),需要传递另外两个实参:play_button, stats
gf.check_events(ai_setting, screen, ship, bullets, play_button, stats)
1.4 重置游戏
为了在玩家每次单击Play按钮时都重置游戏,需要重置统计信息,删除现有的外星人和子弹,创建一群新的外星人,并让飞船居中,如下:
game_function.py
:
def check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship):"""在玩家单击Play按钮时开启新游戏"""if play_button.rect.collidepoint(mouse_x, mouse_y):#重置游戏统计信息stats.reset_stats()stats.game_active = True# 清空外星人列表和子弹列表aliens.empty()bullets.empty()# 创建一群新外星人,并让飞船居中create_fleet(ai_settings, screen, aliens, ship)ship.center_ship()
那么,调用它的函数,实参也需要改:
def check_events(ai_settings, screen, ship, bullets, play_button, stats, aliens):--snip--elif event.type == pygame.MOUSEBUTTONDOWN:mouse_x, mouse_y = pygame.mouse.get_pos()check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship)
alien_invasion.py
中调用check_events的实参也将改变
1.5 将Play按钮切换到非活动状态
当前,play按钮存在一个问题,那就是即便Play按钮不可见,玩家单击其原来的位置时,游戏依然会作出反应。
为修改这个问题,可让游戏仅在game_active为False时才开始。
def check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship):"""在玩家单击Play按钮时开启新游戏"""button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) #单击的区域是否为button的区域if button_clicked and not stats.game_active: #重要#重置游戏统计信息stats.reset_stats()stats.game_active = True# 清空外星人列表和子弹列表aliens.empty()bullets.empty()# 创建一群新外星人,并让飞船居中create_fleet(ai_settings, screen, aliens, ship)ship.center_ship()
1.6隐藏光标
为让玩家能够开始游戏,要让光标可见,但是游戏开始后,光标是会添乱。为修复这种问题,在处于活动时让光标不可见:
def check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship):"""在玩家单击Play按钮时开启新游戏"""button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) #单击的区域是否为button的区域if button_clicked and not stats.game_active:#重置游戏统计信息pygame.mouse.set_visible(False)--snip--
游戏结束后,重新显示光标。
def ship_hit(stats, aliens, bullets, ai_settings, screen, ship):"""响应被外星人撞到的飞船"""if stats.ships_left > 0:--snip--else:stats.game_active = Falsepygame.mouse.set_visible(True)
2提高等级
当前,整群外星人都消灭干净后,玩家将提高一个等级,且希望加快游戏的节奏,让游戏变得更难。
2.1修改速度设置
首先重新组织Settings类,将游戏设置为静态和动态两组。
settings.py
:
class Settings():"""存储《外星人入侵》的所有设置的类"""def __init__(self):"""初始化游戏的静态设置"""#屏幕设置self.screen_width = 1200 #屏幕宽1200self.screen_height = 600 #屏幕高600#self.bg_color = (0, 0, 255) #蓝色self.bg_color = (230, 230, 230) # 浅灰色#飞船设置self.ship_limit = 3 # 飞船的个数限制为3#子弹设置self.bullet_width = 3# self.bullet_width = 300self.bullet_hight = 15self.bullet_color = 60, 60, 60self.bullet_allowed = 3 #允许子弹的最大数量# 外星人速度设置self.fleet_drop_speed = 10 #外星人撞到屏幕边缘时下降速度self.speedup_scale = 1.1self.initialize_dynamic_settings()def initialize_dynamic_settings(self):self.ship_speed_factor = 1.5 # 设置飞船速度为1.5self.bullet_speed_factor = 3self.alien_speed_factor = 0.5 #外星人左右移动的速度# fleet_direction为1表示向右移动,为-1表示向左移动self.fleet_direction = 1
提高速度设置:
def increase_speed(self):"""提高速度设置"""self.ship_speed_factor *= self.speedup_scaleself.bullet_speed_factor *= self.speedup_scaleself.alien_speed_factor *=self.speedup_scale
在game_function.py
中,当外星人被完全击落时,加快速度:
def check_bullet_alien_collisions(bullets, aliens, ai_settings, screen, ship):# 检查是否有子弹击中外星人# 如果是这样,就删除相应的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if len(aliens) == 0: #飞船胜利# 删除现有子弹并新建一群外星人bullets.empty()ai_settings.increase_speed() #这里create_fleet(ai_settings, screen, aliens, ship)
2.2重置速度
在重新点击Paly按钮时,需要重置速度:
game_function.py
:
def check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship):"""在玩家单击Play按钮时开启新游戏"""button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) #单击的区域是否为button的区域if button_clicked and not stats.game_active:#重置游戏统计信息ai_settings.initialize_dynamic_settings() #这里pygame.mouse.set_visible(False) # 隐藏光标--snip--
3计分
实现计分系统,以实时地跟最玩家的得分,并显示最高得分,当前等级和余下的飞船数。
得分是游戏的一个统计信息,因此需要在GameStats中添加一个score属性:
game_ststs.py
:
class GameStats():def reset_stats(self):"""初始化在游戏运行期间可能变化的统计信息"""self.ships_left = self.ai_settings.ship_limitself.score = 0
3.1显示得分
为在屏幕上显示得分,首先创建一个新类Scoreboard,后面将使用他们显示最高得分,等级和余下的飞船数。
scoreboard.py
:
import pygame.fontclass Scoreboard():"""显示得分信息的类"""def __init__(self, screen, ai_settings, stats):"""初始化显示得分涉及的属性"""self.screen = screenself.screen_rect = screen.get_rect()self.ai_settings = ai_settingsself.stats = stats # 游戏统计信息# 显示得分信息时使用的字体设置self.text_color = (30, 30, 30)self.font = pygame.font.SysFont(None, 48)# 准备初始得分图像self.prep_score()def prep_score(self):"""将得分转换为一副渲染的图像"""score_str = str(self.stats.score)self.score_image = self.font.render(score_str, True, self.text_color)# 将得分放在屏幕右上角self.score_rect = self.score_image.get_rect()self.score_rect.right = self.screen_rect.right - 20self.score_rect.top = 20def show_score(self):"""在屏幕上显示得分"""self.screen.blit(self.score_image, self.score_rect)
3.2创建记分牌
为显示得分,需要在alien_invasion.py
中创建一个Scoreborad实例:
# 创建记分牌sb = Scoreboard(ai_setting, stats)
这里报错了:
TypeError: init() missing 1 required positional argument: ‘stats’
显示缺少实参stats,但是我这里有stats,对比了一下Scoreboard类的初始化函数,发现缺少的是第一个形参screen的实参,补上就好了。
# 创建记分牌sb = Scoreboard(screen, ai_setting, stats)
3.3在外星人被消灭时更新得分
首先在settings.py
中更新玩家击落一个外星人将得到的点数:
def initialize_dynamic_settings(self):--snip--self.alien_points = 50 #玩家击落一个外星人将得到的点数
当外星人被击落时,更新得分:
game_function.py
:
def check_bullet_alien_collisions(bullets, aliens, ai_settings, screen, ship, stats, sb):# 检查是否有子弹击中外星人# 如果是这样,就删除相应的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)if collisions: #这里stats.score += ai_settings.alien_pointssb.prep_score()
并要修改调用此函数所需要传递的实参即game_function.py
中的:
def update_bullets(ai_settings, screen, ship, bullets, aliens, stats, sb):bullets.update()# 更新子弹的位置,并删除已经消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)# 检查是否有子弹击中外星人并处理check_bullet_alien_collisions(bullets, aliens, ai_settings, screen, ship, stats, sb)
与alien_invasion.py
中的:
gf.update_bullets(ai_setting, screen, ship, bullets, aliens, stats, sb)
现在运行程序,得分将不断增加。
3.4将消灭的每个外星人的点数都计入得分
上面的代码可能遗漏了一些被消灭的外星人,例如一次循环中两个子弹射中了外星人或者因为子弹更宽而同时击中了外星人,玩家只能得到一个被消灭的外星人的点数。未修复此问题,调整检测子弹和外星人的碰撞方式。
在check_bullet_alien_collisions()
中,与外星人碰撞的子弹都是字典collisions中的一个键,而与每颗子弹相关的值都是一个列表,其中包含该子弹撞到的外星人。这里遍历字典collisions,确保消灭的每一个外星人点数都记入得分:
def check_bullet_alien_collisions(bullets, aliens, ai_settings, screen, ship, stats, sb):# 检查是否有子弹击中外星人# 如果是这样,就删除相应的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if collisions:for aliens in collisions.values(): #只运行了一次stats.score += ai_settings.alien_points * len(aliens)sb.prep_score()
for aliens in collisions.values()
中使用len(aliens)
表示看每个键中值的数量,刚开始不明白为什么只运行了一次,然后我写了一个test检测字典中value()到底输出啥:
dir = { '1':['1', '2', '3'],'2': '1'}
for value in dir.values():print(len(value))
结果:
输出结果为:第一个键值对里面的键中有3个值,第二个键中只有一个值。
而外星人入侵程序中collisions
字典中本次检测只有一个子弹即一个键,因此for循环只执行了一次。
3.5提高点数
玩家每提高一个等级,游戏就变得更难,因此需要击中外星人的点数相应变高。
settintgs.py
:
class Settings():def initialize_dynamic_settings(self):self.ship_speed_factor = 1.5 # 设置飞船速度为1.5self.bullet_speed_factor = 3self.alien_speed_factor = 0.5 #外星人左右移动的速度self.alien_points = 50 #玩家击落一个外星人将得到的点数# fleet_direction为1表示向右移动,为-1表示向左移动self.fleet_direction = 1def increase_speed(self):"""提高速度设置"""self.ship_speed_factor *= self.speedup_scaleself.bullet_speed_factor *= self.speedup_scaleself.alien_speed_factor *=self.speedup_scaleself.alien_points = int(self.score_scale * self.alien_points) #这里print(self.alien_points) #查看是否每次升级的时候点数都会提高
在每次升级后,点数都会增加。
3.6将得分圆整
大多接机风格的游戏都将得分显示为10的整数倍。本程序也将遵循此原则:
在Scoreboard
类中执行者这种修改:
class Scoreboard():def prep_score(self):"""将得分转换为一副渲染的图像"""rounded_score = int(round(self.stats.score, -1))score_str = "{:,}".format(rounded_score)
函数round()通常让小数精确到小数点后多少位,其中小数位数是由第二个实参指定的。本程序第二个实参为负数,round()将远征到最近的10,100,1000等倍数,
"{:,}".format(rounded_score)
是一个字符串格式设置指令,他让Python将数值转换为字符串时在其中插入逗号,例如,输出10,000而不是10000,使得最后得分为整洁得分。
3.7最高得分
最高分纪录将一直存在且不会重置。
因此,在GameStats
类中将high_score初始化在__init__
函数中。
class GameStats():"""跟踪游戏的统计信息"""def __init__(self, ai_settings):"""初始化统计信息"""self.ai_settings = ai_settingsself.reset_stats()self.game_active = False #游戏刚启动处于非活跃状态self.high_score = 0def reset_stats(self):"""初始化在游戏运行期间可能变化的统计信息"""self.ships_left = self.ai_settings.ship_limitself.score = 0
接下来修改Scoreboard
的最高得分:
class Scoreboard():"""显示得分信息的类"""def __init__(self, screen, ai_settings, stats): --snip-- # 准备初始得分图像与最高得分图像self.prep_score()self.prep_high_score()def __init__(self, screen, ai_settings, stats):def prep_high_score(self):"""将最高分渲染为图像"""high_score = int(round(self.stats.high_score, -1))high_score_str = "{:,}".format(high_score)self.high_score_image = self.font.render(high_score_str, True, self.text_color)# 将最高得分放在屏幕上面中央self.high_score_rect = self.high_score_image.get_rect()self.high_score_rect.centerx = self.screen_rect.centerxself.high_score_rect.top = 20def show_score(self):"""在屏幕上显示得分"""self.screen.blit(self.score_image, self.score_rect)self.screen.blit(self.high_score_image, self.high_score_rect)
接下来在game_function.py
中检查是否产生最高分:
def check_high_score(stats, sb):if stats.score > stats.high_score:stats.high_score = stats.scoresb.prep_high_score()
调用:
def check_bullet_alien_collisions(bullets, aliens, ai_settings, screen, ship, stats, sb):# 检查是否有子弹击中外星人# 如果是这样,就删除相应的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)if collisions:for aliens in collisions.values(): #只运行了一次stats.score += ai_settings.alien_points * len(aliens)sb.prep_score()check_high_score(stats, sb)
3.8显示等级
为在游戏中显示等级,首先在GameStats中添加一个当前等级的属性。
def reset_stats(self):"""初始化在游戏运行期间可能变化的统计信息"""self.ships_left = self.ai_settings.ship_limitself.score = 0self.level = 1
并在scoreboard.py
中调用新方法prep_level():
def __init__(self, screen, ai_settings, stats):# 准备初始得分图像与最高得分图像self.prep_score()self.prep_high_score()self.prep_level()def prep_level(self):"""将等级渲染为图像"""self.level = "当前等级" + str(self.stats.level)self.level_image = self.font.render(self.level, True, self.text_color, self.ai_settings.bg_color)# 将等级放在屏幕下方self.level_rect = self.level_image.get_rect()self.level_rect.right = self.screen_rect.right - 20self.level_rect.top = self.score_rect.bottom + 10def show_score(self):"""在屏幕上显示得分"""self.screen.blit(self.score_image, self.score_rect)self.screen.blit(self.high_score_image, self.high_score_rect)self.screen.blit(self.level_image, self.level_rect)def show_score(self):"""在屏幕上显示得分"""self.screen.blit(self.score_image, self.score_rect)self.screen.blit(self.high_score_image, self.high_score_rect)self.screen.blit(self.level_image, self.level_rect)
在game_function.py
中的check_bullet_alien_collisions()
方法中提高等级,并更新等级图像。
def check_bullet_alien_collisions(bullets, aliens, ai_settings, screen, ship, stats, sb):--snip-- if len(aliens) == 0: #飞船胜利# 删除现有子弹并新建一群外星人bullets.empty()ai_settings.increase_speed()# 整群外星人被打灭,则等级加1stats.level += 1sb.prep_level()
为确保新游戏开始时更新计分和等级图像,在按钮Play单击时触发重置:
def check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship, sb):"""在玩家单击Play按钮时开启新游戏"""button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) #单击的区域是否为button的区域if button_clicked and not stats.game_active:#重置游戏统计信息ai_settings.initialize_dynamic_settings()pygame.mouse.set_visible(False) # 隐藏光标stats.reset_stats() #重新生成一些动态的统计信息stats.game_active = True# 重置记分牌图像 在重新sb.prep_level()sb.prep_score()sb.prep_high_score
并在调用此函数的各个函数更新实参的传递。
3.9显示余下的飞船数
以图形的方式在左上角显示余下的飞船数:
首先让Ship继承Sprite:
import pygame
from pygame.sprite import Spriteclass Ship(Sprite):def __init__(self, ai_settings, screen):"""初始化飞船并设置其初始位置"""super().__init__()
接下来,修改Scoreboard,在其中创建一个可以提供显示的飞船编组:
import pygame.font
from pygame.sprite import Group
from ship import Ship
class Scoreboard():"""显示得分信息的类"""def __init__(self, screen, ai_settings, stats):self.prep_ships()def prep_ships(self):"""显示还有多少飞船"""self.ships = Group()for ship_number in range(self.stats.ships_left):ship = Ship(self.ai_settings, self.screen)ship.rect.x = 10 + ship_number * ship.rect.widthship.rect.y = 10self.ships.add(ship) def show_score(self): self.ships.draw(self.screen)
在game_function.py
中,按下Play按钮和击中飞船时都需要重置绘制飞船的函数:
def ship_hit(stats, aliens, bullets, ai_settings, screen, ship, sb):"""响应被外星人撞到的飞船"""if stats.ships_left > 0:# 将ship_left-1stats.ships_left -= 1#更新ships绘制sb.prep_ships()
def check_play_button(play_button, mouse_x, mouse_y, stats, aliens, bullets, ai_settings, screen, ship, sb):"""在玩家单击Play按钮时开启新游戏"""button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) #单击的区域是否为button的区域if button_clicked and not stats.game_active:#重置游戏统计信息ai_settings.initialize_dynamic_settings()pygame.mouse.set_visible(False) # 隐藏光标stats.reset_stats() #重新生成一些动态的统计信息stats.game_active = True# 重置记分牌图像sb.prep_level()sb.prep_score()sb.prep_high_score()sb.prep_ships()
最后在调用这两个函数的函数中更新他们的实参。
最终显示完整的计分系统,左上角显示了余下多少搜飞船。