我们上篇文章给大家介绍了射杀外星人功能的实现,其中主要包括检测子弹与外星人碰撞,生成新的外星人群、提高子弹的速度以及最后将update_bullets()
方法进行了重构。接下来,给大家介绍游戏结束功能的实现,首先介绍检测外星人与飞船的碰撞
功能的实现。
这里需要我们考虑一个问题:如果玩家根本不会输,这游戏还有什么趣味和挑战性可言? 如果玩家没能在足够短的时间内将整群外星人都消灭干净,且有外星人撞到飞船,飞船将被摧毁。与此同时,我们还限制了可供玩家使用的飞船数,而外星人抵达屏幕低端时,飞船也将被摧毁。与此同时,我们还限制了可供玩家使用的飞船数,而有外星人抵达屏幕低端时,飞船也将被摧毁。玩家用光了飞船后,游戏便结束。
我们首先检测外星人和飞船之间的碰撞,以便外星人撞上飞船时我们能够作出合适的响应。我们在更新每个外星人和飞船之间的碰撞:
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()
:
import sys import pygame from settings import Settings from ship import Ship import game_functions as gf from pygame.sprite import Group from alien import Alien def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alian Invasion") # 创建一艘飞船、一个子弹编组和一个外星人编组 ship = Ship(ai_settings, screen) # 创建一组一个用于存储子弹的编组 bullets = Group() aliens = Group() # 创建外星人群 # alien = Alien(ai_settings, screen) gf.create_fleet(ai_settings, screen, ship, aliens) # 开始游戏的主循环 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) run_game()
现在如果你运行这个游戏,则每当有外星人撞到飞船时,终端窗口都将显示ship hit!!!
。具体效果如下:
由于每撞一次,就打印一条相应的信息。因此,这里打印了多条信息。我们在测试这项功能的时候,请将alien_drop_speed
设置为较大的值,如50或100,这样外星人将更快地撞到飞船,我们能够更快看到这个效果。
现在需要确定外星人与飞船发生碰撞时,该做些什么呢。我们不销毁Ship
实例并创建一个新的ship
实例,而是通过跟踪游戏的统计信息来记录飞船被撞多少次(跟踪统计信息还有助于计分)。
下面来编写一个用于跟踪游戏统计信息的新类——GameStats
,并将其保存为文件game_stats.py
中,其Python文件目录机构如下:
具体实现该函数如下:
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
中:
# 飞船的设置 self.ship_speed_factor = 1.5 self.ship_limit = 3
我们还需要对slien_invasion.py
做修改,以创建一个GameStats
实例:
import sys import pygame from settings import Settings from ship import Ship import game_functions as gf from pygame.sprite import Group from alien import Alien from game_stats import GameStats def run_game(): # 初始化游戏并创建一个屏幕对象 pygame.init() ai_settings = Settings() screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption("Alian Invasion") # 創建一個用于存儲遊戲統計信息的實例 stats = GameStats(ai_settings) # 创建一艘飞船、一个子弹编组和一个外星人编组 ship = Ship(ai_settings, screen) # 创建一组一个用于存储子弹的编组 bullets = Group() aliens = Group() # 创建外星人群 # alien = Alien(ai_settings, screen) gf.create_fleet(ai_settings, screen, ship, aliens) # 开始游戏的主循环 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, stats, screen, ship, aliens, bullets) gf.update_screen(ai_settings, screen, ship, aliens, bullets) run_game()
我们导入了新类GameStats
,创建了一个名为stats
的实例,再调用update_aliens()
并添加了实参stats、screen和ship
。在有外星人撞到飞船时,我们将使用这些实参来跟踪玩家还有多少搜飞船,以及创建一群新的外星人。
有外星人撞到飞船时,我们将余下的飞船数减1,创建一群新的外星人,并将飞船重新放置到屏幕低端中央(我们还将游戏暂停一会时间,让玩家在新外星人群出现前注意发生了碰撞,并将重新创建外星人群)。
下面将实现这些功能的大部分代码放到函数ship_hit()
中:
import 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: # 创建一颗子弹,并将其加入到编组bullets中 fire_bullet(ai_settings, screen, ship, bullets) elif event.key == pygame.K_q: sys.exit() 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 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, 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) def update_screen(ai_settings, screen, ship, aliens, bullets): """更新屏幕上的图像,并切换到新屏幕""" # 每次循环时都重绘屏幕 screen.fill(ai_settings.bg_color) # 在飞船和外星人后面重绘所有子弹 for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() aliens.draw(screen) # 让最近绘制的屏幕可见 pygame.display.flip() def update_bullets(ai_settings, screen, ship, aliens, bullets): """更新子弹的位置,并删除已消失的子弹""" # 更新子弹的位置 bullets.update() # 删除已消失的子弹 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) def get_number_aliens_x(ai_settings, alien_width): avaliable_space_x = ai_settings.screen_width - 2 * alien_width number_aliens_x = int(avaliable_space_x / (2 * alien_width)) return number_aliens_x def create_alien(ai_settings, screen, aliens, alien_number, row_number): # 创建一个外星人,并计算一行可容纳多少个外星人 alien = Alien(ai_settings, screen) alien_width = alien.rect.width alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number aliens.add(alien) def create_fleet(ai_settings, screen, ship, aliens): """创建外星人群""" alien = Alien(ai_settings, screen) 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) 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_rows def check_fleet_edges(ai_settings, aliens): """有外星人到达边缘时采取相应措施""" for alien in aliens.sprites(): if alien.check_edges(): 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, 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): """检查是否有外星人位于屏幕边缘,并更新整群外星人的位置""" check_fleet_edges(ai_settings, aliens) aliens.update() # 检测外星人和飞船之间的碰撞 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()
的定义,使其包形参state、screen和bullets
,让它能够在调用ship_hit()
时传递这些值。
下面是新方法center_ship()
,我们需将其添加到ship.py
末尾:
def center_ship(self): """讓飛船在屏幕上方居中""" self.center = self.screen_rect.centerx
为让飞船居中,我们将飞船的属性center
设置为屏幕中心的x坐标,而该坐标是通过属性screen_rect
获得的。
这里需要我们注意的是:我们根本没有创建多艘飞船,在整个游戏运行期间。我们只创建了一个飞船实例,并在该飞船被撞到时将其居中。统计信息ships_left
让我们知道飞船是否用完。
如果目前运行这个游戏,射杀几个外星人,并让一个外星人撞到飞船。游戏暂停后,将出现一群新的外星人,而飞船将在屏幕低端居中。具体效果如下:
如果有外星人到达屏幕底端,我们将像有外星人撞到飞船那样做出响应。请添加一个执行这项任务的新函数,并将其命名为check_aliens_bottom()
:
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) break def update_aliens(ai_settings, stats, screen, ship, aliens, bullets): """检查是否有外星人位于屏幕边缘,并更新整群外星人的位置""" check_fleet_edges(ai_settings, aliens) aliens.update() # 检测外星人和飞船之间的碰撞 if pygame.sprite.spritecollideany(ship, aliens): ship_hit(ai_settings, stats, screen, ship, aliens, bullets) # 檢查是否有外星人到達屏幕底端 check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
函数check_aliens_bottom()
检查是否有外星人到达了屏幕底端。到达屏幕底端后,外星人的属性rect.bottom
的值大于或等于屏幕的属性rect.bottom
的值。如果有外星人到达屏幕底端,我们就调用ship_hit()
;只要检测到一个外星人到达屏幕底端,就无需检查其他外星人,因此我们在调用ship_hit()
后退出循环。
我们在更新所有外星人的位置并检测是否有外星人和飞船发生碰撞后调用check_aliens_bottom()
。现在,每当有外星人撞到飞船或抵达屏幕底端时,都将出现一群新的外星人。
现在这个游戏看起来更完整了,但它永远都不会结束,只是ship_left
不断变成更小的负数。下面来GameStates
中添加一个作为结束的属性game_active
,以便在玩家的飞船用完后结束游戏:
def __init__(self, ai_settings): """初始化統計信息""" self.ai_settings = ai_settings self.reset_stats() # 遊戲剛啟動時處於活動狀態 self.game_active = True
现在在ship_hit()
中添加代码,在玩家的飞船都用完成后将game_active
设置为False
:
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets): """響應被外星人撞到飛船""" if stats.ships_left > 0: # 將飛船ships_left減1 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
ship_hit()
的大部分代码都没变。我们将原来的所有代码都移到了一个if
语句块中,这条if语句检查玩家是否至少还有一艘飞船。如果是这样,就创建一群新的外星人,暂停一会儿,再接着往下继续执行。如果玩家没有了飞船,就将game_active
设置为False
。
在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) run_game()
在主循环中,在任何情况下都需要调用check_events()
,即便游戏处于非活动状态时亦是如此。例如,我们需要知道玩家是否按了Q键以脱出游戏,或单击关闭窗口的按钮。我们还需要不断更新屏幕,以便在等待玩家是否选择开始新游戏时能够改变屏幕。其它函数仅在游戏处于活动状态时才需要调用,因为游戏处于非活动状态,我们不用更新游戏元素的位置。
现在我们运行这个游戏时,它将在飞船用完后停止不动。具体效果如下:
我们上篇文章给大家介绍了射杀外星人功能的实现,其中主要包括检测子弹与外星人碰撞,生成新的外星人群、提高子弹的速度以及最后将update_bullets()
方法进行了重构。接下来,给大家介绍了游戏结束功能的实现,主要包括检测外星人与飞船的碰撞、响应外星人和飞船碰撞以及外星人到达屏幕底端功能的实现。最后又将游戏结束和确定游戏应运行的部分做了实现。不过本文在部分代码中只写了部分代码,因为随着项目的不断扩大,代码行数太多,为了尽可能减少每篇文章的篇幅,大家阅读的方便以及尽可能将文章阅读时间控制在20分钟左右。不过,代码的准确度一定是正确的,可以跑起来的!为了让大家更好的吸收项目所用到的知识点,我们每一篇文章只给大家实现《外星人入侵》的一个功能,所以,希望大家能够仔细阅读,认真跟着写代码,理解其中的深入含义,吧这个项目的价值发挥到最大。其实这个项目已经很典型,代码到处都是,但是,如果你只是简单的粘贴复制,对你知识的学习没有任何的价值,你还是得跟着过一遍,然后要知道每行代码的含义或者是用到了前面我们介绍的哪一块知识点,只有这样,这个项目才会发挥不一样的价值,希望大家认真学习,把基础知识打扎实一点。Python是一门注重实际操作的语言,它是众多编程语言中最简单,也是最好入门的。当你把这门语言学会了,再去学习java、go以及C语言就比较简单了。当然,Python也是一门热门语言,对于人工智能的实现有着很大的帮助,因此,值得大家花时间去学习。生命不息,奋斗不止,我们每天努力,好好学习,不断提高自己的能力,相信自己一定会学有所获。加油!!!