Python项目1 外星人入侵_外星人

在本章中,我们将在游戏《外星人入侵》中添加外星人。首先,我们在屏幕上边缘附近添加一个外星人,然后生成一群外星人。我们让这群外星人向两边和下面移 动,并删除被子弹击中的外星人。最后,我们将显示玩家拥有的飞船数量,并在玩家的飞船用完后结束游戏。

通过阅读本章,你将更深入地了解Pygame和大型项目的管理。你还将学习如何检测游戏对象之间的碰撞,如子弹和外星人之间的碰撞。检测碰撞有助于你定义游戏元 素之间的交互:可以将角色限定在迷宫墙壁之内或在两个角色之间传球。我们将时不时地查看游戏开发计划,以确保编程工作不偏离轨道。

着手编写在屏幕上添加一群外星人的代码前,先来回顾一下这个项目,并更新开发计划。

1 回顾项目

开发较大的项目时,进入每个开发阶段前回顾一下开发计划,搞清楚接下来要通过编写代码来完成哪些任务都是不错的主意。本章涉及以下内容。

1.研究既有代码,确定实现新功能前是否要进行重构。

2.在屏幕左上角添加一个外星人,并指定合适的边距。

3.根据第一个外星人的边距和屏幕尺寸计算屏幕上可容纳多少个外星人。我们将编写一个循环来创建一系列外星人,这些外星人填满了屏幕的上半部分。

4.让外星人群向两边和下方移动,直到外星人被全部击落,有外星人撞到飞船,或有外星人抵达屏幕底端。如果整群外星人都被击落,我们将再创建一群外星人。如果有外星 人撞到了飞船或抵达屏幕底端,我们将销毁飞船并再创建一群外星人。

5.限制玩家可用的飞船数量,配给的飞船用完后,游戏结束。

我们将在实现功能的同时完善这个计划,但就目前而言,该计划已足够详尽。

在给项目添加新功能前,还应审核既有代码。每进入一个新阶段,通常项目都会更复杂,因此最好对混乱或低效的代码进行清理。

我们在开发的同时一直不断地重构,因此当前需要做的清理工作不多,但每次为测试新功能而运行这个游戏时,都必须使用鼠标来关闭它,这太讨厌了。下面来添加一个结束游 戏的快捷键Q:

game_functions.py

 def check_keydown_events(event, ai_settings, screen, ship, bullets):--snip--elif event.key == pygame.K_q:sys.exit()

在check_keydown_events() 中,我们添加了一个代码块,以便在玩家按Q时结束游戏。这样的修改很安全,因为Q键离箭头键和空格键很远,玩家不小心按Q键而导致游戏 结束的可能性不大。现在测试时可按Q关闭游戏,而无需使用鼠标来关闭窗口了。

2 创建第一个外星人 创建第一个外星人

在屏幕上放置外星人与放置飞船类似。每个外星人的行为都由Alien 类控制,我们将像创建Ship 类那样创建这个类。出于简化考虑,我们也使用位图来表示外星人。你可以自 己寻找表示外星人的图像,这幅图像的背景为灰色,与屏幕背景色一致。请 务必将你选择的图像文件保存到文件夹images中。

2.1 创建Alien 类

下面来编写Alien 类:

alien.py

 import pygamefrom pygame.sprite import Spriteclass Alien(Sprite):"""表示单个外星人的类"""def __init__(self, ai_settings, screen):"""初始化外星人并设置其起始位置"""super(Alien, self).__init__()self.screen = screenself.ai_settings = ai_settings# 加载外星人图像,并设置其rect属性
self.image = pygame.image.load('images/alien.bmp')self.rect = self.image.get_rect()# 每个外星人最初都在屏幕左上角附近
❶         
self.rect.x = self.rect.widthself.rect.y = self.rect.height# 存储外星人的准确位置self.x = float(self.rect.x)def blitme(self):"""在指定位置绘制外星人"""self.screen.blit(self.image, self.rect)

除位置不同外,这个类的大部分代码都与Ship 类相似。每个外星人最初都位于屏幕左上角附近,我们将每个外星人的左边距都设置为外星人的宽度,并将上边距设置为外星人 的高度(见❶)。

2.2 创建Alien 实例

下面在alien_invasion.py中创建一个Alien 实例:

alien_invasion.py

--snip--from ship import Shipfrom alien import Alienimport game_functions as gfdef run_game():--snip--# 创建一个外星人alien = Alien(ai_settings, screen)# 开始游戏主循环while True:gf.check_events(ai_settings, screen, ship, bullets)ship.update()gf.update_bullets(bullets)gf.update_screen(ai_settings, screen, ship, alien, bullets)run_game()

在这里,我们导入了新创建的Alien 类,并在进入主while 循环前创建了一个Alien 实例。我们没有修改外星人的位置,因此该while 循环没有任何新东西,但我们修改了 对update_screen() 的调用,传递了一个外星人实例。

2.3 让外星人出现在屏幕上

为让外星人出现在屏幕上,我们在update_screen() 中调用其方法blitme() :

game_functions.py

 def update_screen(ai_settings, screen, ship, alien, bullets):--snip--# 在飞船和外星人后面重绘所有的子弹for bullet in bullets:bullet.draw_bullet()ship.blitme()alien.blitme()# 让最近绘制的屏幕可见pygame.display.flip()

我们先绘制飞船和子弹,再绘制外星人,让外星人在屏幕上位于最前面。图13-2显示了屏幕上的第一个外星人。

第一个外星人正确地现身后,下面来编写绘制一群外星人的代码。

3 创建一群外星人

要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。我们将首先计算外星人之间的水平间距,并创建一行外星人,再确定可用的垂直空间,并创建 整群外星人。

3.1 确定一行可容纳多少个外星人

为确定一行可容纳多少个外星人,我们来看看可用的水平空间有多大。屏幕宽度存储在ai_settings.screen_width 中,但需要在屏幕两边都留下一定的边距,把它设置为 外星人的宽度。由于有两个边距,因此可用于放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍:

