Python项目外星人入侵(终)记录分数

Python项目外星人入侵(终)记录分数

在本章中,我们将结束整个游戏项目的开发,我们会添加一个play按钮,用来使玩家通过点击启动游戏或者在游戏结束后重启游戏。同时我们还将修改这个游戏,使其在玩家等级提高时加快游戏节奏,并实现一个记分系统。代码块中的--snip--表示代码不变的部分。

在本节进行讲解之前,我们首先做了如下修改,使游戏布局更加合理:

# settings.py

        self.ship_speed = 1.5  # 设置飞船速度的初始值
        self.bullet_speed = 3  # 子弹的速度
        self.alien_speed = 0.5  # 外星人的移动速度为0.5

修改了飞船,子弹,外星人的速度。

# game_function.py

def get_number_rows(ai_settings, ship_height, alien_height):
    """计算屏幕可以容纳多少行外星人"""
    # 计算屏幕有多少剩余空间
    available_space_y = ai_settings.screen_height - 4 * alien_height - ship_height
    # 计算屏幕有多少行
    number_rows = int(available_space_y / (2 * alien_height))
    return number_rows

修改了计算屏幕有多少剩余空间的公式,将3个外星人高度修改为4个。

添加Play按键

在本章节中,我们将添加一个play按键,它将在游戏开始前出现,并在游戏结束后再次出现,使玩家能重新开始游戏。

使游戏进入非活跃状态

当前,游戏在运行alien_invasion.py就开始了,接下来我们要使游戏处在非活跃状态,只有当我们点击Play按键后才进入活跃状态,能够开始游戏,为此我们需要修改game_stats.py的参数:

class GameStats:
    """跟踪游戏的统计信息"""

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False  # 游戏刚启动处于非活动状态

创建Button类

由于Pygame没有内置创建按钮的方法,因此,我们需要创建哟个Button类,用来创建带标签的实心矩形,我们可以用这些代码来创建任何按钮,首先我们创建一个Python文件,将其命名为button.py

# button.py
import pygame.ftfont    # 该模块能将文本渲染到屏幕上


class Button:

    # message是我们要在按钮中显示的文本
    def __init__(self, ai_settings, screen, message):
        """初始化按钮的属性"""
        self.screen = screen
        self.screen_rect = screen.get_rect()

        # 设置按钮的尺寸和其他属性
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0) # 按钮设置为绿色
        self.text_color = (255, 255, 255)   # 文本内容设置为白色
        self.font = pygame.font.SysFont(None, 48)   # None表示使用默认字体,48表示字号

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

        # 创建按钮的标签
        self.prep_msg(message)	# 将message渲染为图像,并使其在按钮中居中

新出现的代码的相关解读见注释,其中prep_msg()的代码如下,我们使用该函数将文本渲染为图像:

# button.py
    def prep_msg(self, message):
        """将message渲染为图像,并且使其在按钮中居中"""
        self.msg_image = self.font.render(message, 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,以及要渲染成图像的文本message。调用方法font.render()将存储在message的文本转换为图像,这个方法接收一个布尔实参,该实参指定开启还是关闭反锯齿(反锯齿让文本边缘更加光滑)功能。

最后,我们创建方法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)   # 绘制文本

fill()方法是在指定的位置,用指定的颜色填充块

blit()方法是在指定的地方绘制图像。

在屏幕上绘制按钮

我们使用Button类来创建一个Play按钮,由于我们只需要一个Play按钮,因此我们直接在alien_invasion.py中创建它:

# alien_invasion.py
from button import Button
--snip--
	play_button = Button(ai_settings, screen, "Play")	# 创建一个Button类的实例
--snip--
	gf.update_screen(ai_settings, screen, ship, aliens, bullets, play_button)
    
run_geme()

在这里我们创建了一个Button类的实例,之后将实例传递给update_screen(),以便于在屏幕更新时显示按钮,接下来我们修改update_screen()函数:

# game_function.py

def update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button):
    """更新屏幕上的图像,并且切换新屏幕"""
    screen.fill(ai_settings.bg_color)  # 用指定的颜色填充屏幕
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # 将飞船显示在屏幕中
    aliens.draw(screen)  # 让外星人显示在屏幕中
    if not stats.game_active:   # 如果游戏处于不活跃状态就打印开始按钮
        play_button.draw_button()
    pygame.display.flip()  # 将最近绘制的屏幕显示出来

为了使Play按钮显示在所有其他屏幕元素上边,我们在绘制其他所有元素后再绘制该按钮,现在我们运行主函数后会在屏幕中央看到一个Play按钮。

使用按钮开始游戏

接下来我们要修改check_evernts()的代码,已经创建新的函数check_play_button()

# game_function.py

