Python项目 | 外星人入侵之记分

记分

目标:添加一个Play按钮,让玩家能够开始游戏,以及游戏结束后再玩。每当玩家消灭一群外星人后,加快游戏的节奏,并添加一个记分系统,让游戏更有挑战性和趣味性。

1、添加Play按钮

Play按钮:它在游戏开始前出现,并在游戏结束后再次出现,让玩家能够开始新游戏。

让游戏一开始处于非活动状态,并提示玩家单击Play按钮来开始游戏。在game_stats.py中输入如下代码:


class GameStats():
    # 跟踪游戏的统计信息
    def __init__(self, ai_settings):
        # 初始化统计信息
        self.ai_settings = ai_settings
        self.reset_status()

        # 游戏刚启动时处于非活动状态
        self.game_active = False

    def reset_status(self):
        # skip

现在游戏一开始将处于非活动状态,等我们创建Play按钮后,玩家才能开始游戏。

【1】创建Button类

由于Pygame没有内置创建按钮的方法,创建一个Button 类,用于创建带标签的实心矩形。下面是Button 类的第一部分, 请将这个类保存为文件button.py:

import pygame.font   # 将文本渲染到屏幕上


class Button():
    def __init__(self, ai_settings, screen, msg):
        # 初始化按钮的属性
        self.screen =screen
        self.screen_rect = screen.get_rect()

        # 设置按钮的尺寸和其他属性
        self.width, self.height = 200, 50
        self.button_color = (67, 205, 128)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 48)   # 实参None 让Pygame 使用默认字体,48指定文本的字号。

         # 创建按钮的rect对象,并使其居中
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        # 按钮的标签只需创建一次
        self.prep_msg(msg)



Pygame通过将你要显示的字符串渲染为图像来处理文本。在❺处,我们调用prep_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

方法prep_msg() 接受实参self 以及要渲染为图像的文本(msg )。调用font.render() 将存储在msg 中的文本转换为图像,然后将该图像存储在msg_image 中。方法font.render() 还接受一个布尔实参,该实参指定开启还是关闭反锯齿功能(反锯齿让文本的边缘更平滑)。余下的两个实参分别是文本颜色和背景色。我们启用 了反锯齿功能,并将文本的背景色设置为按钮的颜色(如果没有指定背景色,Pygame将以透明背景的方式渲染文本)。

创建方法draw_button() ,通过调用它可将这个按钮显示到屏幕上:

button.py

    def draw_button(self):
        # 描绘一个用颜色填充的按钮,再绘制文本
        self.screen.fill(self.button_color, self.rect)
        self.screen.blit(self.msg_image, self.msg_image_rect)

调用screen.fill() 来绘制表示按钮的矩形,再调用screen.blit() ,并向它传递一幅图像以及与该图像相关联的rect 对象,从而在屏幕上绘制文本图像。

【2】在屏幕上绘制按钮

alien_invasion.py

#skip

from button import  Button

def run_game():
    # skip
    pygame.display.set_caption("Alien Invasion")

    # 创建Play按钮
    play_button = Button(ai_settings,screen,"Play")
    # skip

    
    # 开始游戏的主循环
    while True:
        # skip

        gf.update_screen(ai_settings, screen,stats, ship, aliens, bullets, play_button)

run_game()

修改update_screen() ,以便在游戏处于非活动状态时显示Play按钮:

game_function.py

def update_screen(ai_settings, screen,stats, ship, aliens, bullets, play_button):
    # skip

    # 如果游戏处于非活动状态,绘制Play按钮
    if not stats.game_active:
        play_button.draw_button()
    # 不断更新屏幕,让最近绘制的屏幕可见
    pygame.display.flip()

运行游戏:

Python项目 | 外星人入侵之记分_第1张图片

【3】开始游戏

需在game_functions.py中添加如下代码,以监视与这个按钮相关的鼠标事件:


def check_events(ai_settings, screen, stats, play_button, ship, bullets):
    # 响应按键和鼠标事件
    # skip

        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(stats, play_button, mouse_x,mouse_y)


def check_play_button(stats, play_button, mouse_x, mouse_y):
    # 在玩家单击Play按钮时开始游戏
    if play_button.rect.collidepoint(mouse_x,mouse_y):
        stats.game_active = True
                # # 向右移动飞船
                # ship.rect.centerx += 1