available_space_x = ai_settings.screen_width – (2 * alien_width)

我们还需要在外星人之间留出一定的空间,即外星人宽度。因此,显示一个外星人所需的水平空间为外星人宽度的两倍:一个宽度用于放置外星人,另一个宽度为外星人右边的 空白区域。为确定一行可容纳多少个外星人,我们将可用空间除以外星人宽度的两倍:

number_aliens_x = available_space_x / (2 * alien_width)

我们将在创建外星人群时使用这些公式。

注意 令人欣慰的是,在程序中执行计算时,一开始你无需确定公式是正确的,而可以尝试直接运行程序,看看结果是否符合预期。即便是在最糟糕的情况下,也只 是屏幕上显示的外星人太多或太少。你可以根据在屏幕上看到的情况调整计算公式。

3.2 创建多行外星人

为创建一行外星人,首先在alien_invasion.py中创建一个名为aliens 的空编组,用于存储全部外星人,再调用game_functions.py中创建外星人群的函数:

alien_invasion.py

  import pygamefrom pygame.sprite import Groupfrom settings import Settingsfrom ship import Shipimport game_functions as gfdef run_game():--snip--# 创建一艘飞船、一个子弹编组和一个外星人编组ship = Ship(ai_settings, screen)bullets = Group()❶     aliens = Group()# 创建外星人群
❷     gf.create_fleet(ai_settings, screen, aliens)# 开始游戏主循环while True:--snip—❸         gf.update_screen(ai_settings, screen, ship, aliens, bullets)run_game()

由于我们不再在alien_invasion.py中直接创建外星人,因此无需在这个文件中导入Alien 类。

❶处创建了一个空编组,用于存储所有的外星人。接下来,调用稍后将编写的函数create_fleet() (见❷),并将ai_settings 、对象screen 和空编组aliens 传递给 它。然后,修改对update_screen() 的调用,让它能够访问外星人编组(见❸)。

我们还需要修改update_screen() :

game_functions.py

def update_screen(ai_settings, screen, ship, aliens, bullets):--snip--ship.blitme()aliens.draw(screen)# 让最近绘制的屏幕可见pygame.display.flip()

对编组调用draw() 时,Pygame自动绘制编组的每个元素,绘制位置由元素的属性rect 决定。在这里,aliens.draw(screen) 在屏幕上绘制编组中的每个外星人。

3.3 创建外星人群

现在可以创建外星人群了。下面是新函数create_fleet() ,我们将它放在game_functions.py的末尾。我们还需要导入Alien 类,因此务必在文件game_functions.py开头添加相应 的import 语句:

game_functions.py

  --snip--from bullet import Bulletfrom alien import Alien--snip--def create_fleet(ai_settings, screen, aliens):"""创建外星人群"""# 创建一个外星人,并计算一行可容纳多少个外星人# 外星人间距为外星人宽度
❶     alien = Alien(ai_settings, screen)❷     alien_width = alien.rect.width❸     available_space_x = ai_settings.screen_width - 2 * alien_width❹     number_aliens_x = int(available_space_x / (2 * alien_width))# 创建第一行外星人
❺     for alien_number in range(number_aliens_x):# 创建一个外星人并将其加入当前行
❻         alien = Alien(ai_settings, screen)alien.x = alien_width + 2 * alien_width * alien_numberalien.rect.x = alien.xaliens.add(alien)

这些代码大都在前面详细介绍过。为放置外星人,我们需要知道外星人的宽度和高度,因此在执行计算前,我们先创建一个外星人(见❶)。这个外星人不是外星人群的成员, 因此没有将它加入到编组aliens 中。在❷处,我们从外星人的rect 属性中获取外星人宽度,并将这个值存储到alien_width 中,以免反复访问属性rect 。在❸处,我们计 算可用于放置外星人的水平空间,以及其中可容纳多少个外星人。

相比于前面介绍的工作,这里唯一的不同是使用了int() 来确保计算得到的外星人数量为整数(见❹),因为我们不希望某个外星人只显示一部分,而且函数range() 也需要 一个整数。函数int() 将小数部分丢弃,相当于向下圆整(这大有裨益,因为我们宁愿每行都多出一点点空间,也不希望每行的外星人之间过于拥挤)。

接下来,我们编写了一个循环,它从零数到要创建的外星人数(见❺)。在这个循环的主体中,我们创建一个新的外星人,并通过设置x 坐标将其加入当前行(见❻)。将每个 外星人都往右推一个外星人的宽度。接下来,我们将外星人宽度乘以2,得到每个外星人占据的空间(其中包括其右边的空白区域),再据此计算当前外星人在当前行的位置。最 后,我们将每个新创建的外星人都添加到编组aliens 中。

如果你现在运行这个游戏,将看到第一行外星人,如图所示。

这行外星人在屏幕上稍微偏向了左边,这实际上是有好处的,因为我们将让外星人群往右移,触及屏幕边缘后稍微往下移,然后往左移,以此类推。就像经典游戏《太空入侵 者》,相比于只往下移,这种移动方式更有趣。我们将让外形人群不断这样移动,直到所有外星人都被击落或有外星人撞上飞船或抵达屏幕底端。

注意  根据你选择的屏幕宽度,在你的系统中,第一行外星人的位置可能稍有不同。

3.4 重构create_fleet()

倘若我们创建了外星人群,也许应该让create_fleet() 保持原样,但鉴于创建外星人的工作还未完成,我们稍微清理一下这个函数。下面是create_fleet() 和两个新函 数,get_number_aliens_x() 和create_alien() :

game_functions.py

❶ def get_number_aliens_x(ai_settings, alien_width):"""计算每行可容纳多少个外星人"""available_space_x = ai_settings.screen_width - 2 * alien_widthnumber_aliens_x = int(available_space_x / (2 * alien_width))return number_aliens_xdef create_alien(ai_settings, screen, aliens, alien_number):"""创建一个外星人并将其放在当前行"""alien = Alien(ai_settings, screen)❷     alien_width = alien.rect.widthalien.x = alien_width + 2 * alien_width * alien_numberalien.rect.x = alien.xaliens.add(alien)def create_fleet(ai_settings, screen, aliens):"""创建外星人群"""# 创建一个外星人,并计算每行可容纳多少个外星人alien = Alien(ai_settings, screen)❸     number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)# 创建第一行外星人for alien_number in range(number_aliens_x):❹         create_alien(ai_settings, screen, aliens, alien_number)