def check_events(ai_settings, screen, stats, play_button, ship, bullets):
    """响应键盘和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果事件的类型是退出,相当于鼠标点击×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # 判断是否有按键按下
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # 判断是否有按键松开
            check_keyup_events(event, ship)
        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

无论玩家单击屏幕什么地方,Pygame都会检测一个MOUSEBUTTONDOWN(鼠标按下)事件,但是我们只希望这个游戏在玩家单击Play按键时做出响应,为此我们使用pygame.mouse.get_pos()函数,他返回包含点击位置的坐标的元组,之后我们将值传递给函数check_play_button()判断点击的位置是否是按钮内,如果是就开始游戏,否则什么也不会发生。

接下来我们在主函数中修改check_events()函数,并传递新的实参。

# alien_invasion.py

gf.check_events(ai_settings, screen, stats, play_button, ship, bullets)

重置游戏

前边编写的代码只处理了玩家第一次单击Play按钮的情况,而没有处理游戏结束时候的情况,因为没有重置导致游戏结束的条件。接下来,我们通过设置使玩家每次单击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.rest_stats()
        stats.game_active = True
        # 清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        # 创建一群新的外星人并使其居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

在这里我们更新了check_play_button()函数,在这里我们传入了新的参数,在点击后给玩家提供三条新的飞船,删除现有的外星人和子弹群并且创建一群新的,最后将飞船居中。

接下来我们需要修改check_events()部分的代码,并使其能正确的传入参数。

# game_function.py

def check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets):
    """响应键盘和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果事件的类型是退出,相当于鼠标点击×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # 判断是否有按键按下
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # 判断是否有按键松开
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # 判断是否有鼠标按下
            mouse_x, mouse_y = pygame.mouse.get_pos()  # 返回按下的点的坐标
            check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y)

同时,alien_invasion.py部分的代码也需要修改:

# alien_invasion.py

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

现在当我们点击Play按键时,游戏就会被正确的重置,我们想玩几次就玩几次。

将Play按钮切换至非活跃状态

当前使用Play按钮存在如下问题,那就是即便Play按钮不可见,玩家单击原来所在的区域时,游戏会重新开始,接下来我们通过设置成游戏处于不活跃状态时点击Play按键才有效来解决这个问题

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) and not stats.game_active:
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        # 清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        # 创建一群新的外星人并使其居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

隐藏游戏光标

为了能让玩家开始游戏,我们需要使光标可见,但是接下来我们希望当游戏处在非活动状态时光标不可见:

# 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) and not stats.game_active:
        # 隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        # 清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        # 创建一群新的外星人并使其居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

通过set_visible()函数里的参数为False,我们可以在游戏开始时隐藏鼠标,接下来,我们需要在游戏结束后重新显示光标。

# game_function.py

def 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
        pygame.mouse.set_visible(True)

在上述最后一行,我们在set_visible()函数中传入参数True,以便于当游戏进入非活跃状态后能够使鼠标再次出现,以便与玩家进行操作。

随着游戏进度不断地提高等级

当前将整群外星人都消灭干净后,游戏的难度并没有变化,接下来我们将给游戏增加难度:每当玩家将屏幕上的外资那个人都消灭干净后,加快游戏的节奏,使游戏更难。

修改速度设置

首先,我们重新组织Settings类,将其中的游戏设置分为静态和动态两个部分,其中静态设置是指不会随着游戏的进度而变化的设置。

# settings.py
class Settings:
    """存储外星人入侵的有关的所有的类"""

    def __init__(self):
        """初始化游戏静态设置"""
        # 屏幕设置
        self.screen_width = 1200  # 设置窗口宽度
        self.screen_height = 700  # 设置窗口高度
        self.bg_color = (230, 230, 230)  # 设置背景颜色

        # 飞船设置
        self.ship_limit = 3  # 设置玩家最多的飞船数

        # 子弹设置
        self.bullet_width = 3  # 子弹的宽度
        self.bullet_height = 15  # 子弹的高度
        self.bullet_color = (60, 60, 60)  # 子弹的颜色
        self.bullets_allowed = 3  # 将未消失的子弹限制为3颗

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

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

        self.initialize_dynamic_speed()

    def initialize_dynamic_speed(self):
        """随着游戏的进行动态变化的量"""
        self.ship_speed = 1.5  # 设置速度的初始值
        self.bullet_speed = 3  # 子弹的速度
        self.alien_speed = 1  # 外星人的移动速度为0.5

        # fleet_direction为1时表示右移,为-1时表示左移
        self.fleet_direction = 1

我们在__init__()中初始化静态设置,同时增加了设置speed_up用来控制游戏节奏的加快速度。同时我们将一些随着游戏进行会变化的量放在函数initialize_dynamic_speed()中,每当游戏重新开始时我们都会重置这些变量。

