返回顶部

python项目1--【外星人入侵游戏】之计分系统

python项目1--【外星人入侵游戏】之计分系统

本节将结束游戏《外星人入侵》的开发。我们会添加一个Play按钮,用于根据需要启动游戏以及在游戏结束后重启游戏,还会修改这个游戏,使其随玩家等级提高而加快节奏,并实现一个计分系统。阅读本节后,你将掌握足够多的知识,能够开始编写随玩家等级提高而加大难度以及显示得分的游戏。

一、添加Play按钮

本节将添加一个Play按钮,它在游戏开始前出现,并在游戏结束后再次出现,让玩家能够开始新游戏。

当前,这个游戏在玩家运行alien_invasion.py时就开始了。下面让游戏一开始处于非活动状态,并提示玩家单击Play按钮来开始游戏。为此,像下面这样修改GameStats类的方法__init__()

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

1. 创建Buttom类

由于Pygame没有内置创建按钮的方法,我们将编写一个Buttom类,用于创建带标签的实心矩形,你可在游戏中使用这些代码来创建任何按钮。下面是Buttom类的第一部分,请将这个类保存为文件buttom.py

首先,导入模块pygame.font,它让Pygame能够将文本渲染到屏幕上。方法__init__()接受参数self、对象ai_gamemsg,其中msg是要在按钮中显示的文本。设置按钮的尺寸,再通过设置button_color,让按钮的rect对象为亮绿色,并通过设置text_color染发文本为白色。

然后,指定使用什么字体来渲染文本。实参None让Pygame使用默认字体,而48指定了文本的字号。为让按钮在屏幕上居中,创建一个表示按钮的rect对象,并将其center属性设置为屏幕的centershuxing .

Pygame处理文本的方式是,将要显示的字符串渲染为图像。最后,调用了_prep_msg()来处理这样的渲染。

_prep_msg()的代码如下:

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

然后,让文本图像在按钮上居中:根据文本图像创建一个rect,并将其center属性设置为按钮的center属性。

最后,创建方法draw_button(),用于将这个按钮显示到屏幕上:

我们调用screen.fill()来绘制表示按钮的矩形,再调用screen.blit()并向它传递一幅图像以及与该图像相关联的rect,从而在屏幕上绘制文本图像。至此,Button类便创建好了。

2. 在屏幕上绘制按钮

我们将在AlienInvasion中使用Button类来创建一个Play按钮。首先,更新import语句:

只需要一个Play按钮,因此在AlienInvasion类的方法__init__()中创建它。可将这些代码放在方法__init__()的末尾:

这些代码创建一个标签为PlayButton实例,但没有将它显示到屏幕上。为显示该按钮,在_update_screen()对其调用方法draw_button()

为让Play按钮位于其他所有屏幕元素上面,在绘制其他所有游戏元素后再绘制这个按钮,然后切换到新屏幕。将这些代码放在一个if代码块中,让按钮仅在游戏处于非活动状态时才出现。

如果现在运行这个游戏,将在屏幕中央看到一个Play按钮,如下图所示:

3. 开始游戏

为在玩家单击Play按钮时开始新游戏,在_check_events()末尾添加如下elif代码块,以监视与该按钮相关的鼠标事件:

无论玩家单击屏幕的什么地方,Pygame都将检测到一个MOUSEBUTTONDOWN事件,但我们只想让这个游戏在玩家用鼠标单击Play按钮时做出响应。为此,使用了pygame.mouse.get_pos(),它返回一个元组,其中包含玩家单击时鼠标的x坐标和y坐标。我们将这些值传递给新方法_check_play_button()

方法_ckeck_play_button()的代码如下,将它放在_check_events()后面:

这里使用了rect的方法collidepoint()检查鼠标单击位置是否在Play按钮的rect内。如果是,就将game_active设置为True,让游戏开始!

至此,现在应该能够开始这个游戏了。游戏结束时,应将game_active设置为False,并重新显示Play按钮。

4. 重置游戏

前面编写的代码只处理了玩家第一次单击Play按钮的情况,而没有处理游戏结束的情况,因为没有重置导致游戏结束的条件。

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

首先,重置游戏统计信息,给玩家提供三艘新飞船。接下来,将game_active设置为True。这样,这个方法的代码执行完毕后,游戏就将开始。清空编组aliensbullets,然后创建一群新的外星人并将飞船居中。

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

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

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

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