函数get_number_aliens_x() 的代码都来自create_fleet() ,且未做任何修改(见❶)。函数create_alien() 的代码也都来自create_fleet() ,且未做任何修 改,只是使用刚创建的外星人来获取外星人宽度(见❷)。在❸处,我们将计算可用水平空间的代码替换为对get_number_aliens_x() 的调用,并删除了引 用alien_width 的代码行,因为现在这是在create_alien() 中处理的。在❹处,我们调用create_alien() 。通过这样的重构,添加新行进而创建整群外星人将更容 易。

3.5 添加行

要创建外星人群,需要计算屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。为计算可容纳的行数,我们这样计算可用垂直空间:将屏幕高度减去第一行外星人 的上边距(外星人高度)、飞船的高度以及最初外星人群与飞船的距离(外星人高度的两倍):

available_space_y = ai_settings.screen_height – 3 * alien_height – ship_height

这将在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。

每行下方都要留出一定的空白区域,并将其设置为外星人的高度。为计算可容纳的行数,我们将可用垂直空间除以外星人高度的两倍(同样,如果这样的计算不对,我们马上就 能发现,继而将间距调整为合理的值)。

number_rows = available_height_y / (2 * alien_height)

知道可容纳多少行后,便可重复执行创建一行外星人的代码:

game_functions.py

❶ def get_number_rows(ai_settings, ship_height, alien_height):"""计算屏幕可容纳多少行外星人"""❷     available_space_y = (ai_settings.screen_height (3 * alien_height) - ship_height)number_rows = int(available_space_y / (2 * alien_height))return number_rowsdef create_alien(ai_settings, screen, aliens, alien_number, row_number):--snip--alien.x = alien_width + 2 * alien_width * alien_numberalien.rect.x = alien.x❸     alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_numberaliens.add(alien)def create_fleet(ai_settings, screen, ship, aliens):--snip--number_aliens_x = get_number_aliens_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_aliens_x):create_alien(ai_settings, screen, aliens, alien_number,row_number)

为计算屏幕可容纳多少行外星人,我们在函数get_number_rows() 中实现了前面计算available_space_y 和number_rows 的公式(见❶),这个函数 与get_number_aliens_x() 类似。计算公式用括号括起来了,这样可将代码分成两行,以遵循每行不超过79字符的建议(见❷)。这里使用了int() ,因为我们不想创建 不完整的外星人行。

为创建多行,我们使用两个嵌套在一起的循环:一个外部循环和一个内部循环(见❸)。其中的内部循环创建一行外星人,而外部循环从零数到要创建的外星人行数。Python将重 复执行创建单行外星人的代码,重复次数为number_rows 。

为嵌套循环,我们编写了一个新的for 循环,并缩进了要重复执行的代码。(在大多数文本编辑器中,缩进代码块和取消缩进都很容易,详情请参阅附录B。)我们调 用create_alien() 时,传递了一个表示行号的实参,将每行都沿屏幕依次向下放置。

create_alien() 的定义需要一个用于存储行号的形参。在create_alien() 中,我们修改外星人的y 坐标(见❹),并在第一行外星人上方留出与外星人等高的空白区 域。相邻外星人行的y 坐标相差外星人高度的两倍,因此我们将外星人高度乘以2,再乘以行号。第一行的行号为0,因此第一行的垂直位置不变,而其他行都沿屏幕依次向下放 置。

在create_fleet() 的定义中,还新增了一个用于存储ship 对象的形参,因此在alien_invasion.py中调用create_fleet() 时,需要传递实参ship :

alien_invasion.py

# 创建外星人群
gf.create_fleet(ai_settings, screen, ship, aliens)

如果你现在运行这个游戏,将看到一群外星人,如图所示。

在下一节,我们将让外星人群动起来!

动手试一试

星星 :找一幅星星图像,并在屏幕上显示一系列整齐排列的星星。

更逼真的星星:为让星星的分布更逼真,可随机地放置星星。可像下面这样来生成随机数:

from random import randint
random_number = randint(-10,10)

上述代码返回一个-10和10之间的随机整数。在为完成练习13-1而编写的程序中,随机地调整每颗星星的位置。

4 让外星人群移动

下面来让外星人群在屏幕上向右移动,撞到屏幕边缘后下移一定的距离,再沿相反的方向移动。我们将不断地移动所有的外星人,直到所有外星人都被消灭,有外星人撞上飞 船,或有外星人抵达屏幕底端。下面先来让外星人向右移动。

4.1 向右移动外星人

为移动外星人,我们将使用alien.py中的方法update() ,且对外星人群中的每个外星人都调用它。首先,添加一个控制外星人速度的设置:

settings.py

 def __init__(self):--snip--# 外星人设置self.alien_speed_factor = 1

然后,使用这个设置来实现update() :

alien.py

   def update(self):"""向右移动外星人"""❶         self.x += self.ai_settings.alien_speed_factor❷         self.rect.x = self.x

每次更新外星人位置时,都将它向右移动,移动量为alien_speed_factor 的值。我们使用属性self.x 跟踪每个外星人的准确位置,这个属性可存储小数值(见❶)。然 后,我们使用self.x 的值来更新外星人的rect 的位置(见❷)。

在主while 循环中已调用了更新飞船和子弹的方法,但现在还需更新每个外星人的位置:

alien_invasion.py

  # 开始游戏主循环while True:gf.check_events(ai_settings, screen, ship, bullets)ship.update()gf.update_bullets(bullets)gf.update_aliens(aliens)gf.update_screen(ai_settings, screen, ship, aliens, bullets)

我们在更新子弹后再更新外星人的位置,因为稍后要检查是否有子弹撞到了外星人。