接下来,我们在其中定义increase_speed()函数,用来提高速度:

# settings.py

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

在这个函数中,我们每次让飞船,子弹,外星人的速度提高为上一次的speed_up的倍数,当我们消灭完一群外星人后,我们会先调用该函数来加快游戏进度,再创建一群新的外星人:

def update_bullets(ai_settings, screen, ship, aliens, bullets):
    """更新子弹的位置,并且删除已经消失的子弹"""
    bullets.update()

    # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)

在游戏结束时重置速度

每当玩家开始新的游戏时,我们都需要将变化了的设置还原成初始值。

# 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) and not stats.game_active:
        # 重置游戏速度设置
        ai_settings.initialize_dynamic_speed()
        # 隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        # 清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        # 创建一群新的外星人并使其居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

现在游戏的游戏性功能基本开发完成,我们目前能通过飞船对外星人进行设计,并且能逐步增加游戏难度,接下来我们将开发计分系统。

分数记录

记下来我们将实现计分系统,以便于实时追踪玩家的得分,并显示最高分,当前等级,还有余下的飞船数目。

因为得分是游戏的一项统计信息,因此我们在GameStats类中增加一个score属性:

 # gamestats.py
    
    def reset_stats(self):
        """初始化游戏运行期间可能变化的信息"""

        # 统计游戏中剩余的飞船数目
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0  # 统计游戏得分

为了每次在游戏开始时重置得分,我们在reset_stats()函数中重置得分信息。

显示得分

为了在屏幕上显示得分,我们首先创建一个新类ScoreBoard。就目前而言,这个类只显示得分,但是之后我们也用它来显示最高分,等级,还有余下的飞船数。

# scoreboard.py

import pygame.ftfont


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 = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)
        
        # 准备初始得分图像
        self.prep_score()

由于需要在屏幕上显示文本,因此我们需要先导入pygame.ftfont,接下来我们在方法prep_score()中将文本转换为图像:

# scoreboard.py
    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.rigtht = self.screen_rect.right - 20
        self.score_rect.top = 20

在上述方法中,我们先将数字值转换为字符串,再将字符串传递给render()函数渲染为图像,为了更加清楚的显示得分,我们向render()传递了屏幕背景色和文本颜色。

在这里我们将得分窗口放在屏幕的右上角,并在得分增大导致数字变宽时,让它向左延申。

接下来,我们创建方法show_score(),用来更好的渲染图象:

# scoreboard.py

    def show_score(self):
        """显示得分"""
        self.screen.blit(self.score_image, self.score_rect)

这个方法将得分图像显示到指定的位置。

创建记分牌

为了显示得分,我们在alien_invasion.py中创建一个ScoreBoard的实例:

# alien_invasion.py

		--snip--
    
from scoreboard import Scoreboard

		--snip--
    
    stats = GameStats(ai_settings)
    sb = Scoreboard(ai_settings, screen, stats)
    
    	--snip--
        
	gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)
    
    
run_game()
    

接下来我们在屏幕更新函数中调用打印计分板的函数,并传递相应的实参:

# game_funcrion.py

def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    """更新屏幕上的图像,并且切换新屏幕"""
    screen.fill(ai_settings.bg_color)  # 用指定的颜色填充屏幕
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # 将飞船显示在屏幕中
    aliens.draw(screen)  # 让外星人显示在屏幕中
    sb.show_score() # 打印计分信息
    if not stats.game_active:  # 如果游戏处于不活跃状态就打印开始按钮
        play_button.draw_button()
    pygame.display.flip()  # 将最近绘制的屏幕显示出来

在外星人被消灭时获得分数

为了在屏幕上实时的显示得分,每当有外星人被击中时,我们都会更新stats.score的值,再调用prep_score()更新得分图像,但在此之前,我们需要指明玩家每击落一个外星人可以获得多少分数:

# settings.py

    def initialize_dynamic_speed(self):
        """随着游戏的进行动态变化的量"""
        self.ship_speed = 1.5  # 设置飞船速度的初始值
        self.bullet_speed = 3  # 子弹的速度
        self.alien_speed = 0.5  # 外星人的移动速度为0.5
        self.alien_point = 50 # 每击败一个外星人获得的点数

        # fleet_direction为1时表示右移,为-1时表示左移
        self.fleet_direction = 1

随着游戏的进行,我们将会提高每个外星人值的点数,为了确保每次开始新游戏前这个值都会被重置,我们在initialize_dynamic_speed()中设置它。

接下来在check_bullet_alien_collisions()中,每当有外星人 被击落,都会更新得分

# game_function.py

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_point
        sb.prep_score()
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

