Pytho练手项目一《外星人入侵4》

版权声明:敲一敲看一看,记得评论告诉我错误 https://blog.csdn.net/idealhunting/article/details/88389536

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)

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

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: #1

  # 删除现有的子弹并新建一群外星人
  bullets.empty()      #2
  create_fleet(ai_settings, screen, ship, aliens)     

        在1处,我们检查编组 aliens 是否为空。如果是,就使用方法 empty() 删除编组中余下的所有精灵,从而删除现有的所有子弹。我们还调用了 create_fleet() ,再次在屏幕上显示一群外星人。现在, update_bullets() 的定义包含额外的形参 ai_settings 、 screen 和 ship ,因此我们需要
更新alien_invasion.py中对 update_bullets() 的调用:

 #开始游戏的主循环
    while True:
        gf.check_events(ai_settings, screen, ship, aliens, bullets)        
        ship.update()                                              
        #删除已消失的子弹                                             
        gf.update_bullets(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 = 3
    self.bullet_width = 3
    -- snip --


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

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

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)

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

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

game_functions.py

#检测外星人和飞船之间的碰撞
    if pygame.sprite.spritecollideany(ship,aliens):   #1
        print("Ship hit!!!")      

        方法 spritecollideany() 接受两个实参:一个精灵和一个编组。它检查编组是否有成员与精灵发生了碰撞,并在找到与精灵发生了碰撞的成员后就停止遍历编组。在这里,它遍历编组aliens ,并返回它找到的第一个与飞船发生了碰撞的外星人。如果没有发生碰撞, spritecollideany() 将返回 None ,因此Ø处的 if 代码块不会执行。如果找到了与飞船发生碰撞的外星人,它就返回这个外星人,因此 if 代码块将执行:打印“Ship hit!!!”
(见)。(有外星人撞到飞船时,需要执行的任务很多:需要删除余下的所有外星人和子弹,让飞船重新居中,以及创建一群新的外星人。编写完成这些任务的代码前,需要确定检测外星人和飞船碰撞的方法是否可行。而为确定这一点,最简单的方式是编写一条 print 语句。)现在,我们需要将 ship 传递给 update_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)      

        现在如果你运行这个游戏,则每当有外星人撞到飞船时,终端窗口都将显示“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()        #1

    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.5
   self.ship_limit = 3


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

-- snip --
from settings import Settings
from game_stats import GameStats  #1
-- snip --
def run_game():
    -- snip --
    pygame.display.set_caption("Alien Invasion")


    # 创建一个用于存储游戏统计信息的实例
    stats = GameStats(ai_settings)        #2
    -- snip --
    # 开始游戏主循环
    while True:
        -- snip --
        gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
        gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets) #3
        -- snip --

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

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

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

import sys
from time import sleep #1
import pygame
-- snip --

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    """响应被外星人撞到的飞船"""
    # 将ships_left减1
    stats.ships_left -= 1     #2
    # 清空外星人列表和子弹列表
    aliens.empty()          #3
    bullets.empty()
    # 创建一群新的外星人,并将飞船放到屏幕底端中央
    create_fleet(ai_settings, screen, ship, aliens)  #4
    ship.center_ship()
1
# 暂停
sleep(0.5)                                     #5

def update_aliens(ai_settings, stats, screen, ship, aliens, bullets): #6
-- snip --
# 检测外星人和飞船碰撞
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)

们首先从模块 time 中导入了函数 sleep() ,以便使用它来让游戏暂停(见1)。新函数ship_hit() 在飞船被外星人撞到时作出响应。在这个函数内部,将余下的飞船数减1(见2),然后清空编组 aliens 和 bullets (见3)。接下来,我们创建一群新的外星人,并将飞船居中(见4),稍后将在 Ship 类中添加方法center_ship() 。最后,我们更新所有元素后(但在将修改显示到屏幕前)暂停,让玩家知道其飞船被撞到了(见5)。屏幕将暂时停止变化,让玩家能够看到外星人撞到了飞船。函数 sleep()执行完毕后,将接着执行函数 update_screen() ,将新的外星人群绘制到屏幕上。我们还更新了 update_aliens() 的定义,使其包含形参 stats 、 screen 和 bullets (见6),让它能够在调用 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: #1
        # 像飞船被撞到一样进行处理
        ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
        break

def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
    -- snip --
    # 检查是否有外星人到达屏幕底端
    check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets) #2

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

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

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.botom:      #1
            #像飞船被撞到一样处理
            ship_hit(ai_settings,stats,screen,ship,aliens,bullets)
            break

def update_aliens(ai_settings,stats,screen,ship, aliens,bullets):
    """
    检查是否有外星人位于屏幕边缘,并更新整群外星人的位置
    """
    check_aliens_bottom(ai_settings,stats,screen,ship,aliens,bullets) #2
    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() 检查是否有外星人到达了屏幕底端。到达屏幕底端后,外星人的属性 rect.bottom 的值大于或等于屏幕的属性 rect.bottom 的值(见1)。如果有外星人到达屏幕底端,我们就调用 ship_hit() ;只要检测到一个外星人到达屏幕底端,就无需检查其他外星人,因
此我们在调用 ship_hit() 后退出循环。

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

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

game_stats.py

def __init__(self, settings):
-- snip --
# 游戏刚启动时处于活动状态
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 。

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按钮,让玩家能够开始游戏,以及游戏结束后再玩。每当玩家消灭一群外星人后,我们都将加快游戏的节奏,并添加一个记分系统,得到一个极具可玩性的游戏!

补充:内容来源 《python编程:从入门到实践》 

本文只做学习记录使用,其中部分代码出现纰漏,可参照原文或源代码 

这里附上相关资源(包括原文,原代码,及本人最后修订代码):https://download.csdn.net/download/idealhunting/11012497

猜你喜欢

转载自blog.csdn.net/idealhunting/article/details/88389536