最后,在文件game_functions.py末尾添加新函数update_aliens() :

game_functions.py

def update_aliens(aliens):"""更新外星人群中所有外星人的位置"""aliens.update()

我们对编组aliens 调用方法update() ,这将自动对每个外星人调用方法update() 。如果你现在运行这个游戏,会看到外星人群向右移,并逐渐在屏幕右边缘消失。

4.2 创建表示外星人移动方向的设置

下面来创建让外星人撞到屏幕右边缘后向下移动、再向左移动的设置。实现这种行为的代码如下:

settings.py

 # 外星人设置self.alien_speed_factor = 1self.fleet_drop_speed = 10# fleet_direction为1表示向右移,为-1表示向左移self.fleet_direction = 1

设置fleet_drop_speed 指定了有外星人撞到屏幕边缘时,外星人群向下移动的速度。将这个速度与水平速度分开是有好处的,这样你就可以分别调整这两种速度了。

要实现fleet_direction 设置,可以将其设置为文本值,如'left' 或'right' ,但这样就必须编写if-elif 语句来检查外星人群的移动方向。鉴于只有两个可能的方 向,我们使用值1和-1来表示它们,并在外星人群改变方向时在这两个值之间切换。另外,鉴于向右移动时需要增大每个外星人的x 坐标,而向左移动时需要减小每个外星人的x 坐标,使用数字来表示方向更合理。

4.3 检查外星人是否撞到了屏幕边缘

现在需要编写一个方法来检查是否有外星人撞到了屏幕边缘,还需修改update() ,以让每个外星人都沿正确的方向移动:

alien.py

def check_edges(self):"""如果外星人位于屏幕边缘,就返回True"""screen_rect = self.screen.get_rect()❶         if self.rect.right >= screen_rect.right:return True❷         elif self.rect.left <= 0:return Truedef update(self):"""向左或向右移动外星人"""❸         self.x += (self.ai_settings.alien_speed_factor *self.ai_settings.fleet_direction)self.rect.x = self.x

我们可对任何外星人调用新方法check_edges() ,看看它是否位于屏幕左边缘或右边缘。如果外星人的rect 的right 属性大于或等于屏幕的rect 的right 属性,就说明外 星人位于屏幕右边缘(见❶)。如果外星人的rect 的left 属性小于或等于0,就说明外星人位于屏幕左边缘(见❷)。

我们修改了方法update() ,将移动量设置为外星人速度和fleet_direction 的乘积,让外星人向左或向右移。如果fleet_direction 为1,就将外星人当前的 x 坐标增 大alien_speed_factor ,从而将外星人向右移;如果fleet_direction 为-1,就将外星人当前的 x 坐标减去alien_speed_factor ,从而将外星人向左移。

4.4 向下移动外星人群并改变移动方向

有外星人到达屏幕边缘时,需要将整群外星人下移,并改变它们的移动方向。我们需要对game_functions.py做重大修改,因为我们要在这里检查是否有外星人到达了左边缘或右边 缘。为此,我们编写函数check_fleet_edges() 和change_fleet_direction() ,并对update_aliens() 进行修改:

game_functions.py

 def check_fleet_edges(ai_settings, aliens):"""有外星人到达边缘时采取相应的措施"""❶     for alien in aliens.sprites():if alien.check_edges():change_fleet_direction(ai_settings, aliens)breakdef change_fleet_direction(ai_settings, aliens):"""将整群外星人下移,并改变它们的方向"""for alien in aliens.sprites():❷         alien.rect.y += ai_settings.fleet_drop_speedai_settings.fleet_direction *= -1def update_aliens(ai_settings, aliens):"""检查是否有外星人位于屏幕边缘,并更新整群外星人的位置"""❸     check_fleet_edges(ai_settings, aliens)aliens.update()

在check_fleet_edges() 中,我们遍历外星人群,并对其中的每个外星人调用check_edges() (见❶)。如果check_edges() 返回True ,我们就知道相应的外星人位 于屏幕边缘,需要改变外星人群的方向,因此我们调用change_fleet_direction() 并退出循环。在change_fleet_direction() 中,我们遍历所有外星人,将每个外 星人下移fleet_drop_speed 设置的值(见❷);然后,将fleet_direction 的值修改为其当前值与-1的乘积。

我们修改了函数update_aliens() ,在其中通过调用check_fleet_edges() 来确定是否有外星人位于屏幕边缘。现在,函数update_aliens() 包含形 参ai_settings ,因此我们调用它时指定了与ai_settings 对应的实参:

alien_invasion.py

# 开始游戏主循环while True:gf.check_events(ai_settings, screen, ship, bullets)ship.update()gf.update_bullets(bullets)gf.update_aliens(ai_settings, aliens)gf.update_screen(ai_settings, screen, ship, aliens, bullets)

如果你现在运行这个游戏,外星人群将在屏幕上来回移动,并在抵达屏幕边缘后向下移动。现在可以开始射杀外星人,检查是否有外星人撞到飞船,或抵达了屏幕底端。

动手试一试

雨滴 :寻找一幅雨滴图像,并创建一系列整齐排列的雨滴。让这些雨滴往下落,直到到达屏幕底端后消失。

连绵细雨:修改为完成练习而编写的代码,使得一行雨滴消失在屏幕底端后,屏幕顶端又出现一行新雨滴,并开始往下落。

5 射杀外星人

我们创建了飞船和外星人群,但子弹击中外星人时,将穿过外星人,因为我们还没有检查碰撞。在游戏编程中,碰撞指的是游戏元素重叠在一起。要让子弹能够击落外星人,我 们将使用sprite.groupcollide() 检测两个编组的成员之间的碰撞。

5.1 检测子弹与外星人的碰撞

子弹击中外星人时,我们要马上知道,以便碰撞发生后让外星人立即消失。为此,我们将在更新子弹的位置后立即检测碰撞。

方法sprite.groupcollide() 将每颗子弹的rect 同每个外星人的rect 进行比较,并返回一个字典,其中包含发生了碰撞的子弹和外星人。在这个字典中,每个键都是一 颗子弹,而相应的值都是被击中的外星人。