在这里我们更新了check_bullet_alien_collisions()的定义,使其中包括了形参stats和sb,在子弹撞到外星人时,Pygame会返回一个字典,我们检查这个字典是否存在,如果就增加一个外星人的得分,然后我们用prep_score()来创建一幅显示最新得分的图像。

接下来我们修改update_bullets(),确保在函数间传递正确的实参

# game_function.py
def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """更新子弹的位置,并且删除已经消失的子弹"""
    bullets.update()

    # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)

同时我们还需要修改主循环中调用上述函数部分的代码:

 while True:
        gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

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

当前,我们的代码可能会遗漏了一些消灭的外星人,比如在一次循环中有两颗子弹击中了外星人,或者因为子弹更宽同时击中了多个外星人,在这个时候玩家只会获得一个外星人的分数,为了修复这个问题,我们调整检测碰撞的方式:

# game_function.py

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_point * len(aliens)
            sb.prep_score()
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

check_bullet_alien_collisions()中,与外星人碰撞的每个子弹都是字典中的一个键,而与子弹相关的值都是一个列表,我们遍历字典的值的列表,确保每个被消灭的外星人都计入得分。

提高点数

由于玩家每消灭一群外星人游戏都会变得更难,因此在处于较高的等级时,外星人的点数应该更高:

# settings.py

class Settings:
    """存储外星人入侵的有关的所有的类"""

    def __init__(self):
        
        --snip--
        self.speed_up = 1.1  # 以什么样的速度加快游戏节奏

        self.score_scale = 1.5  # 加快分数的速度
        --snip
        
	def increase_speed(self):
        """提高速度"""
        self.ship_speed *= self.speed_up
        self.bullet_speed *= self.speed_up
        self.alien_speed *= self.speed_up
        self.alien_point = int(self.alien_point * self.score_scale)
        # print(self.alien_point)   # 打印显示当前外星人的分数

在这里我们定义了分数按照1.5倍的速度增加,为了让计算后的点数为整数,我们这里用了函数int()

最后的print语句是为了能让我们每当提高一个等级时,能在终端窗口看到当前外星人的分值,在最后运行时,我们可以将这行代码注释掉。

将得分圆整

很多射击游戏都会将得分显示为10的倍数,因此我们这个小游戏也会遵循这个原则,并且我们还会设置数字的格式,在较大的数字中添加逗号来分割千分位,因此我们在prep_score()方法中增加以下代码

# scoreboard.py

    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()用来将小数精确到小数点后多少位,但是如果传递的是负数,那么它会精确到10,100,1000的整数倍等等。同时在下一行使用了字符串格式化指令,他让Python将数值转化为字符串时在其中插入逗号。

最高得分

接下来我们跟踪并且记录游戏的最高分:

# game_function.py

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False  # 游戏刚启动处于活动状态
        self.high_score = 0  # 在任何时候都不应该重置最高分

我们首先创建新的属性high_score,由于在任何情况下都不会重置最高分,所以我们在__init__函数中创建该属性。

接下来我们在scoreboard.py中增加显示最高分的函数,同时将其与显示当前分数区别开。

# scoreboard.py

    def __init__(self, ai_settings, screen, stats):
        """初始化显示得分涉及的属性"""
        
 		--snip--
        
        # 准备初始得分图像
        self.prep_score()
        self.prep_high_score()

接下来我们在prep_high_score()方法中将最高分渲染为图像,并且采用和当前分数一样的输出格式

# scoreboard.py

    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.screen_rect.centerx
        self.high_score_rect.top = 20

该方法的主要功能是将最高分放在屏幕的顶部中央,并且采用了与显示当前得分一样的圆整手段,在此不再赘述。

接下来我们在show_score()方法中调用该方法,实现在屏幕上对最高分进行显示

# scoreboard.py
    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_function.py中添加新的函数check_high_score()

# game_function