标志button_clicked的值为TrueFalse。仅当玩家单击了Play按钮且游戏当前处于非活动状态时,游戏才重新开始。要测试这种行为,可开始新游戏,并不断单击Play按钮所在的区域。如果一切都想预期的那样工作,单击Play按钮所处的区域应该没有任何影响。

6. 隐藏鼠标光标

为让玩家能够开始游戏,要让鼠标光标课件,但游戏开始后,光标只会添乱。为修复这种问题,需要在游戏处于活动状态时然后光标不可见。可在方法_check_play_button()末尾的if代码块中完成这项任务:

通过向set_visible()传递False,让Pygame在光标位于游戏窗口内时将其隐藏起来。

游戏结束后,将重新显示光标,让玩家能够单击Play按钮来开始新游戏。相关的代码如下:

_ship_hit()中,在游戏进入非活动状态后,立即让光标可见。关注这样的细节让游戏显得更专业,也让玩家能够专注于玩游戏而不是去费力理解用户界面。

二、提高等级

当前,将整群外星人消灭干净后,玩家将提高一个等级,但游戏的难度没变。下面来增加一点趣味性:每当玩家将屏幕上的外星人消灭干净后,都加快游戏的节奏,让游戏玩起来更难。

1. 修改速度设置

首先重新组织Settings类,将游戏设置划分成静态和动态两组。对于随着游戏进行而变化的设置,还要确保在开始新游戏时进行重置。settings.py的方法__init__()如下:

依然在__init__()中初始化静态设置。首先,添加设置speedup_scale,用于控制游戏节奏的加快速度:2表示玩家没提高一个等级,游戏的节奏就翻一倍;1表示游戏节奏始终不变。将其设置为1.1能够将游戏节奏提高到足够快,让游戏既有难度又并非不可完成。最后,调用initialize_dynamic_settings()初始化随游戏进行而变化的属性。

initialize_dynamic_settings()的代码如下:

这个方法设置飞船、子弹和外星人的初始速度。随着游戏的进行,将提高这些速度。每当玩家开始新游戏时,都将重置这些速度。在这个方法中,还设置了fleet_direction,使得游戏刚开始时,外星人总是向右移动。不需要增大fleet_drop_speed的值,因为外星人移动的速度越快,到达屏幕底端所需的时间越短。

为在玩家的等级提高时提高飞船、子弹和外星人的速度,编写一个名为increase_speed()的新方法:

为提高这些游戏元素的速度,将每个速度都乘以speedup_scale的值。

_check_bullet_alien_collisions()中,在整群外星人都被消灭后调用increase_speed()来加快游戏的节奏:

通过修改速度设置ship_speedalien_speedbullet_speed的值,足以加快整个游戏的节奏!

2. 重置速度

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

现在,游戏《外星人入侵》玩起来更有趣,也更有挑战性了。每当玩家将屏幕上的外星人消灭干净后,游戏都将加快节奏,因此难度更大。如果游戏的难度提高得太快,可降低settings.speedup_scale的值;如果游戏的挑战性不足,可稍微提高这个设置的值。找出这个设置的最佳值,让难度的提高速度相对合理:一开始的几群外星人很容易消灭干净,接下来的几群消灭起来有一定难度,但也不是不可能,而要将之后的外星人群消灭干净几乎不可能。

三、计分

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

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

为在每次开始游戏时都重置得分,我么在reset_stats()而不是__init__()中初始化score

1. 显示得分

为在屏幕上显示得分,首先创建一个新类Scoreboard。当前,这个类只显示当前得分,但后面也将使用它来显示最高得分、等级和余下的飞船数。下面是这个类的前半部分,被保存为文件scoreboard.py

由于Scoreboard在屏幕上显示文本,首先导入模块pygame.font。接下来,在__init__()中包含形参ai_game,以便访问报告跟踪的值所需的对象settingsscreenstats。然后,设置文本颜色并实例化一个字体对象。

为将要显示的文本转换为图像,调用prep_score(),其定义如下:

prep_score()中,将数值stats_score转换为字符串,再将这个字符串传递给创建图像的render()。为在屏幕上清晰地显示得分,向render()传递屏幕背景色和文本颜色。