在函数update_bullets() 中,使用下面的代码来检查碰撞:

game_functions.py

 def update_bullets(aliens, bullets):"""更新子弹的位置,并删除已消失的子弹"""--snip--# 检查是否有子弹击中了外星人
# 如果是这样,就删除相应的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

新增的这行代码遍历编组bullets 中的每颗子弹,再遍历编组aliens 中的每个外星人。每当有子弹和外星人的rect 重叠时,groupcollide() 就在它返回的字典中添加一 个键-值对。两个实参True 告诉Pygame删除发生碰撞的子弹和外星人。(要模拟能够穿行到屏幕顶端的高能子弹——消灭它击中的每个外星人,可将第一个布尔实参设置 为False ,并让第二个布尔实参为True 。这样被击中的外星人将消失,但所有的子弹都始终有效,直到抵达屏幕顶端后消失。)

我们调用update_bullets() 时,传递了实参aliens :

alien_invasion.py

# 开始游戏主循环
while True:gf.check_events(ai_settings, screen, ship, bullets)ship.update()gf.update_bullets(aliens, bullets)gf.update_aliens(ai_settings, aliens)gf.update_screen(ai_settings, screen, ship, aliens, bullets)

如果你此时运行这个游戏,被击中的外星人将消失。如图所示,其中有一部分外星人被击落。

可以射杀外星人了

5.2 为测试创建大子弹 为测试创建大子弹

只需通过运行这个游戏就可以测试其很多功能,但有些功能在正常情况下测试起来比较烦琐。例如,要测试代码能否正确地处理外星人编组为空的情形,需要花很长时间将屏幕 上的外星人都击落。

测试有些功能时,可以修改游戏的某些设置,以便专注于游戏的特定方面。例如,可以缩小屏幕以减少需要击落的外星人数量,也可以提高子弹的速度,以便能够在单位时间内 发射大量子弹。

测试这个游戏时,我喜欢做的一项修改是增大子弹的尺寸,使其在击中外星人后依然有效,如图所示。请尝试将bullet_width 设置为300,看看将所有外星人都射杀有多 快!

类似这样的修改可提高测试效率,还可能激发出如何赋予玩家更大威力的思想火花。(完成测试后,别忘了将设置恢复正常。)

 威力更大的子弹让游戏的有些方法测试起来更容易

5.3 生成新的外星人群

这个游戏的一个重要特点是外星人无穷无尽,一个外星人群被消灭后,又会出现一群外星人。

要在外星人群被消灭后又显示一群外星人,首先需要检查编组aliens 是否为空。如果为空,就调用create_fleet() 。我们将在update_bullets() 中执行这种检查,因 为外星人都是在这里被消灭的:

game_functions.py

 def update_bullets(ai_settings, screen, ship, aliens, bullets):--snip--# 检查是否有子弹击中了外星人# 如果是,就删除相应的子弹和外星人collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)❶     if len(aliens) == 0:# 删除现有的子弹并新建一群外星人
❷         bullets.empty()create_fleet(ai_settings, screen, ship, aliens)

在❶处,我们检查编组aliens 是否为空。如果是,就使用方法empty() 删除编组中余下的所有精灵,从而删除现有的所有子弹。我们还调用了create_fleet() ,再次在屏 幕上显示一群外星人。

现在,update_bullets() 的定义包含额外的形参ai_settings 、screen 和ship ,因此我们需要更新alien_invasion.py中对update_bullets() 的调用:

alien_invasion.py

 # 开始游戏主循环while True:gf.check_events(ai_settings, screen, ship, bullets)ship.update()gf.update_bullets(ai_settings, screen, ship, aliens, bullets)gf.update_aliens(ai_settings, aliens)gf.update_screen(ai_settings, screen, ship, aliens, bullets)

现在,当前外星人群消灭干净后,将立刻出现一个新的外星人群。

5.4 提高子弹的速度

如果你现在尝试在这个游戏中射杀外星人,可能发现子弹的速度比以前慢,这是因为在每次循环中,Pygame需要做的工作更多了。为提高子弹的速度,可调整settings.py 中bullet_speed_factor 的值。例如,如果将这个值增大到3,子弹在屏幕上向上穿行的速度将变得相当快:

settings.py

 # 子弹设置self.bullet_speed_factor = 3self.bullet_width = 3--snip--

这项设置的最佳值取决于你的系统速度,请找出适合你的值吧。

5.5  重构update_bullets()

下面来重构update_bullets() ,使其不再完成那么多任务。我们将把处理子弹和外星人碰撞的代码移到一个独立的函数中:

game_functions.py

def update_bullets(ai_settings, screen, ship, aliens, bullets):--snip--# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)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()create_fleet(ai_settings, screen, ship, aliens)

我们创建了一个新函数——check_bullet_alien_collisions() ,以检测子弹和外星人之间的碰撞,以及在整群外星人都被消灭干净时采取相应的措施。这避免 了update_bullets() 太长,简化了后续的开发工作。

动手试一试

抓球 :创建一个游戏,在屏幕底端放置一个玩家可左右移动的角色。让一个球出现在屏幕顶端,且水平位置是随机的,并让这个球以固定的速度往下落。如果角 色与球发生碰撞(表示将球抓住了),就让球消失。每当角色抓住球或球因抵达屏幕底端而消失后,都创建一个新球。

6 结束游戏

如果玩家根本不会输,游戏还有什么趣味和挑战性可言?如果玩家没能在足够短的时间内将整群外星人都消灭干净,且有外星人撞到了飞船,飞船将被摧毁。与此同时,我们还 限制了可供玩家使用的飞船数,而有外星人抵达屏幕底端时,飞船也将被摧毁。玩家用光了飞船后,游戏便结束。   

6.1 检测外星人和飞船碰撞

我们首先检查外星人和飞船之间的碰撞,以便外星人撞上飞船时我们能够作出合适的响应。我们在更新每个外星人的位置后立即检测外星人和飞船之间的碰撞。