def check_high_score(stats, sb):
    """检查是否诞生了最高分"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_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_point * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, ship, aliens)

字典存在的时候,我们根据消灭了多少外星人来更新得分,之后我们再调用check_high_score()函数,来判断是否需要更新最高分。

显示等级

为了在在游戏中显示玩家的等级,我们需要在GameStats类中添加一个表示当前等级的属性。同时,为了切薄每次开始新的游戏时都可以重置等级,我们在reset_stats()函数中初始化该属性。

# game_stats.py
    def reset_stats(self):
        """初始化游戏运行期间可能变化的信息"""

        # 统计游戏中剩余的飞船数目
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0  # 统计游戏得分
        self.level = 1  # 统计游戏的等级信息

接下来为了能够在当前得分下方显示当前等级,我们在__init__方法中调用一个新的方法prep_level()用来把游戏等级渲染为图像。

# scoreboard.py

class Scoreboard:
    """显示得分信息的类"""

    def __init__(self, ai_settings, screen, stats):
        """初始化显示得分涉及的属性"""

        --snip--

        # 准备显示当前得分和最高分的图像
        self.prep_score()
        self.prep_high_score()
        self.prep_level()

其中方法prep_level()的代码如下:

# scoreboard.py
    def prep_level(self):
        """将等级转换为图像"""
        self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_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()方法,来打印等级的值:

# scoreboard.py
    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
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_point * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        ai_settings.increase_speed()
        # 提高等级
        stats.level += 1
        sb.prep_level()
        create_fleet(ai_settings, screen, ship, aliens)

在上述函数中,如果我们将一群外星人都消灭完了,就将level的值加1,并且调用prep_level()方法,以便于能正常显示等级。

同时为了重新开始游戏时能够更新计分和等级图像,我们需要修改我们单击Play按键后的代码:

# game_function.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """在玩家单击Play后开始新游戏"""
    # 判断鼠标单击的位置是否在按键内, 以及此时游戏状态是否活跃
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # 重置游戏设置
        ai_settings.initialize_dynamic_speed()
        # 隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        # 重置记分牌图像
        sb.prep_score()
        sb.prep_high_score()
        sb.prep_level()
        # 清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        # 创建一群新的外星人并使其居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

在上述代码中我们在重置完游戏的统计信息后,重置记分牌的图像,由于我们需要调用scoreboard.py中的方法,因此我们需要在参数中传入该模块的实例。

同时我们需要修改check_events()的形参,向其中传入scoreboard模块的实例:

# game_function.py
def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    """响应键盘和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果事件的类型是退出,相当于鼠标点击×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # 判断是否有按键按下
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # 判断是否有按键松开
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # 判断是否有鼠标按下
            mouse_x, mouse_y = pygame.mouse.get_pos()  # 返回按下的点的坐标
            check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)

之后我们需要修改主函数中调用check_events()部分传递的形参

# alien.invasion.py 
    while True:
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

显示余下的飞船数

最后我们来显示玩家还剩余多少条飞船,但是这里我们将使用图片而不是数字。为此,我们在屏幕左上角绘制出来还有多少条飞船。

首先我们让Ship类继承Sprite,以便于我们能正确的创建飞船编组。

# ship.py
class Ship(Sprite):
    def __init__(self, ai_settings, screen):
        """初始化飞船并且设置其初始位置"""
        super(Ship, self).__init__()
        
        --snip--

接下来,我们需要修改ScoreBoard,在其中创建一个可供显示的飞船编组,首先我们需要导入ship模块,并更新初始化方法

# scoreboard.py
import pygame.ftfont
from pygame.sprite import Group
from ship import Ship


class Scoreboard:
    """显示得分信息的类"""

    def __init__(self, ai_settings, screen, stats):
        """初始化显示得分涉及的属性"""
        
        --snip--

        # 准备显示当前得分和最高分的图像
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

接下来,我们在prep_ship()方法中创建一个飞船编组,并使用该方法显示还剩余多少条飞船。

# scoreboard.py

    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.width
            self.rect.y = 10
            self.ships.add(ship)

在这个方法中,我们创建了一个空的编组用来存储飞船的实例,为了填充这个编组,我们根据玩家还有多少艘飞船来循环相应的次数。在这个方法中,我们创建了一艘飞船,并且重新设置了其坐标,之后将其添加到编组中。

接下来我们需要开始绘制飞船了:

# scoreboard.py
    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)
        # 绘制飞船
        self.ships.draw(self.screen)

为了让玩家在游戏开始时知道他还有多少艘飞船,我们在开始游戏时调用上述函数

# game_function.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """在玩家单击Play后开始新游戏"""

    --snip--
    
        stats.game_active = True
        # 重置记分牌图像
        sb.prep_score() # 重置当前分数
        sb.prep_high_score()    # 重置最高分数
        sb.prep_level() # 重置游戏等级
        sb.prep_ships() # 重置可用飞船数

接下来我们还需要在飞船被外星人撞到时调用prep_ships()方法

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

    if stats.ships_left > 0:
        stats.ships_left -= 1  # 将飞船数目减一
        sb.prep_ships()  # 更新记分牌
        # 清空子弹和外星人列表
        aliens.empty()
        bullets.empty()

        # 创建一群新的外星人,并把飞船放到屏幕中央
        create_fleet(ai_settings, screen, ship, aliens)  # 创建新的外星人
        ship.center_ship()  # 将飞船移动到屏幕中央

        # 暂停
        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)

在这里我们传入了模块scoreboard的实例sb,并且在飞船数目减少时调用prep_ships()方法

同时我们需要修改下列函数,以及对应的函数调用,确保参数传递正确

def check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """检查是否有外星人到达屏幕底端"""
    screen_rect = screen.get_rect()  # 读取屏幕的矩阵信息
    for alien in aliens:

        # 如果外星人的底部矩阵的坐标大于屏幕,就执行碰撞响应
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
            break


