资讯专栏INFORMATION COLUMN

Python学习之路12-外星人

chemzqm / 3003人阅读

摘要:现在开始创建多行外星人。小结本篇讲述了如何在游戏中添加大量相同的元素如何用嵌套循环来创建元素网格如何控制对象在屏幕上移动的方向以及响应事件如何检测和响应元素碰撞如何在游戏中跟踪统计信息如何使用标志来判断游戏是否结束。

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

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

研究即有代码,确定实现新功能前是否需要重构代码

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

根据第一个外星人的边距和屏幕尺寸计算屏幕上可容纳多少个外星人。编写一个循环来填满屏幕的上半部分

让外星舰队向两边和下方移动,直到外星人被全部击落,或有外星人撞到飞船,或有外星人抵达屏幕底部。如果所有外星人都被击落,再创建一批外星人。如果有外星人撞到飞船或到达屏幕底部,则销毁飞船并再创建一群外星人。

限制玩家可用的飞机数,消耗完则游戏结束

希望各位上一篇的代码没有删掉。在开始新的代码前,我们先在前面的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类。现在开始创建多行外星人。

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(): 计算能放多少行外星人

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(): 创建外星人

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()函数:

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 ~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41789.html

相关文章

  • Python学习之路5-字典

    摘要:本章主要介绍字典的概念,基本操作以及一些进阶操作。使用字典在中,字典是一系列键值对。中用花括号来表示字典。代码定义空字典的语法结果如果要修改字典中的值,只需通过键名访问就行。 《Python编程:从入门到实践》笔记。本章主要介绍字典的概念,基本操作以及一些进阶操作。 1. 使用字典(Dict) 在Python中,字典是一系列键值对。每个键都与一个值相关联,用键来访问值。Python中用...

    NicolasHe 评论0 收藏0
  • Python学习之路11-武装飞船

    摘要:和标志,用于表示飞船是否正在移动,用于实现飞船在不松开按键下连续移动。重写了函数,用于绘制飞船模块该模块主要是集中处理游戏中发生的各种事件。函数用于监听游戏的事件,比如,它表示游戏推出事件和分别表示键盘按下与松开事件。 《Python编程:从入门到实践》笔记。本章主要学习如何使用pygame编写一个简单的小飞机打外星人的游戏,由于本人对用python写游戏并不是特别感兴趣,所以主要是看...

    李昌杰 评论0 收藏0
  • Python学习之路13-记分

    摘要:之所以这里要添加这四行代码,其实是为了当你重新开始也就是第二次及以后点击按钮游戏时,计分板能正确显示。当第一运行游戏时,没有这四行也能正确显示计分板。 《Python编程:从入门到实践》笔记。本篇是Python小游戏《外星人入侵》的最后一篇。 1. 前言 本篇我们将结束Pygame小游戏《外星人入侵》的开发。在本篇中,我们将添加如下内容: 添加一个Play按钮,用于根据需要启动游戏以...

    tommego 评论0 收藏0
  • 云栖大会上有哪些亮点值得关注?

    摘要:年云栖大会在杭州举行,据主办方介绍本次云栖大会吸引了五万多人参会,但是在密集的会议中又有哪些亮点值得关注领导致辞很无聊每次重要的大会,都离不开一些政府要员们的参与,但是在高新技术的互联网,云计算,大数据领域真心不敢恭维,除了让整个会议前半场 2016年云栖大会在杭州举行,据主办方介绍本次云栖大会吸引了五万多人参会,但是在密集的会议中又有哪些亮点值得关注?领导致辞很无聊每次重要的大会,都离不开...

    Lavender 评论0 收藏0
  • 我家猫老喜欢和我躲猫猫,我用Python赶忙写了个猫脸检测器。在哪里都逃不出我的手心。

    摘要:前言喵星人真的是要统治世界了。完整的代码如下所示效果如下我们选择这位颜值高的喵星人代码测试效果要测试代码,只需使用您选择的工具运行它。  前言      喵星人真的是要统治世界了。?不然为什么OpenCV自带的检测器中除了人脸检测、行人检测 这些意料之中就应该存在的检测器之外,还悄悄多出了猫...

    crossoverJie 评论0 收藏0

发表评论

0条评论

chemzqm

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<