game_functions.py

 def update_aliens(ai_settings, ship, aliens):"""检查是否有外星人到达屏幕边缘然后更新所有外星人的位置"""check_fleet_edges(ai_settings, aliens)aliens.update()# 检测外星人和飞船之间的碰撞
❶     if pygame.sprite.spritecollideany(ship, aliens):❷         print("Ship hit!!!")

方法spritecollideany() 接受两个实参:一个精灵和一个编组。它检查编组是否有成员与精灵发生了碰撞,并在找到与精灵发生了碰撞的成员后就停止遍历编组。在这里, 它遍历编组aliens ,并返回它找到的第一个与飞船发生了碰撞的外星人。

如果没有发生碰撞,spritecollideany() 将返回None ,因此❶处的if 代码块不会执行。如果找到了与飞船发生碰撞的外星人,它就返回这个外星人,因此if 代码块将执 行:打印“Ship hit!!!”(见❷)。(有外星人撞到飞船时,需要执行的任务很多:需要删除余下的所有外星人和子弹,让飞船重新居中,以及创建一群新的外星人。编写完成这些任 务的代码前,需要确定检测外星人和飞船碰撞的方法是否可行。而为确定这一点,最简单的方式是编写一条print 语句。)

现在,我们需要将ship 传递给update_aliens() :

alien_invasion.py

  # 开始游戏主循环while True:gf.check_events(ai_settings, screen, ship, bullets)ship.update()gf.update_bullets(ai_settings, screen, ship, aliens, bullets)gf.update_aliens(ai_settings, ship, aliens)gf.update_screen(ai_settings, screen, ship, aliens, bullets)

现在如果你运行这个游戏,则每当有外星人撞到飞船时,终端窗口都将显示“Ship hit!!!”。测试这项功能时,请将alien_drop_speed 设置为较大的值,如50或100,这样外星人 将更快地撞到飞船。

6.2 响应外星人和飞船碰撞

现在需要确定外星人与飞船发生碰撞时,该做些什么。我们不销毁ship 实例并创建一个新的ship 实例,而是通过跟踪游戏的统计信息来记录飞船被撞了多少次(跟踪统计信息 还有助于记分)。

下面来编写一个用于跟踪游戏统计信息的新类——GameStats ,并将其保存为文件game_stats.py:

game_stats.py

  class GameStats():"""跟踪游戏的统计信息"""def __init__(self, ai_settings):"""初始化统计信息"""self.ai_settings = ai_settings❶         self.reset_stats()def reset_stats(self):"""初始化在游戏运行期间可能变化的统计信息"""self.ships_left = self.ai_settings.ship_limit

在这个游戏运行期间,我们只创建一个GameStats 实例,但每当玩家开始新游戏时,需要重置一些统计信息。为此,我们在方法reset_stats() 中初始化大部分统计信息, 而不是在__init__() 中直接初始化它们。我们在__init__() 中调用这个方法,这样创建GameStats 实例时将妥善地设置这些统计信息(见❶),同时在玩家开始新游戏 时也能调用reset_stats() 。

当前只有一项统计信息——ships_left ,其值在游戏运行期间将不断变化。一开始玩家拥有的飞船数存储在settings.py的ship_limit 中:

settings.py

  # 飞船设置self.ship_speed_factor = 1.5self.ship_limit = 3

我们还需对alien_invasion.py做些修改,以创建一个GameStats 实例:

alien_invasion.py

  --snip--from settings import Settings❶ from game_stats import GameStats--snip--def run_game():--snip--pygame.display.set_caption("Alien Invasion")# 创建一个用于存储游戏统计信息的实例
❷     stats = GameStats(ai_settings)--snip--# 开始游戏主循环while True:--snip--gf.update_bullets(ai_settings, screen, ship, aliens, bullets)❸         gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)--snip--

我们导入了新类GameStats (见❶),创建了一个名为stats 的实例(见❷),再调用update_aliens() 并添加了实参stats 、screen 和ship (见❸)。在有外星人 撞到飞船时,我们将使用这些实参来跟踪玩家还有多少艘飞船,以及创建一群新的外星人。

有外星人撞到飞船时,我们将余下的飞船数减1,创建一群新的外星人,并将飞船重新放置到屏幕底端中央(我们还将让游戏暂停一段时间,让玩家在新外星人群出现前注意到发 生了碰撞,并将重新创建外星人群)。

下面将实现这些功能的大部分代码放到函数ship_hit() 中:

game_functions.py

 import sys❶ from time import sleepimport pygame--snip--def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):"""响应被外星人撞到的飞船"""# 将ships_left减1❷     stats.ships_left -= 1# 清空外星人列表和子弹列表
❸     aliens.empty()bullets.empty()# 创建一群新的外星人,并将飞船放到屏幕底端中央
❹     create_fleet(ai_settings, screen, ship, aliens)ship.center_ship()# 暂停
❺     sleep(0.5)❻ def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):--snip--# 检测外星人和飞船碰撞if pygame.sprite.spritecollideany(ship, aliens):ship_hit(ai_settings, stats, screen, ship, aliens, bullets)

我们首先从模块time 中导入了函数sleep() ,以便使用它来让游戏暂停(见❶)。新函数ship_hit() 在飞船被外星人撞到时作出响应。在这个函数内部,将余下的飞船数 减1(见❷),然后清空编组aliens 和bullets (见❸)。

接下来,我们创建一群新的外星人,并将飞船居中(见❹),稍后将在Ship 类中添加方法center_ship() 。最后,我们更新所有元素后(但在将修改显示到屏幕前)暂停, 让玩家知道其飞船被撞到了(见❺)。屏幕将暂时停止变化,让玩家能够看到外星人撞到了飞船。函数sleep() 执行完毕后,将接着执行函数update_screen() ,将新的外 星人群绘制到屏幕上。

我们还更新了update_aliens() 的定义,使其包含形参stats 、screen 和bullets (见❻),让它能够在调用ship_hit() 时传递这些值。

下面是新方法center_ship() ,请将其添加到ship.py的末尾:

ship.py

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

为让飞船居中,我们将飞船的属性center 设置为屏幕中心的x 坐标,而该坐标是通过属性screen_rect 获得的。

注意  我们根本没有创建多艘飞船,在整个游戏运行期间,我们都只创建了一个飞船实例,并在该飞船被撞到时将其居中。统计信息ships_left 让我们知道飞船 是否用完。

请运行这个游戏,射杀几个外星人,并让一个外星人撞到飞船。游戏暂停后,将出现一群新的外星人,而飞船将在屏幕底端居中。

6.3 有外星人到达屏幕底端

如果有外星人到达屏幕底端,我们将像有外星人撞到飞船那样作出响应。请添加一个执行这项任务的新函数,并将其命名为update_aliens() :

game_functions.py

  def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):"""检查是否有外星人到达了屏幕底端"""screen_rect = screen.get_rect()for alien in aliens.sprites():❶         if alien.rect.bottom >= screen_rect.bottom:# 像飞船被撞到一样进行处理ship_hit(ai_settings, stats, screen, ship, aliens, bullets)breakdef update_aliens(ai_settings, stats, screen, ship, aliens, bullets):--snip--# 检查是否有外星人到达屏幕底端
❷     check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)

函数check_aliens_bottom() 检查是否有外星人到达了屏幕底端。到达屏幕底端后,外星人的属性rect.bottom 的值大于或等于屏幕的属性rect.bottom 的值(见 ❶)。如果有外星人到达屏幕底端,我们就调用ship_hit() ;只要检测到一个外星人到达屏幕底端,就无需检查其他外星人,因此我们在调用ship_hit() 后退出循环。

我们在更新所有外星人的位置并检测是否有外星人和飞船发生碰撞后调用check_aliens_bottom() (见❷)。现在,每当有外星人撞到飞船或抵达屏幕底端时,都将出现一 群新的外星人。

6.4 游戏结束

现在这个游戏看起来更完整了,但它永远都不会结束,只是ships_left 不断变成更小的负数。下面在GameStats 中添加一个作为标志的属性game_active ,以便在玩家的 飞船用完后结束游戏:

game_stats.py

  def __init__(self, settings):--snip--# 游戏刚启动时处于活动状态self.game_active = True

现在在ship_hit() 中添加代码,在玩家的飞船都用完后将game_active 设置为False :

game_functions.py

 def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):"""响应飞船被外星人撞到"""if stats.ships_left > 0:# 将ships_left减1stats.ships_left -= 1--snip--#暂停一会儿sleep(0.5)else:stats.game_active = False

ship_hit() 的大部分代码都没变。我们将原来的所有代码都移到了一个if 语句块中,这条if 语句检查玩家是否至少还有一艘飞船。如果是这样,就创建一群新的外星人,暂 停一会儿,再接着往下执行。如果玩家没有飞船了,就将game_active 设置为False 。

7 确定应运行游戏的哪些部分

在alien_invasion.py中,我们需要确定游戏的哪些部分在任何情况下都应运行,哪些部分仅在游戏处于活动状态时才运行:

alien_invasion.py

 # 开始游戏主循环while True:gf.check_events(ai_settings, screen, ship, bullets)if stats.game_active:ship.update()gf.update_bullets(ai_settings, screen, ship, aliens, bullets)gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)gf.update_screen(ai_settings, screen, ship, aliens, bullets)

在主循环中,在任何情况下都需要调用check_events() ,即便游戏处于非活动状态时亦如此。例如,我们需要知道玩家是否按了Q键以退出游戏,或单击关闭窗口的按钮。 我们还需要不断更新屏幕,以便在等待玩家是否选择开始新游戏时能够修改屏幕。其他的函数仅在游戏处于活动状态时才需要调用,因为游戏处于非活动状态时,我们不用更新 游戏元素的位置。

现在,你运行这个游戏时,它将在飞船用完后停止不动。

动手试一试

游戏结束 :在为完成练习,而编写的代码中,跟踪玩家有多少次未将球接着。在未接着球的次数到达三次后,结束游戏。

8 小结

在本章中,你学习了:如何在游戏中添加大量相同的元素,如创建一群外星人;如何使用嵌套循环来创建元素网格,还通过调用每个元素的方法update() 移动了大量的元素; 如何控制对象在屏幕上移动的方向,以及如何响应事件,如有外星人到达屏幕边缘;如何检测和响应子弹和外星人碰撞以及外星人和飞船碰撞;如何在游戏中跟踪统计信息,以 及如何使用标志game_active 来判断游戏是否结束了。

在与这个项目相关的最后一章中,我们将添加一个Play按钮,让玩家能够开始游戏,以及游戏结束后再玩。每当玩家消灭一群外星人后,我们都将加快游戏的节奏,并添加一个记 分系统,得到一个极具可玩性的游戏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/808325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从路由器syslog日志监控路由器流量

路由器是关键的网络基础设施组件&#xff0c;需要随时监控&#xff0c;定期监控路由器可以帮助管理员确保路由器通信正常。日常监控还可以清楚地显出通过网络的流量&#xff0c;通过分析路由器流量&#xff0c;安全管理员可及早识别可能发生的网络事件&#xff0c;从而避免停机…

算法训练营第二十三天(二叉树完结)

算法训练营第二十三天&#xff08;二叉树完结&#xff09; 669. 修剪二叉搜索树 力扣题目链接(opens new window) 题目 给定一个二叉搜索树&#xff0c;同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[L, R]中 (R>L) 。你可能需要改…

SQLite数据库在Linux系统上的使用

SQLite是一个轻量级的数据库解决方案&#xff0c;它是一个嵌入式的数据库管理系统。SQLite的特点是无需独立的服务器进程&#xff0c;可以直接嵌入到使用它的应用程序中。由于其配置简单、支持跨平台、服务器零管理&#xff0c;以及不需要复杂的设置和操作&#xff0c;SQLite非…

共享低碳未来!科士达新一代工商业储能产品引爆ESIE 2024