def update_aliens(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """更新外星人的位置"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    # 检测外星人和飞船的碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets)

最后我们在主函数中修改调用部分的代码,向其传递实参sb

# alien.invasion.py

    while True:
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, sb,screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)

下图显示了完整的游戏计分系统,左上角指出了剩余的飞船数目

总结

项目外星人入侵的学习到此结束,在这个项目中学到了如火热在Pygame上绘制图像,如何响应鼠标,键盘按键,如果创建可视化的按钮等等,但是这个项目还有很多不完善的地方,比如每次重新运行项目,最高分都会还原成0,没有其他额外的功能,比如外星人也能发射子弹,然后控制飞船躲避等等,在后续的过程中,如果有机会会对项目进行优化。接下来主要学习图表绘制以及图像处理相关的知识。

全部代码

如下是在开篇所说的环境下可以成功运行的代码作为参照,总共包括九个模块

alien_invasion.py

# 主模块,alien_invasion.py
import pygame  # 包含了游戏开发所需的功能
from settings import Settings
from ship import Ship
import game_function as gf
from pygame.sprite import Group
from game_stats import GameStats
from button import Button
from scoreboard import Scoreboard


def run_game():
    """初始化游戏并创建一个屏幕对象"""
    pygame.init()  # 进行初始化,检测工具包是否完整
    ai_settings = Settings()
    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))  # 创建一个大小为800*600的窗口
    pygame.display.set_caption("外星人入侵")  # 设置屏幕名字
    play_button = Button(ai_settings, screen, "Play")
    stats = GameStats(ai_settings)
    sb = Scoreboard(ai_settings, screen, stats)
    ship = Ship(ai_settings, screen)  # 创建一艘飞船
    bullets = Group()  # 创建一个存储子弹的编组
    aliens = Group()  # 创建一个外星人编组
    gf.create_fleet(ai_settings, screen, ship, aliens)  # 创建外星人群
    # 开始游戏的主循环
    while True:
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, sb,screen, ship, aliens, bullets)
        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)


run_game()  # 运行程序

ship.py

# 初始化飞船的相关类,ship.py
import pygame
from pygame.sprite import Sprite


class Ship(Sprite):
    def __init__(self, ai_settings, screen):
        """初始化飞船并且设置其初始位置"""
        super(Ship, self).__init__()
        self.screen = screen
        self.moving_right = False  # 能否向右移动标志
        self.moving_left = False  # 能否向左移动标志
        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  # 获取屏幕的x轴的中点数据并赋值给rect
        self.rect.bottom = self.screen_rect.bottom  # 获取屏幕的底部位置数据并赋值给rect

        # 再飞船的属性center中存储小数
        self.center = float(self.rect.centerx)  # 设置一个存储小数的新属性

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

    def update(self):
        """检查标志的状态,如果标志为True就移动飞船"""

        # 更新飞船的center值而不是rect值
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.center += self.ai_settings.ship_speed
        if self.moving_left and self.rect.left > 0:
            self.center -= self.ai_settings.ship_speed

        # 根据self.center的值更新self.centerx的值
        self.rect.centerx = self.center

    def center_ship(self):
        """让飞船在屏幕上居中"""
        self.center = self.screen_rect.centerx

game_function.py

# 关于游戏各种功能的函数,game_fuction.py
import sys  # 使用sys模块来退出游戏
import pygame  # 包含了游戏开发所需的功能
from bullet import Bullet
from alien import Alien
from time import sleep


def check_keydown_events(event, ai_settings, screen, ship, bullets):
    """响应按键进行操作"""
    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:  # 判断按下的是不是空格
        fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_q:  # 判断按下的是不是Q,如果是退出游戏
        sys.exit()


def check_keyup_events(event, ship):
    """响应松开按键"""
    if event.key == pygame.K_RIGHT:  # 判断松开的时右箭头
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:  # 判断松开的是有箭头
        ship.moving_left = False


def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    """响应键盘和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果事件的类型是退出,相当于鼠标点击×
            sys.exit()
        elif event.type == pygame.KEYDOWN:  # 判断是否有按键按下
            check_keydown_events(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:  # 判断是否有按键松开
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:  # 判断是否有鼠标按下
            mouse_x, mouse_y = pygame.mouse.get_pos()  # 返回按下的点的坐标
            check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)


def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """在玩家单击Play后开始新游戏"""
    # 判断鼠标单击的位置是否在按键内, 以及此时游戏状态是否活跃
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # 重置游戏设置
        ai_settings.initialize_dynamic_speed()
        # 隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        # 重置记分牌图像
        sb.prep_score()  # 重置当前分数
        sb.prep_high_score()  # 重置最高分数
        sb.prep_level()  # 重置游戏等级
        sb.prep_ships()  # 重置可用飞船数
        # 清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        # 创建一群新的外星人并使其居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()


def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    """更新屏幕上的图像,并且切换新屏幕"""
    screen.fill(ai_settings.bg_color)  # 用指定的颜色填充屏幕
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # 将飞船显示在屏幕中
    aliens.draw(screen)  # 让外星人显示在屏幕中
    sb.show_score()  # 打印计分信息
    if not stats.game_active:  # 如果游戏处于不活跃状态就打印开始按钮
        play_button.draw_button()
    pygame.display.flip()  # 将最近绘制的屏幕显示出来


def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """更新子弹的位置,并且删除已经消失的子弹"""
    bullets.update()

    # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)
    # print(len(bullets)) # 显示还有多少个子弹,这个知识测试,运行时把这个语句删除可以降低内存


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_point * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        ai_settings.increase_speed()
        # 提高等级
        stats.level += 1
        sb.prep_level()
        create_fleet(ai_settings, screen, ship, aliens)


def fire_bullet(ai_settings, screen, ship, bullets):
    """如果没有达到发射限制就发射一个子弹"""
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)  # 创建一个子弹的实例
        bullets.add(new_bullet)  # 将子弹实例放在编组中


