Python学习之路12-外星人

《Python编程:从入门到实践》笔记。
本章主要是对上一篇的继续,添加“外星人”,“外星人”与飞船的交互。

1. 回顾项目

开发较大的项目时,进入每一个开发阶段前回顾一下开发计划,搞清楚接下来要经过代码实现哪些功能相当重要。本篇将设计一下内容:python

  • 研究即有代码,肯定实现新功能前是否须要重构代码
  • 在屏幕左上角添加一个外星人,并指定合适的边距
  • 根据第一个外星人的边距和屏幕尺寸计算屏幕上可容纳多少个外星人。编写一个循环来填满屏幕的上半部分
  • 让外星舰队向两边和下方移动,直到外星人被所有击落,或有外星人撞到飞船,或有外星人抵达屏幕底部。若是全部外星人都被击落,再建立一批外星人。若是有外星人撞到飞船或到达屏幕底部,则销毁飞船并再建立一群外星人。
  • 限制玩家可用的飞机数,消耗完则游戏结束

但愿各位上一篇的代码没有删掉。在开始新的代码前,咱们先在前面的check_keydown_events()函数中添加“经过快捷键Q结束游戏”的代码:编程

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

2. 建立外星人

首先咱们须要编写一个外星人Alien类。新建alien.py模块,在其中加入以下代码:微信

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
    """表示单个外星人的类"""
    def __init__(self, ai_settings, screen):
        """初始化外星人并设置其起始位置"""
        super(Alien, self).__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)

它和Bullet类同样继承自Sprite类。如今开始建立多行外星人。ide

2.1 修改game_functions.py模块

首先在game_functions.py模块中添加create_fleet()函数用于建立外星舰队:函数

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)

而后咱们依次补充下面三个函数(注意各个函数的参数),这三个函数也位于game_functions.py中:测试

get_number_aliens_x(): 计算一行能放多少个外星人网站

def get_number_aliens_x(ai_settings, alien_width):
    """计算每行可容纳多少个外星人"""
    # 左右两侧留出一个外星人的宽度
    available_space_x = ai_settings.screen_width - 2 * alien_width
    # 列间距为一个外星人宽度
    number_aliens_x = int(available_space_x / (2 * alien_width))
    return number_aliens_x

get_number_rows(): 计算能放多少行外星人idea

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

create_alien(): 建立外星人spa

def create_alien(ai_settings, screen, aliens, alien_number, row_number):
    """建立一个外星人并将其放在当前行"""
    alien = Alien(ai_settings, screen)
    # 下面就是根据上面的公式计算每个外星人在窗口中的位置(这是左上角的坐标)
    alien.x = alien.rect.width * (1 + 2 * alien_number)
    alien.rect.x = alien.x
    alien.rect.y = alien.rect.height * (1 + 2 * row_number)
    aliens.add(alien)

如今咱们还须要修改update_screen()函数:.net

def update_screen(ai_settings, screen, ship, bullets, aliens):
    -- snip --
    # 绘制外星人,放在绘制子弹的代码后面,让外星人的图像覆盖掉子弹的图像
    aliens.draw(screen)
    -- snip --

注意,该函数增长了一个参数aliens,这是个Group对象,因此代码中的draw()方法也跟前一篇中的bullets.update()方法同样,一行代码更新全部对象。

2.2 修改alien_invasion.py模块

在主程序中添加建立外星人的代码:

def run_game():
    -- snip --
    gf.create_fleet(ai_settings, screen, ship, aliens)

    while True:
        -- snip --
        # 比以前代码多传入了一个aliens参数
        gf.update_screen(ai_settings, screen, ship, bullets, aliens)
        
-- snip --

如今咱们执行程序将会获得以下结果:

图片描述

3. 让外星舰队动起来

咱们将让外星舰队在窗体中向右移动,撞到屏幕边缘后下以必定距离降低,再沿反方向移动,直到外星人被消灭,或外星人撞上飞船,或有外星人到达窗体底部。

3.1 补充settings.py模块

class Settings:
    def __init__(self):
        -- snip --
        self.fleet_drop_speed = 10
        # 外星舰队方向标志:1向右,-1向左
        # 也能够用如left, right之类的标志,但这样会很麻烦
        self.fleet_direction = 1

3.2 修改alien.py模块

咱们须要在Alien类中添加两个方法,一个用于检测窗体边缘,一个用于更新Alien对象:

class Alien(Sprite):
    -- snip --
    def check_edges(self):
        """若是外星人位于屏幕边缘则返回True"""
        screen_rect = self.screen.get_rect()
        return self.rect.right >= screen_rect.right or self.rect.left <= 0

    def update(self):
        """向右移动外星人"""
        # 之后这样的方式会用的不少
        self.x += self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction
        self.rect.x = self.x

若是使用文本值来控制方向,那就须要添加if-else语句来检测舰队移动方向。鉴于只有两个可能的方向,这里使用-11来表示,这样更容易改变外星人对象的坐标。

3.3 修改game_functions.py模块

首先,咱们在该模块中添加一个更新外星舰队的函数update_aliens()

