Python项目外星人入侵(二):外星人

Python项目外星人入侵(二):外星人

在本章节中主要完成以下功能,首先在屏幕边缘添加一个外星人,然后尝试生成一群外星人,我们让这群外星人像两百年和下面移动,并删除被子弹击中的外星人。最后我们显示玩家拥有的飞船数量,并在玩家的飞船用完后结束游戏。在本节中,由于显示需要,将Setting类中的屏幕的大小修改为1200*700,飞船移动速度修改为1.5,子弹速度修改为1,代码其他地方不需要修改(主要是为了显示在屏幕中的外星人的数量合理)。完整代码在最下方,如果有问题可以在评论区询问,看到了会回复:

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

    def __init__(self):
        """初始化游戏设置"""
        # 屏幕设置
        self.screen_width = 1200  # 设置窗口宽度

        self.screen_height = 700  # 设置窗口高度
        self.bg_color = (230, 230, 230)  # 设置背景颜色
        self.ship_speed = 1.5  # 设置速度的初始值

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

使用Q来退出游戏

接下来我们设置退出游戏的快捷键,当我们从键盘按下Q时,游戏自动退出,为此我们需要修改函数check_keydown_events()

# game_function.py
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()

创建一个外星人

在屏幕上防止外星人与在屏幕上放置飞船类似,我们将创建一个新的Alien类,在该类中我们控制外星人的行为,为了简化程序,我们使用位图来表示外星人,我们既可以像上一章一样从网上获取外星人的图片,也可以直接使用下述图片,请务必将图片保存在alien_invasion文件夹下的images文件夹中。

首先我们创建Alien类:

# alien.py
import pygame
from pygame.sprite import Sprite


class Alien(Sprite):
    def __init__(self, screen, ai_settings):
        """初始化外星人并设置其起始位置"""
        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)

接着我们需要在主函数中将Alien类实例化:

# alien_invasion.py
import pygame  # 包含了游戏开发所需的功能
from settings import Settings
from ship import Ship
import game_function 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_length))  # 创建一个大小为800*600的窗口
    pygame.display.set_caption("外星人入侵")  # 设置屏幕名字
    ship = Ship(ai_settings, screen)  # 创建一艘飞船
    bullets = Group()  # 创建一个存储子弹的编组
    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()  # 运行程序

之后我们在game_function.py中修改屏幕更新函数,使外星人能出现在屏幕上:

# game_function.py
def update_screen(ai_settings, screen, ship, alien, bullets):
    """更新屏幕上的图像,并且切换新屏幕"""
    screen.fill(ai_settings.bg_color)  # 用指定的颜色填充屏幕
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # 将飞船显示在屏幕中
    alien.blitme()  # 让外星人显示在屏幕中
    pygame.display.flip()  # 将最近绘制的屏幕显示出来

之后我们运行主函数,就能在屏幕右上角看到一个外星人了,我们先绘制子弹和飞船是为了保证外星人再最前边。

创建一群外星人

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

要绘制一群外星人首先我们需要确定一行可以容纳多少外星人,我们需要首先计算水平间距,确定一行可以容纳多少外星人,再计算垂直间距,确定一列可以容纳多少外星人。

首先我们计算水平可用空间有多少,屏幕的宽度储存在ai_settings.screen.width中,由于我们需要在屏幕两边留下两个外星人宽度的间距,因此实际一行可用的空间为:

available_space_x = ai_settings.screen_width - 2 * alien_width

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

number_alien_x = available_space_x / (2 * alien_width)

创建一行外星人

为创建一行外星人,我们首先需要在主函数中创建一个名为aliens的空编组,用来存储全部外星人,再调用game_function.py中的创建外星人群的函数:

# alien_invasion.py 
import pygame  # 包含了游戏开发所需的功能
from settings import Settings
from ship import Ship
import game_function as gf
from pygame.sprite import Group