def get_number_rows(ai_settings, ship_height, alien_height):
    """计算屏幕可以容纳多少行外星人"""
    # 计算屏幕有多少剩余空间
    available_space_y = ai_settings.screen_height - 4 * alien_height - ship_height
    # 计算屏幕有多少行
    number_rows = int(available_space_y / (2 * alien_height))
    return number_rows


def get_number_alien_x(ai_settings, alien_width):
    """计算每行可以容纳多少个外星人"""
    # 计算一行有多少个位置
    available_space_x = ai_settings.screen_width - 2 * alien_width
    # 计算一行能容纳多少个外星人
    number_alien_x = int(available_space_x / (2 * alien_width))
    return number_alien_x


def create_alien(ai_settings, screen, aliens, alien_number, row_number):
    """创建一个外星人并放入当前行"""
    alien = Alien(ai_settings, screen)  # 创建一个外星人
    alien_width = alien.rect.width  # 获取一个外星人的宽度
    alien_height = alien.rect.height
    alien.x = alien_width + 2 * alien_width * alien_number  # 设定每个外星人的初始位置
    alien.rect.x = alien.x

    # 确定每个外星人在垂直方向的位置
    alien.rect.y = alien_height + 2 * alien_height * row_number
    aliens.add(alien)  # 将外星人添加到编组中


def create_fleet(ai_settings, screen, ship, aliens):
    """创建外星人群"""
    alien = Alien(ai_settings, screen)  # 创建一个外星人
    number_alien_x = get_number_alien_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_alien_x):
            create_alien(ai_settings, screen, aliens, alien_number, row_number)


def check_fleet_edges(ai_settings, aliens):
    """有外星人到达边缘时采取相应的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():
            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


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

    if stats.ships_left > 0:
        stats.ships_left -= 1  # 将飞船数目减一
        sb.prep_ships()  # 更新记分牌
        # 清空子弹和外星人列表
        aliens.empty()
        bullets.empty()

        # 创建一群新的外星人,并把飞船放到屏幕中央
        create_fleet(ai_settings, screen, ship, aliens)  # 创建新的外星人
        ship.center_ship()  # 将飞船移动到屏幕中央

        # 暂停
        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)


def check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """检查是否有外星人到达屏幕底端"""
    screen_rect = screen.get_rect()  # 读取屏幕的矩阵信息
    for alien in aliens:

        # 如果外星人的底部矩阵的坐标大于屏幕,就执行碰撞响应
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
            break


def update_aliens(ai_settings, stats, sb, screen, ship, aliens, bullets):
    """更新外星人的位置"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    # 检测外星人和飞船的碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, stats, sb, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, sb, screen, ship, aliens, bullets)


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

alien.py

# 初始化外星人相关的模块,alien.py
import pygame
from pygame.sprite import Sprite


class Alien(Sprite):
    def __init__(self, ai_settings, screen):
        """初始化外星人并设置其起始位置"""
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_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 blitme(self):
        """在指定位置绘制外星人"""
        self.screen.blit(self.image, self.rect)

    def check_edge(self):
        """如果外星人位于屏幕边缘,就返回True"""
        screen_rect = self.screen.get_rect()
        # 如果外星人的边缘大于等于屏幕的右边缘
        if self.rect.right >= screen_rect.right:
            return True
        # 如果外星人的边缘小于等于屏幕的左边缘,即坐标0
        elif self.rect.left <= 0:
            return True

    def update(self):
        """向右移动外星人"""
        self.x += (self.ai_settings.alien_speed *
                   self.ai_settings.fleet_direction)
        self.rect.x = self.x