4月11日&#xff0c;第十二届储能国际峰会暨展览会&#xff08;ESIE 2024&#xff09;在北京首钢会展中心盛大开幕&#xff0c;科士达以“数智光储&#xff0c;共享低碳未来”为主题&#xff0c;携多款工商业储能产品及解决方案惊艳亮相本次盛会。 展会首日&#xff0c;科士达展…

C/C++基础----运算符

算数运算符 运算符 描述 例子 两个数字相加 两个变量a b得到两个变量之和 - 两个数字相减 - * 两个数字相乘 - / 两个数字相除 - % 两个数字相除后取余数 8 % 3 2 -- 一个数字递减 变量a&#xff1a;a-- 、--a 一个数字递增 变量a: a 、 a 其中递…

VSCode中调试C++程序

目录 一、准备工作&#xff1a;安装插件 1、C/C插件 ​编辑 2、CMake插件 3、CMake tool插件 二、调试过程 1、debug 2、打断点 3、调C/C文件 每次重新调试的时候都忘了具体步骤&#xff0c;直接给自己写个备忘录好了。 一、准备工作&#xff1a;安装插件 1、C/C插件…

zabbix“专家坐诊”第236期问答

问题一 Q&#xff1a;我的trap里已经可以收到信息了&#xff0c;后续要怎么创建监控项呀&#xff1f; A&#xff1a;参考&#xff1a; 问题二 Q&#xff1a;snmp和snmp trap咋搞&#xff1f; A&#xff1a;你指的是如何开启这些协议还是如何做监控项&#xff1f; Q&#xff1…

Opentelemetry——Observability Primer

Observability Primer 可观测性入门 Core observability concepts. 可观测性核心概念。 What is Observability? 什么是可观测性&#xff1f; Observability lets us understand a system from the outside, by letting us ask questions about that system without know…

Java——数组练习

目录 一.数组转字符串 二.数组拷贝 三.求数组中元素的平均值 四.查找数组中指定元素(顺序查找) 五.查找数组中指定元素(二分查找) 六.数组排序(冒泡排序) 七.数组逆序 一.数组转字符串 代码示例&#xff1a; import java.util.Arrays int[] arr {1,2,3,4,5,6}; String…

数据分析——数据规范化

数据规范化是数据分析中的一个重要步骤&#xff0c;其目的在于确保数据的一致性和可比性&#xff0c;提高数据质量和分析结果的准确性。以下是一些数据规范化的常见方法和技术&#xff1a; 数据清洗&#xff1a;此步骤主要清除数据中的重复项、空格、格式错误等&#xff0c;确…

批归一化(BN)在神经网络中的作用与原理

文章目录 1. 批归一化&#xff08;BN&#xff09;在神经网络中的作用与原理1.1 作用与优势1.2 原理与推导 2. 将BN应用于神经网络的方法2.1 训练时的BN 2. 将BN应用于神经网络的方法2.1 训练时的BN2.2 测试时的BN代码示例&#xff08;Python&#xff09;&#xff1a; 3. BN的优…

编程规范(保姆级教程)

文章目录 为什么需要编程规范&#xff1f;&#x1f4a1;代码检测工具 ESLint&#x1f4a1;代码格式化 Prettier&#x1f4a1;ESLint 与 Prettier 配合解决代码格式问题eslint支持ts约定式提交规范Commitizen助你规范化提交代码什么是 Git Hooks使用 husky commitlint 检查提交…

探索设计模式的魅力:MVVM模式在AI大模型领域的创新应用-打破传统,迎接智能未来

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MVVM模式在AI大模型领域的创新应用-打破传统迎接智能未来 &#x1f680; “在人工智能的领域里&a…

【Entity Framework】如何使用EF中的生成值

【Entity Framework】如何使用EF中的生成值 文章目录 【Entity Framework】如何使用EF中的生成值一、概述二、默认值三、计算列四、设置主键五、显示配置值生成六、设置日期/时间值生成6.1 创建时间戳6.2 更新时间戳 七、替代值生成八、无值生成九、总结 一、概述 数据库列的值…

DHCP抓包分析

DHCP动态路由配置协议&#xff0c;是C/S架构&#xff0c;由DHCP服务器为客户端动态分配IP信息。 DHCP客户端首次接入网络数据交互过程&#xff1a; 如何解决IP地址的冲突&#xff1a; ▫ DHCP服务器端&#xff1a;收到DHCP DISCOVER报文时&#xff0c;给客户端分配IP地址前会发…

【C++第三阶段】stackqueue容器

以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 stack容器queue容器 stack容器 是什么&#xff1f;功能是什么&#xff1f;常用接口是什么&#xff1f;局限性有哪些&#xff1f;优势又有哪些&#xff1f; 栈容器&#xff0c;先进…

error-ckeditor-duplicated-modules 在vue中引入ckeditor插件报错

报错原因&#xff1a; 由于集成了webpack&#xff0c;统一引入了ckeditor &#xff0c;但是页面里面我们又通过import引入了一遍&#xff0c;所以导致引入重复了 解决&#xff1a; ckeditor本身除了基本的一些功能&#xff0c;其他基本全靠插件引入&#xff0c;所以想加功能必…

谷歌推出Vids:AI驱动的PowerPoint,重新定义演示文稿的未来|TodayAI

不管你喜不喜欢&#xff0c;找到集成了AI技术以简化常见任务的工具和服务变得越来越容易。谷歌的应用套件迅速成为了一个集成了Gemini AI的产品中心&#xff0c;这些产品可以在工作场所到你的家中的任何地方使用。例如&#xff0c;如果在工作中做演示一直是你的痛点&#xff0c…

git操作码云(gitee)创建仓库到上传到远程仓库

想必有的小伙伴在为上传到码云远程仓库而感到烦恼吧&#xff01;本篇为大家详细讲解实现过程&#xff0c;跟着我的步伐一步一步来。 我就当大家已经注册好了码云 一、在码云上需要的操作 接下来我们需要使用到 git 了 二、git 上的操作 到了咋们的git了&#xff0c;开整 首…

外包干了3天,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;19年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…