将得分放在屏幕右上角,并在等分增大导致数变宽时让其向左延伸。为确保得分始终锚定在屏幕右边,创建一个名为score_rectrect,让其右边缘与屏幕右边缘相距20像素,并让其上边缘与屏幕上边缘也相距20像素。

接下来,创建方法show_score(),用于显示渲染好的得分图像:

这个方法在屏幕上显示得分图像,并将其放在score_rect指定的位置。

2. 创建计分牌

为显示得分,在AlienInvasion中创建一个Scoreboard实例。先来更新import语句:

接下来,在方法__init__()中创建一个Scoreboard实例:

然后,在_update_screen()中将计分牌绘制到屏幕上:

在显示Play按钮前调用show_score()

如果现在运行这个游戏,将在屏幕右上角看到0。(当前,我们只想在进一步开发计分系统前确认得分出现在正确的地方)。下图显示了游戏开始前的得分。

下面来指定每个外星人值多少分!

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

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

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

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

有子弹击中外星人时,Pygame返回一个字典(coliisions)。我们检查这个字典是否存在,如果存在,就将得分加上一个外星人的分数。接下来,调用prep_score()来创建一幅包含最新得分的新图像。

如果现在运行这个游戏,得分将不断增加!

4. 重置得分

当前,仅在有外星人被射杀之后生成得分。这在大多数情况下可行,但从开始新游戏到有外星人被射杀之间,显示的是上一次的得分。

为修复这个问题,可在开始新游戏时生成得分:

开始新游戏时,我们重置游戏统计信息再调用prep_score(),此时生成的计分牌上显示的得分为零。

5. 将消灭的每个外星人都计入得分

当前的代码可能会遗漏一些被消灭的外星人。例如,如果在一次循环中,有两颗子弹击中了外星人,或者因子弹较宽而同时击中了多个外星人,玩家将智能得到一个外星人的分数。为修复这种问题,我们来调整检测子弹和外星人碰撞的方式。

_check_bullet_alien_collisions()中,与外星人碰撞的子弹都是字典collisions中的一个键,而与每颗子弹相关的值都是一个列表,其中包含该子弹击中的外星人。我们遍历字典collisions,确保将消灭的每个外星人都计入得分:

如果字典collisions存在,就遍历其中的所有值。别忘了,每个值都是一个列表,包含被同一颗子弹击中的所有外星人。对于每个列表,都将其包含的外星人数量乘以一个外星人的分数,并将结果加入当前得分。为测试这一点,请将子弹宽度改为300像素,并核实得到了其击中的每个外星人的分数,再将子弹宽度恢复正常值。

6. 提高分数

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

我们定义了分数的提高速度,并称之为score_scale。较低的节奏加快速度(1.1)让游戏很快变得极具挑战性,但为了让计分发生显著的变化,需要将根数的提高速度设置为更大的值(1.5)。现在,在加快游戏节奏的同时,提高了每个外星人的分数。为让分数为整数,使用了函数int()

为显示外星人的分数,在Settings的方法increase_speed()中调用函数print()

现在每当提高一个等级时,你都将在终端窗口看到新的分数值。

注意:确认分数在不断增加后,一定要删除调用函数print()的代码,否则可能影响游戏的性能,分散玩家的注意力。

7. 舍入得分

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

函数ronud()通常让小数精确到小数点后某一位,其中小数位数是都第二个实参指定的。然而,如果将第二个实参指定为负数,round()将舍入到最近的10的整数倍,如101001000等。下面是让Python将stats.score的值舍入到最近的10的整数倍,并将结果存储到rounded_score中。

接着,使用一个字符串格式设置指令,让Python将数值转换为字符串时在其中插入逗号。例如,输出为1,000,000而不是1000000。如果现在运行这个游戏,看到的得分将是10的整数倍,即便得分很高亦如此。如下图所示。

8. 最高得分

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

因为在任何情况下都不会重置最高分,所以在__init__()而不是reset_stats()中初始化high_score

下面来修改Scoreboard以显示最高得分。先来修改方法__init__()

最高得分将与当前得分分开显示,因此需要编写一个新方法prep_htgh_score,用于准备包含最高得分的图像。

方法prep_high_score()的代码如下:

将最高得分舍入到最近的10的整数倍,并添加用逗号表示的千分位分隔符。然后,根据最高得分生成一幅图像,使其水平居中,并将其top属性设置为当前得分图像的top属性。