settings.py

# 存储游戏相关的设置信息,settings.py
class Settings:
    """存储外星人入侵的有关的所有的类"""

    def __init__(self):
        """初始化游戏静态设置"""
        # 屏幕设置
        self.screen_width = 1200  # 设置窗口宽度
        self.screen_height = 700  # 设置窗口高度
        self.bg_color = (230, 230, 230)  # 设置背景颜色

        # 飞船设置
        self.ship_limit = 3  # 设置玩家最多的飞船数

        # 子弹设置
        self.bullet_width = 300  # 子弹的宽度
        self.bullet_height = 15  # 子弹的高度
        self.bullet_color = (60, 60, 60)  # 子弹的颜色
        self.bullets_allowed = 3  # 将未消失的子弹限制为3颗

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

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

        self.score_scale = 1.5  # 加快分数的速度

        self.initialize_dynamic_speed()

    def initialize_dynamic_speed(self):
        """随着游戏的进行动态变化的量"""
        self.ship_speed = 1.5  # 设置飞船速度的初始值
        self.bullet_speed = 3  # 子弹的速度
        self.alien_speed = 0.5  # 外星人的移动速度为0.5
        self.alien_point = 50  # 每击败一个外星人获得的点数

        # fleet_direction为1时表示右移,为-1时表示左移
        self.fleet_direction = 1

    def increase_speed(self):
        """提高速度"""
        self.ship_speed *= self.speed_up
        self.bullet_speed *= self.speed_up
        self.alien_speed *= self.speed_up
        self.alien_point = int(self.alien_point * self.score_scale)
        # print(self.alien_point)   # 打印显示当前外星人的分数

game_stats.py

# 存储游戏统计信息的模块,game_stats.py
class GameStats:
    """跟踪游戏的统计信息"""

    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False  # 游戏刚启动处于活动状态
        self.high_score = 0  # 在任何时候都不应该重置最高分

    def reset_stats(self):
        """初始化游戏运行期间可能变化的信息"""

        # 统计游戏中剩余的飞船数目
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0  # 统计游戏得分
        self.level = 1  # 统计游戏的等级信息

bullet.py

# 初始化子弹的模块,bullet.py
import pygame
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  # 将子弹的centerx设置为飞船的centerx
        self.rect.top = ship.rect.top  # 将子弹的top设置为飞船的top

        # 存储小数表示的子弹位置
        self.y = float(self.rect.y)  # 将子弹的y坐标按照小数存储

        self.color = ai_settings.bullet_color  # 设置子弹的颜色
        self.speed = ai_settings.bullet_speed  # 设置子弹的速度

    def update(self):
        """向上移动子弹"""
        self.y -= self.speed
        self.rect.y = self.y

    def draw_bullet(self):
        """在屏幕上绘制子弹"""
        pygame.draw.rect(self.screen, self.color, self.rect)

button.py

# 初始化游戏按钮的相关信息,button.py
import pygame.ftfont  # 该模块能将文本渲染到屏幕上


class Button:

    # message是我们要在按钮中显示的文本
    def __init__(self, ai_settings, screen, message):
        """初始化按钮的属性"""
        self.screen = screen
        self.screen_rect = screen.get_rect()

        # 设置按钮的尺寸和其他属性
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0)  # 按钮设置为绿色
        self.text_color = (255, 255, 255)  # 文本内容设置为白色
        self.font = pygame.font.SysFont(None, 48)  # None表示使用默认字体,48表示字号

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

        # 创建按钮的标签
        self.prep_msg(message)

    def prep_msg(self, message):
        """将message渲染为图像,并且使其在按钮中居中"""
        self.msg_image = self.font.render(message, True, self.text_color,
                                          self.button_color)    # 文本转换为图像
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self):
        """先绘制一个按钮,再绘制文本"""
        self.screen.fill(self.button_color, self.rect)  # 绘制按钮
        self.screen.blit(self.msg_image, self.msg_image_rect)   # 绘制文本

scoreboard.py

# 统计分数并渲染为图像打印的模块,scoreboard.py
import pygame.ftfont
from pygame.sprite import Group
from ship import Ship


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 = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)

        # 准备显示当前得分和最高分的图像
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

    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)

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

    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.screen_rect.centerx
        self.high_score_rect.top = 20

    def prep_level(self):
        """将等级转换为图像"""
        self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_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

    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.width
            ship.rect.y = 10
            self.ships.add(ship)



    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)
        # 绘制飞船
        self.ships.draw(self.screen)

结语

如果有什么学习中的问题欢迎在评论区留言,看到都会回复,大家一起加油。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值