def update_aliens(ai_settings, aliens):
    """检查是否有外星人位于屏幕边缘,并更新外星舰队中全部外星人的位置"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update()  # “一键更新”

check_fleet_edges()函数用于检测舰队是否碰到了窗体边缘,代码以下:

def check_fleet_edges(ai_settings, aliens):
    """有外星人到达边缘时采起相应的措施"""
    # 检测舰队中每个外星人是否碰到了窗体边缘
    for alien in aliens.sprites():
        if alien.check_edges():
            change_fleet_direction(ai_settings, aliens)
            break

change_fleet_direction()函数用于改变舰队的移动方向,以及让舰队向下移动,代码以下:

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

上面三个函数就是在game_functions.py中的全部变更。

3.4 修改alien_invasion.py模块

在该模块中咱们只须要在while循环中添加一行代码:

# 开始游戏的主循环
    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, bullets, aliens)

最后运行主程序,获得以下效果:

图片描述

截了一张静态图,实际是动态的。

4. 击杀外星人

对于当前的程序,若是发射子弹,子弹将穿过外星人,而不是击杀,下面咱们继续完善该项目,使其能击杀外星人。而要实现这一点,关键就是要检测到子弹图像与外星人图像是否重叠,重叠了则表示击中。

4.1 修改game_functions.py

为什么检测子弹与卫星人的碰撞,咱们须要修改update_bullets()函数,这里咱们增长了update_bullets()的参数,还调用了一个新函数:

def update_bullets(bullets, aliens, ship, screen, ai_settings):
    -- snip --
    check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)

函数check_bullet_alien_collisions()用于检测子弹与外星人的碰撞,当外星人被消灭光时,清空现有子弹,并生成新的外星舰队,它的代码以下:

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)

sprite.groupcollide()方法用于检测对象之间的碰撞,它将bullets中的每一个子弹的rectaliens中的每一个外星人的rect进行比较,并返回一个字典。该字典以第一个参数中的对象为键,以第二个参数中的键为值,在这里,以bullets中发生了碰撞的bullet为键,它的值为与之碰撞的alien(不是aliens)!第三个参数表示是否删除第一个参数中发生了碰撞的对象,而四个参数表示是否删除第二个参数中发生了碰撞的对象。

4.2 修改alien_invasion.py

只须要修改调用update_bullets()函数的那行代码便可,增长几个参数:

gf.update_bullets(bullets, aliens, ship, screen, ai_settings)

基础功能基本完成。

4.3 测试技巧补充

对于上述代码,咱们可能须要测试当消灭完外星人后,新的舰队是否能被正确建立等,若是咱们以如今游戏的设定,即子弹速度为1,子弹宽度为3,那测试起来将会很痛苦。此时,咱们能够修改修改游戏的参数,好比将子弹宽度修改成300,子弹速度修改成3,这样就至关于对游戏进行了快进,此时代码的运行效果以下:

图片描述

不过最后记得将参数修改回去。

5. 结束游戏

接下来咱们实现外星人碰到飞船,外星人抵达窗体底部,飞船数用光致使游戏结束的代码。

5.1 建立GameStats类

首先咱们建立一个用于存储游戏信息的GameStats类,存放在game_stats.py文件中:

class GameStats:
    """跟踪游戏的统计信息"""
    def __init__(self, ai_settings):
        """初始化统计信息"""
        # 用于控制游戏启动与否
        self.game_active = True
        self.ai_settings = ai_settings
        self.reset_stats()

    def reset_stats(self):
        """初始化在游戏运行期间可能变化的统计信息"""
        # 重置飞船数
        self.ships_left = self.ai_settings.ship_limit

5.2 修改settings.py

从上述代码能够看出,咱们须要在settings.py中添加一项表示“飞船数”的信息:

class Settings:
    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        -- snip --
        # 设置飞船数量限制
        self.ship_limit = 3
        -- snip --

5.3 响应飞船与外星人的碰撞,修改game_functions.py

咱们在更新每一个外星人的位置后当即检测外星人和飞船之间的碰撞,随后再检查外星人是否到达了窗体底部。修改update_aliens()函数,使用sprite中的spritecollideany()方法来检测碰撞:将第二参数中的每个元素与第一个参数比较,检测是否碰撞,返回第二个参数中第一个发生碰撞的对象,若是没有发生碰撞则返回None:

# 增长了参数和碰撞检测
def update_aliens(ai_settings, aliens, ship, screen, bullets, stats):
    -- snip --
    # 检测外星人和飞船之间的碰撞
    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)

为此咱们须要增长两个函数:

ship_hit():当外星人与飞船发生碰撞时,调用次函数

-- snip --
from time import sleep

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    """响应被外星人撞到的飞船"""
    # 将ship_left减1
    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

从上面的代码还能够看出,咱们还须要在Ship类中添加一个center_ship()方法:

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

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

5.4 修改主程序alien_invasion.py

修改游戏的循环部分:

# 开始游戏的主循环
while True:
    gf.check_events(ai_settings, screen, ship, bullets)
    
    # 决定程序运行时该执行的部分
    if stats.game_active:
        ship.update()
        gf.update_bullets(bullets, aliens, ship, screen, ai_settings)
        gf.update_aliens(ai_settings, aliens, ship, screen, bullets, stats)

    gf.update_screen(ai_settings, screen, ship, bullets, aliens)

在主循环中,任何状况下都须要调用check_events(),即便游戏处于非活动状态;还须要不断更新屏幕,以便在等待玩家是否选择从新开始游戏时可以修改屏幕;其余函数仅在游戏处于活动状态时太须要调用。

6. 小结

本篇讲述了:

  • 如何在游戏中添加大量相同的元素;
  • 如何用嵌套循环来建立元素网格;
  • 如何控制对象在屏幕上移动的方向以及响应事件;
  • 如何检测和响应元素碰撞;
  • 如何在游戏中跟踪统计信息;
  • 如何使用标志game_active来判断游戏是否结束。

下一篇中,同时也是本项目的最后一篇,咱们将:

  • 添加一个Play按钮让玩家可以开始游戏,以及游戏结束后再开始;
  • 每当玩家消灭一群外星人后,加快游戏节奏;
  • 添加一个分数系统。


迎你们关注个人微信公众号"代码港" & 我的网站 www.vpointer.net ~

相关文章
相关标签/搜索