def run_game():
    """初始化游戏并创建一个屏幕对象"""
    pygame.init()  # 进行初始化,检测工具包是否完整
    ai_settings = Settings()
    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_length))  # 创建一个大小为800*600的窗口
    pygame.display.set_caption("外星人入侵")  # 设置屏幕名字
    ship = Ship(ai_settings, screen)  # 创建一艘飞船
    bullets = Group()  # 创建一个存储子弹的编组
    aliens = Group()   # 创建一个外星人编组
    gf.create_fleet(ai_settings, screen, aliens)

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


run_game()  # 运行程序

我们不再在alien_invasion.py中直接创建外星人,而是创建了一个空的编组,在game_function.pycreate_fleet()中来生成一群外星人,为此我们修改了gf.update_screen()中的调用,将外星人编组传给它。同时我们也需要修改该函数内部的调用:

# game_function.py
def update_screen(ai_settings, screen, ship, aliens, bullets):
    """更新屏幕上的图像,并且切换新屏幕"""
    screen.fill(ai_settings.bg_color)  # 用指定的颜色填充屏幕
    for bullet in bullets:
        bullet.draw_bullet()
    ship.blitme()  # 将飞船显示在屏幕中
    aliens.draw(screen)  # 让外星人显示在屏幕中
    pygame.display.flip()  # 将最近绘制的屏幕显示出来

调用draw()函数在每个屏幕上绘制外星人时,Python会自动绘制编组中的每个元素,绘制位置由rect决定。

之后我们就可以在game_function.py中编写新函数creat_fleet()来生成一群外星人,同时由于生成外星人需要使用外星人的基本参数,所以,我们要在这里导入Alien类:

# game_function.py
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_alien_x = int(available_space_x / (2 * alien_width))

    # 创建第一行外星人
    for alien_number in range(number_alien_x):
        # 创建一个外星人并加入当前行
        alien = Alien(ai_settings, screen)  # 创建一个外星人
        alien.x = alien_width + 2 * alien_width * alien_number  # 设定每个外星人的初始位置
        alien.rect.x = alien.x
        aliens.add(alien)   # 将外星人添加到编组中

为了放置外星人,我们需要知道他的高度和宽度,因此在计算前我们先创建了一个外星人,但是并没有放到编组中,之后获取外星人的宽度属性,计算一行可以容纳多少个外星人。

接下来我们将计算出来的数目转换为整数,因为我们不希望某个外星人只显示一部分,同时range()函数也需要一个整数,该函数返回从0开始到传入数字减1的整数列表

在循环的主体中,我们创建了一个新的外星人,通过设置x坐标将其加入当前行,之后设置好外星人之间的距离即可。

运行后我们将会看到一行外星人。

重构create_fleet()

接下来我们重构create_fleet()函数,将其功能分解为计算一行能容纳多少外星人的get_number_alien_x()函数,和创建一个外星人的函数create_alien()

# game_function.py
def create_alien(ai_settings, screen, aliens, alien_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
    aliens.add(alien)  # 将外星人添加到编组中


def create_fleet(ai_settings, screen, aliens):
    """创建外星人群"""
    alien = Alien(ai_settings, screen)  # 创建一个外星人
    number_alien_x = get_number_alien_x(ai_settings, alien.rect.width)

    # 创建第一行外星人
    for alien_number in range(number_alien_x):
        create_alien(ai_settings, screen, aliens, alien_number)

添加行

要创建外星人群,需要计算屏幕可以容纳多少行,并对创建一行外星人循环相同的次数。为了计算可以容纳的行数,我们需要计算可用的垂直空间,将屏幕高度减去飞船的高度,外星人的高度,以及最初飞船和外星人的距离。

available_space_y = ai_settings.screen_height-3 * alien_height-ship_height

这样每行的上方都会留出一定的空白区域,给玩家射杀外星人的时间。

同时在每行的下方都留出一点空白区域,为计算可容纳的行数,我们之间用空间除以外星人高度的两倍

number_rows = available_space_y / (2 * alien_height)

知道可以容纳多少行后,就可以重复执行生成一行外星人的代码:

# game_function.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_rows

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)