了pygame.mouse.get_pos() ,它返回一个元组,其中包含玩家单击时鼠标的x 和y 坐标。将这些值传递给函数check_play_button() ,而函 数使用collidepoint() 检查鼠标单击位置是否在Play按钮的rect 内。如果是这样的,将game_active 设置为True ,让游戏就此开始!

在alien_invasion.py更新check_events()调用:

# 开始游戏的主循环
    while True:
        # 监视键盘和鼠标
        gf.check_events(ai_settings, screen, stats, play_button, ship, bullets)
       

现在运行程序,点击按钮Play开始游戏,游戏结束时,game_active 应为False ,并重新显示Play按钮。

【4】重置代码

为在玩家每次单击Play按钮时都重置游戏,需要重置统计信息、删除现有的外星人和子弹、创建一群新的外星人,并让飞船居中,如下所示:

game_function.py

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    # 在玩家单击Play按钮时开始游戏
    if play_button.rect.collidepoint(mouse_x,mouse_y):
        # 重置游戏统计信息
        stats.reset_status()
        stats.game_active = True

        # 清空外星人列表和子弹列表
        aliens.empty()
        bullets.empty()

        # 创建一群新外星人,并让飞船居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

更新check_events() 方法,及调用check_play_button() 的代码。

现在玩家单击Play按钮时,这个游戏都将正确地重置,玩家想玩多少次就玩多少次!

【5】将Play按钮切换到非活动状态

当前,Play按钮存在一个问题:即便Play按钮不可见,玩家单击其原来所在的区域时,游戏依然会作出响应。游戏开始后,如果玩家不小心单击了Play按钮原来所处的区域,游戏将重新开始!

为修复这个问题,可让游戏仅在game_active 为False 时才开始:

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    # 在玩家单击Play按钮时开始游戏
    button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
    if button_clicked and not stats.game_active:
        # 重置游戏统计信息
        
        # skip

【6】隐藏光标

玩家能够开始游戏,让光标可见,游戏开始后,让光标不可见:

game_function.py


def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    # 在玩家单击Play按钮时开始游戏
    button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
    if button_clicked and not stats.game_active:
        # 隐藏光标
        pygame.mouse.set_visible(False)

        # SKIP

游戏结束后,重新显示光标,让玩家能够单击Play按钮来开始新游戏:

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    # 响应被外星人撞到的飞船

    if stats.ship_left > 0:
        # SKIP

    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)

2、提高难度

增加一点趣味性:每当玩家将屏幕上的外星人都消灭干净后,加快游戏的节奏,让游戏玩起来更难。

【1】修改速度设置

先重新组织Settings 类,将游戏设置划分成静态的和动态的两组。对于随着游戏进行而变化的设置,确保它们在开始新游戏时被重置。

settings.py


class Setting():
    # 存储该项目所有设置的类

    def __init__(self):
        # 初始化游戏的静态设置
        # 屏幕设置
        self.screen_width = 1100
        self.screen_height = 620
        self.bg_color = (65,65,65)
        
        #飞船的设置
        self.ship_limit = 3

        # 子弹设置
        self.bullet_width = 3  # 些设置创建宽3像素、高15像素的深灰色子弹
        self.bullet_height = 15
        self.bullet_color = 178,34,34
        self.bullet_allowed = 3 # 将未消失的子弹数限制为3颗

        #外星人设置
        self.fleet_drop_speed = 10   # 外星人群向下移动的速度

         # 以什么样的速度加快游戏节奏
        self.speedup_scale = 1.1

        self.initialize_dynamic_settings()
       

    def initialize_dynamic_settings(self):
        # 初始化随游戏进行而变化的设置
        self.ship_speed_factor = 1.5  # 移动1.5px
        self.bullet_speed_factor = 3
        self.alien_speed_factor = 1  # 外星人移动速度设置
        
        # fleet_direction为1表示右移,-1表示为左移
        self.fleet_direction = 1

    def increase_speed(self):
        # 提高速度的设置
        self.ship_speed_factor *= self.speedup_scale
        self.bullet_speed_factor *= self.speedup_scale
        self.alien_speed_factor *= self.speedup_scale


在check_bullet_alien_collisions() 中,在整群外星人都被消灭后调用increase_speed() 来加快游戏的节奏,再创建一群新的外星人:

game_function.py


def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
    # 响应子弹和外星人的碰撞
    # 删除发生碰撞的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # 删除现有子弹,加快游戏速度,并新建一群外星人
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

【2】重置速度

每当玩家开始新游戏时,需要将发生了变化的设置重置为初始值,否则新游戏开始时,速度设置将是前一次游戏增加了的值:


def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    # 在玩家单击Play按钮时开始游戏
    button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
    if button_clicked and not stats.game_active:
        # 重置游戏设置
        ai_settings.initialize_dynamic_settings()
        
        # skip

现在,玩家将屏幕上的外星人消灭干净后,游戏都将加快节奏,因此难度会更大些。如果游戏的难度提高得太快,可降低settings.speedup_scale 的值;如果游戏的挑战性不足,可稍微提高这个设置的值。

3、记分

实现一个记分系统,以实时地跟踪玩家的得分,并显示最高得分、当前等级和余下的飞船数。

得分是游戏的一项统计信息,们在GameStats 中添加一个score 属性:


class GameStats():
    # 跟踪游戏的统计信息
    # skip

    def reset_status(self):
        # 初始化在游戏运行期间可能变化的统计信息
        self.ship_left = self.ai_settings.ship_limit
        self.score = 0

【1】显示得分

为在屏幕上显示得分,先创建一个新类Scoreboard 。就当前而言,这个类只显示当前得分,后面我们也将使用它来显示最高得分、等级和余下的飞船数。

scoreboard.py:


import pygame.font

class Scoreboard():
    # 显示得分信息的类

    def __init__(self, ai_settings, screen, stats):
        # 初试化显示得分涉及的属性
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats

        # 显示得分信息时显示的字体设置
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 40)

        # 准备初始得分图像
        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.ai_settings.bg_color)

         # 将得分放在屏幕右上角
        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = self.screen_rect.right - 20
        self.score_rect.top = 20

    def show_score(self):
        # 在屏幕上显示得分
        self.screen.blit(self.score_image, self.score_rect)

【2】创建记分牌

为显示得分,在alien_invasion.py中创建一个Scoreboard 实例:


# skip
from scoreboard import Scoreboard
def run_game():
    # skip

    # 创建存储游戏统计信息的实例,并创建记分牌
    sb = Scoreboard(ai_settings,screen,stats)

    # 开始游戏的主循环
    while True:
        # skip

        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

更新方法update_scree():


def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    # 更新屏幕上的图像,并切换到新屏幕
    # skip

    # 显示得分
    sb.show_score()

    # 如果游戏处于非活动状态,绘制Play按钮

运行游戏,可在屏幕右上角看到0:

Python项目 | 外星人入侵之记分_第2张图片

【3】在外星人被消灭时更新得分

指定玩家每击落一个外星人都将得到多少个点:

    def initialize_dynamic_settings(self):
        # skip

        # 记分
        self.alien_points = 50

在check_bullet_alien_collisions() 中,每当有外星人被击落时,都更新得分:

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    # 响应子弹和外星人的碰撞
    # 删除发生碰撞的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if  collisions:
        stats.score += ai_settings.alien_points
        sb.prep_score()   # 创建一幅显示最新得分的新图像
        
     # skip

更新改update_bullets() 形参,以及改主while 循环中调用update_bullets() 的代码。

运行游戏,得分不断增加:

【4】将消灭的每个外星人的点数都计入得分

代码可能遗漏了一些被消灭的外星人。例如,如果在一次循环中有两颗子弹射中了外星人,或者因子弹更宽而同时击中了多个外星人,玩家将只能得到一个被消灭 的外星人的点数。

为修复这种问题,调整检测子弹和外星人碰撞的方式。 在check_bullet_alien_collisions() 中,与外星人碰撞的子弹都是字典collisions 中的一个键;而与每颗子弹相关的值都是一个列表,其中包含该子弹撞到的外星 人。遍历字典collisions ,确保将消灭的每个外星人的点数都记入得分:

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    # 响应子弹和外星人的碰撞
    # 删除发生碰撞的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if  collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
            sb.prep_score()  # 创建一幅显示最新得分的新图像

可放大子弹尺寸测试结果。

【5】提高点数