现在,方法show_score()需要在屏幕右上角显示当前得分,并在屏幕顶部中央显示最高得分:

为检查是否诞生了新的最高得分,在Scoreboard中添加一个新方法check_high_score()

方法check_high_score()比较当前得分和最高得分。如果当前得分更高,就更新high_score的值,并调用prep_high_score()来更新包含最高得分的图像。

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

如果字典collisions存在,就根据消灭了多少外星人更新得分,再调用check_high_score()

第一次玩这个游戏时,当前得分就是最高得分,因此两个地方显示的都是当前得分。但再次开始该游戏时,最高得分会出现在中央,而当前得分则出现在右边,如下图所示。

9. 显示等级

为在游戏中显示玩家的等级,首先需要在GameStats中添加一个表示当前等级的属性。为确保每次开始新游戏时都重置等级,在reset_stats()中初始化它:

为了让Scoreboard显示当前等级,在__init__()中调用一个新方法prep_lavel()

prep_level()的代码如下:

方法prep_level()根据存储在stats.level中的值创建一幅图像,并将其right属性设置为得分的right属性。然后,将top属性设置为比得分图像的bottom属性大10像素,以便在得分和等级之间留出一定的空间。

还需要更新show_score():

新增的代码行在屏幕上显示等级图像。

我们在_check_bullet_alien_collisions()中提高等级并更新等级图像:

如果郑群外星人都被消灭,就将stats.level的值加1,并调用prep_level()确保正确地显示了新等级。

为确保在开始新游戏时更新等级图像,还需在玩家单击按钮Play时调用prep_level():

这里在调用prep_score()后立即调用prep_level()

现在可以知道到了多少级,如下图所示。

注意:在一些经典游戏中,得分带有标签,如ScoreHigh ScoreLavel。这里没有显示这些标签,游戏开始后,每个数的含义将一目了然。要包含这些标签,只需在Scoreboard中调用font.render()前,将它们添加到得分字符串中。

10. 显示余下的飞船数

最后来显示玩家还有多少艘飞船,但使用图形而不是数字。为此,在屏幕左上角绘制飞船图像来指出还余下多少艘飞船,就像众多经典的街机游戏中那样。

首先,需要让Ship继承Sprite,以便创建飞船编组:

这里导入了Sprite,让Ship继承Sprite,并在__init__()的开头调用super()

接下来,需要修改Scoreboard,以创建可供显示的飞船编组。下面是其中的import语句:

鉴于需要创建飞船编组,导入GroupShip类。

下面是方法__init__():

我们将游戏实例赋给一个属性,因为创建飞船时需要用到它。在调用prep_level()后调用了prep_ships()

prep_ships()的代码如下:

方法prep_ships()创建一个空编组self.ships,用于存储飞船实例。为填充这个编组,根据玩家还有多少艘飞船以响应的次数运行一个循环。在这个循环中,创建新飞船并设置其x坐标,让整个飞船编组都位于屏幕左边,且每艘飞船的左边距都为10像素。还将y坐标设置为例屏幕上边缘10像素,让所有飞船都出现在屏幕左上角。最后,将每艘飞船都添加到编组ships中。

现在需要在屏幕上绘制飞船了:

为在屏幕上显示飞船,对编组调用draw()。Pygame将绘制每艘飞船。未在游戏开始时让玩家知道自己有多少艘飞船,在开始新游戏时调用prep_ships()。这是在AlienInvasion_check_play_button()中进行的:

还要在飞船被外星人撞到时调用prep_ships(),从而在玩家损失飞船时更新飞船图像:

这里在将ships_left的值减1后调用prep_ships()。这样每次损失飞船后,显示的飞船数都是正确的。

下图显示的计分系统,它在屏幕左上角指出还余下多少艘飞船。

四、小结

在本篇中,学习了如何创建用于开始新游戏的Play按钮,如何检测鼠标事件,以及在游戏处于活动状态时如何隐藏鼠标光标。可以利用学到的知识在游戏中创建其他按钮,如用于显示玩法说明的Help按钮。还学习了如何随着游戏的进行调整其节奏,如何实现计分系统,以及如何以文本和非文本方式显示信息

posted @ 2022-10-26 17:39  丨君丶陌  阅读(448)  评论(0编辑  收藏  举报