我们在get_number_rows()中实现了对前边计算行的两个公式的修改,这里同样使用了int()函数,避免产生不完整的外星人行,同时range()函数需要传递整数。

在最后我们使用了一个嵌套循环,内部循环用来建立一行外星人,外部循环用来从0数到创建外星人的行数。

之后我们需要在主函数中修改对create_fleet()函数的参数的修改:

gf.create_fleet(ai_settings, screen, ship, aliens)

运行之后我们就可以在屏幕上显示出多个外星人。

让外星人群移动

接下来我们让外星人向右移动,撞到屏幕边缘后下移一定距离再向左移动。我们将不断的移动所有外星人,直到所有的外星人都被消灭,有外星人撞上飞船,或有外星人抵达屏幕底端.

让外星人向右移动

接下来首先让外星人向右移动,首先我们再Settings类中设置外星人的移动速度:

# settings.py        
    	# 外星人设置
        self.alien_speed = 0.5  # 外星人的移动速度为0.5

之后我们采用这个速度来实现update()

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

借下来我们去game_function.py编写新函数update_alien()来调用该方法:

# game_function.py
def update_aliens(aliens):
    """更新外星人的位置"""
    aliens.update()

最后我们在主函数的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)

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

首先我们在Settings类中设定检测外星人碰撞屏幕的标志,以及向下移动的速度:

# settings.py        
    	# 外星人设置
        self.alien_speed = 0.5  # 外星人的移动速度为1
        self.fleet_drop_speed = 10  # 外星人向下移动的速度
        
        # fleet_direction为1时表示右移,为-1时表示左移
        self.fleet_direction = 1

这里,我们并没有用left或者right标志来标记外星人的移动方向,而是采用1和-1,因为向右移动要增大x的坐标,向左移动要降低x的坐标,这样设置计算起来会比较方便。

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

现在在alien.py模块中增加函数check_edge()来检测外星人是否移动到屏幕边缘,同时我们要修改update()函数,保证每个外星人按照正确的方向前行:

# alien.py
    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

update()方法中,将移动量设置为外星人速度和fleet_direction乘积,让外星人向左和向右平移。如果fleet_direction为1,就将外星人向左移动,如果fleet_direction为-1,就将外星人向右移动。

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

当有外星人到达屏幕边缘时需要将整群外星人下移,并改变它们的移动方向。为此我们需要修改game_function.py,并且创建函数check_fleet_edges()用来在有外星人到达屏幕边缘时采取一定的措施,编写change_fleet_direction()将外星人下移并且改变他们的方向,并在update_aliens()中进行修改;

# game_function.py

def check_fleet_edges(ai_settings, aliens):
    """有外星人到达边缘时采取相应的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():
            change_fleet_direction(ai_settings, aliens)
            # 这里使用break的原因是,调用一次会自动帮所有的编组成员下移一行
            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 update_aliens(ai_settings, aliens):
    """更新外星人的位置"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

在函数check_fleet_edges()中,我们遍历外星人群,并对其中的每个外星人调用check_edge(),如果位于边缘,就会返回True值,这时候我们知道外星人位于屏幕边缘,需要改变外星人群的方向,因此我们调用change_fleet_direction()遍历所有外星人,将每个外星人都下移ai_settings.fleet_drop_speed中设置的值,之后将标志设置位上一个标志的相反数。

最后在alien_invasion.py中,修改update_aliens(),将其中加入形参ai_settings

    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)

之后我们就能看到外星人在屏幕上来回移动,并且在抵达边缘后向下移动。

射杀外星人

我们创建了飞船和外星人群,单数子弹在击中外星人时,将会穿过外星人,为了避免这种情况我们需要检查碰撞,在游戏中,碰撞就是游戏元素重叠在一起,为了使子弹能够击落外星人,我们将使用sprite.groupcollide()检测两个成员编组的碰撞。

检测子弹和外星人碰撞

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

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