玩家每提高一个等级,游戏都变得更难,因此处于较高的等级时,外星人的点数应更高。为实现这种功能,添加一些代码,以在游戏节奏加快时提高点数:

setting.py


class Setting():
    # 存储该项目所有设置的类

    def __init__(self):
        # skip

         # 以什么样的速度加快游戏节奏
        self.speedup_scale = 1.1
        # 外星人点数的提高速度
        self.score_scale = 1.5

        self.initialize_dynamic_settings()


   #skip

    def increase_speed(self):
        # 提高速度的设置和外星人点数
        self.ship_speed_factor *= self.speedup_scale
        self.bullet_speed_factor *= self.speedup_scale
        self.alien_speed_factor *= self.speedup_scale

        self.alien_points = int(self.alien_points * self.score_scale)

在测试效果时可在Settings 的方法increase_speed() 中添加了一条print 语句:

 def increase_speed(self): 
        # skip  

        self.alien_points = int(self.alien_points * self.score_scale)
        print(self.alien_points)

列如在得分如下时:

Python项目 | 外星人入侵之记分_第3张图片

测试完成后删除该print语句。

【6】将得分圆整

大多数街机风格的射击游戏都将得分显示为10的整数倍,下面让记分系统遵循这个原则。将设置得分的格式,在大数字中添加用逗号表示的千位分隔符。在Scoreboard 中执行这种修改:

 def prep_score(self):
        # 将得分转化为一幅渲染的图像
        rounded_score = int(round(self.stats.score, -1))
        score_str = "{:,}".format(rounded_score)
        self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)

函数round() 通常让小数精确到小数点后多少位,其中小数位数是由第二个实参指定的。将第二个实参指定为负数,round() 将圆整到最近的10、100、1000等整数倍。这里代码让Python将stats.score 的值圆整到最近的10的整数倍,并将结果存储到rounded_score 中。(在Python 2.7中,round() 总是返回一个小数值,因此使用int() 来确保报告的得分为整数。如果使用的是Python 3,可省略对int() 的调用。)

score_str使用了一个字符串格式设置指令,它让Python将数值转换为字符串时在其中插入逗号,例如,输出1,000,000 而不是1000000 。

得分为10的整数倍,并将逗号用作千分位分隔符:

Python项目 | 外星人入侵之记分_第4张图片

【7】最高得分

每个玩家都想超过游戏的最高得分记录。下面跟踪并显示最高得分,给玩家提供要超越的目标。将最高得分存储在GameStats 中:

class GameStats():
    # 跟踪游戏的统计信息
    def __init__(self, ai_settings):
        # skip

        # 任何情况下都不应该重置最高分
        self.high_score = 0

来修改Scoreboard 以显示最高得分:



class Scoreboard():
    # 显示得分信息的类

    def __init__(self, ai_settings, screen, stats):
        # skip

        # 准备包含最高得分和当前得分的图像
        self.prep_score()
        self.prep_high_score()

   #skip

    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.ai_settings.bg_color)

        # 将最高得分放在屏幕顶部中央
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.score_rect.centerx
        self.high_score_rect.top = self.score_rect.top

    def show_score(self):
        # 在屏幕上显示当前得分和最高
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)

为检查是否诞生了新的最高得分,在game_functions.py中添加一个新函数check_high_score() :

def check_high_score(stats, sb):
    # 检查是否诞生了最高分数
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()

在check_bullet_alien_collisions() 中,每当有外星人被消灭,都需要在更新得分后调用check_high_score() :

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    # 响应子弹和外星人的碰撞
    # 删除发生碰撞的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if  collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
            sb.prep_score()  # 创建一幅显示最新得分的新图像
        check_high_score(stats ,sb)
   

第一次玩这款游戏时,当前得分就是最高得分。再次开始这个游戏时,最高得分出现在中央,而当前得分出现在右边。

Python项目 | 外星人入侵之记分_第5张图片

4、小结

学习了

  1. 如何创建用于开始新游戏的Play按钮;
  2. 如何检测鼠标事件,以及在游戏处于活动状态时如何隐藏光标;
  3. 如何随游戏的进行调整其节奏;
  4. 如何实现记分系统;
  5. 以及如何以文本和非文本方式显示信息。

后面还会继续改进该游戏,将该项目全部代码打包放到了:外星人入侵项目(github地址)

你可能感兴趣的:(Python学习,python项目)