# game_function.py
def update_bullets(aliens, bullets):
    """更新子弹的位置,并且删除已经消失的子弹"""
    bullets.update()
    for bullet in bullets.copy():  # 在for循环中不应该修改列表或者编组的数目,这样会导致遍历缺失
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
            
    # 检测是否有子弹击中了外星人,如果有,就删除子弹和外星人
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)

最后一行代码遍历编组bullets中的每颗子弹,再遍历编组aliens中的每个外星人,每当有子弹和外星人重叠时,groupcollide()就在返回的字典中添加一个键-值对,两个实参告诉删除发生碰撞的子弹和外星人,由于我们调用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)

现在我们运行主函数,就能够击杀外星人了。

为测试创建大子弹

只要运行这个游戏就可以测试很多功能,但有些功能在正常情况下测试起来比较繁琐,例如要检测代码能否正确处理外星人编组为空的情况时候,需要很长时间 将所有外星人击落。我们可以通过在Settings类中修改子弹的宽度,使其更容易的击中外星人,比如我们可以将bullet_width修改为300。

生成新的外星人群

接下来我们要编写代码,来使一个外星人群被消灭后出现新的外星人群,要让外星人被消灭后出现一群新的外星人,首先要检测aliens编组是否为空,如果为空,就重新调用create_fleet()方法,我们将在update_bullets()进行检查,因为外星人是在这里被消灭的:

# update_bullets.py
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)

    # 检测是否有子弹击中了外星人,如果有,就删除子弹和外星人
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        create_fleet(ai_settings, screen, ship, aliens)

因为我们修改了update_bullets()的定义包含的形参,因此我们需要更新主函数中对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)

现在我们消灭屏幕上的外星人后就会出现一群新的外星人。

重构update_bullets()

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

# game_function.py
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)
    # print(len(bullets)) # 显示还有多少个子弹,这个用来测试,运行时把这个语句删除可以降低内存


def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
    """响应子弹和外星人的碰撞,并在瓦西那个人全部死亡后生成新的外星人群"""
    # 检测是否有子弹击中了外星人,如果有,就删除子弹和外星人
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        create_fleet(ai_settings, screen, ship, aliens)

结束游戏

如果玩家没有在足够的时间内将整群外星人消灭干净,并且有外星人撞到了飞船,飞船将会被摧毁,与此同时我们限制了玩家可用的飞船数,当外星人抵达屏幕底端,飞船也会被摧毁,玩家用光飞船后游戏就会结束。

检测外星人和飞船的碰撞

首先我们检查飞船和外星人的碰撞,我们这里在移动完每个外星人的位置后会立即检测飞船和外星人之间的碰撞:

# game_function
def update_aliens(ai_settings, ship, aliens):
    """更新外星人的位置"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    # 检测外星人和飞船的碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        print("砰")

方法spritecollideany()接受两个实参,一个物体,一个编组,它检查编组的成员是否与物体发生了碰撞,并在找到了发生碰撞的成员后就停止遍历,并输出提示信息。如果没有发生碰撞,方法就会返回None,此时if代码块就不会被执行,入股找到了碰撞的外星人,就会返回这个外星人,此时if代码块后的代码就会被执行。

响应飞船和外星人的碰撞

现在需要确定外星人和飞船碰撞时应该做些什么,我们不销毁已经有的ship实例并创建一个新的ship实例,而是通过跟踪游戏统计游戏信息来统计飞船被撞了多少次,这样有助于在第三部分统计分数。

接下来编写一个用来跟踪游戏统计信息的类,GameStates,并将其保存为文件game_states.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

在整个运行期间,我们只创建一个GameStates实例,但是每当玩家重新开始游戏时,我们需要重置一些统计信息,为此我们在reset_stats()方法中初始化大部分统计信息。

当前只有一项统计信息,ship_left,永安里统计剩下的可用的飞船数量

之后我们在Settings类中增加参数:

# settings.py

        # 飞船设置
        self.ship_speed = 1.5  # 设置速度的初始值
        self.ship_limit = 3 # 设置玩家最多的飞船数

接下来我们修改alien_invasion.py,创建一个GameStats的实例:

# alien_invasion.py
    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("外星人入侵")  # 设置屏幕名字
    stats = GameStats(ai_settings)
    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, 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)

在主循环中我们预先修改了update_aliens的参数,并且添加了实参stats,screen,ship,在有外星人撞到飞船后我们将用这些实参来跟踪玩家还有多少条飞船,以及创建新的外星人。

在有外星人撞到飞船后,我们将余下的飞船数量减一,创建一群新的外星人,并将飞船放置到底部中央(同时我们还会将游戏暂停一段时间,给玩家缓冲)

接下来把实现这些功能的代码放在ship_hit()中,并修改update_aliens()函数:

from time import sleep

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

    # 将飞船数目减一
    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,然后清空子弹和外星人编组,重新生成一群外星人,并将飞船移动回屏幕中央,之后暂停游戏,使玩家可以看到碰撞,之后将新的飞船和外星人显示到屏幕上,接下来将编写center_ship()函数,将飞船放到屏幕中央:

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

有外星人到达屏幕底端

如果有外星人到达屏幕底端,响应的方式应该和外星人撞到宇宙飞船相同,我们将添加一个新的函数,将其命名为check_aliens_bottom()

# game_function
def check_aliens_bottom(ai_settings, stats, 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, 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)

结束游戏

目前虽然检测了碰撞,但是游戏永远不会结束,知识ships_left的值会变成负数,下边在GameStats中添加一个作为标志的属性game_active,以便玩家在用完飞船后结束游戏:

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

之后在ship_hit()函数中增加代码,在玩家将所有飞船都用完后将game_active设置为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

确定游戏运行部分

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)

在任何情况下我们都需要检测键盘和鼠标事件,以及更新屏幕,剩下的只要游戏处于活跃状态运行就行,现在我们运行游戏,使用完飞船后游戏将静止不动。

回顾

接下来我们将到目前为止游戏创建的所有文件,和文件中的代码粘贴在下方:

# 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 time import sleep


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("外星人入侵")  # 设置屏幕名字
    stats = GameStats(ai_settings)
    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, 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()  # 运行程序
# game_stats.py
class GameStats:
    """跟踪游戏的统计信息"""

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

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

        # 统计游戏中剩余的飞船数目
        self.ships_left = self.ai_settings.ship_limit
# 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
class Settings:
    """存储外星人入侵的有关的所有的类"""

    def __init__(self):
        """初始化游戏设置"""
        # 屏幕设置
        self.screen_width = 1200  # 设置窗口宽度

        self.screen_height = 700  # 设置窗口高度
        self.bg_color = (230, 230, 230)  # 设置背景颜色

        # 飞船设置
        self.ship_speed = 1.5  # 设置速度的初始值
        self.ship_limit = 3  # 设置玩家最多的飞船数

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

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

        # fleet_direction为1时表示右移,为-1时表示左移
        self.fleet_direction = 1
# ship.py
import pygame


class Ship:
    def __init__(self, ai_settings,screen):
        """初始化飞船并且设置其初始位置"""
        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
# 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)

# game_function.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, 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:
        bullet.draw_bullet()
    ship.blitme()  # 将飞船显示在屏幕中
    aliens.draw(screen)  # 让外星人显示在屏幕中
    pygame.display.flip()  # 将最近绘制的屏幕显示出来


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)
    # print(len(bullets)) # 显示还有多少个子弹,这个知识测试,运行时把这个语句删除可以降低内存


def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
    """响应子弹和外星人的碰撞,并在瓦西那个人全部死亡后生成新的外星人群"""
    # 检测是否有子弹击中了外星人,如果有,就删除子弹和外星人
    collision = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        # 如果外星人被全部消灭了,就删除现有子弹,重新生成外星人
        bullets.empty()
        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 - 3 * 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, 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


def check_aliens_bottom(ai_settings, stats, 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, 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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值