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

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

8.射击

         下面来添加射击功能。我们将编写玩家按空格键时发射子弹(小矩形)的代码。子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。

8.1 添加子弹设置

        首先,更新settings.py,在其方法 __init__() 末尾存储新类 Bullet 所需的值:

def __init__(self):
-- snip --
   # 子弹设置
       self.bullet_speed_factor = 1
       self.bullet_width = 3
       self.bullet_height = 15
       self.bullet_color = 60, 60, 60

8.2 创建Bullet类

        下面来创建存储 Bullet 类的文件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,  #1
            ai_settings.bullet_height)
        self.rect.centerx = ship.rect.centerx                    #2
        self.rect.top = ship.rect.top                            #3
        #存储用小数表示的子弹位置 
        self.y = float(self.rect.y)                              #4
        self.color = ai_settings.bullet_color                    #5
        self.speed_factor = ai_settings.bullet_speed_factor

        Bullet 类继承了我们从模块 pygame.sprite 中导入的 Sprite 类。通过使用精灵,可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,需要向 __init__() 传递ai_settings 、 screen 和 ship 实例,还调用了 super() 来继承 Sprite 。

注意 代码 super(Bullet, self).__init__() 使用了Python 2.7语法。这种语法也适用于Python 3,
但你也可以将这行代码简写为 super().__init__() 。

         在1处,我们创建了子弹的属性 rect 。子弹并非基于图像的,因此我们必须使用 pygame.Rect()类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的x坐标和y坐标,还有矩形的宽度和高度。我们在(0, 0)处创建这个矩形,但接下来的两行代码将其移到了正确的位置,因为子弹的初始位置取决于飞船当前的位置。子弹的宽度和高度是从 ai_settings 中获取的。在2处,我们将子弹的 centerx 设置为飞船的 rect.centerx 。子弹应从飞船顶部射出,因此我们将表示子弹的 rect 的 top 属性设置为飞船的 rect 的 top 属性,让子弹看起来像是从飞船中射出的(见3)。我们将子弹的y坐标存储为小数值,以便能够微调子弹的速度(见4)。在5处,我们将子弹的颜色和速度设置分别存储到 self.color 和 self.speed_factor 中。下面是bullet.py的第二部分——方法 update() 和 draw_bullet() :

    def update(self):
        """向上移动子弹"""
        # 更新表示子弹位置的小数值
        self.y -= self.speed_factor  #1
        # 更新表示子弹的rect的位置
        self.rect.y = self.y        #2

    def draw_bullet(self):
        """在屏幕上绘制子弹"""
        pygame.draw.rect(self.screen, self.color, self.rect)   #3

        方法 update() 管理子弹的位置。发射出去后,子弹在屏幕中向上移动,这意味着y坐标将不断减小,因此为更新子弹的位置,我们从 self.y 中减去 self.speed_factor 的值(见1)。接下来,我们将 self.rect.y 设置为 self.y 的值(见2)。属性 speed_factor 让我们能够随着游戏的进行或根
据需要提高子弹的速度,以调整游戏的行为。子弹发射后,其x坐标始终不变,因此子弹将沿直线垂直地往上穿行

        需要绘制子弹时,我们调用 draw_bullet() 。函数 draw.rect() 使用存储在 self.color 中的颜色
填充表示子弹的 rect 占据的屏幕部分(见3)。

8.3  将子弹存储到编组中
        定义 Bullet 类和必要的设置后,就可以编写代码了,在玩家每次按空格键时都射出一发子弹。首先,我们将在alien_invasion.py中创建一个编组(group),用于存储所有有效的子弹,以便能够管理发射出去的所有子弹。这个编组将是 pygame.sprite.Group 类的一个实例; pygame.sprite.Group 类类似于列表,但提供了有助于开发游戏的额外功能。在主循环中,我们将使用这个编组
在屏幕上绘制子弹,以及更新每颗子弹的位置:

#alien_invasion.py
import pygame
from  pygame.sprite import Group

from settings import  Settings
from ship import Ship
import game_functions as gf

def run_game():
    #初始化pygame、设置和屏幕对象
    pygame.init() #1
    ai_settings = Settings()
    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Alien Invasion")

    #创建一艘飞船
    ship = Ship(ai_settings,screen)             
    bullets = Group()         #1

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

run_game()

        我们导入了 pygame.sprite 中的 Group 类。在1处,我们创建了一个 Group 实例,并将其命名为
bullets 。这个编组是在 while 循环外面创建的,这样就无需每次运行该循环时都创建一个新的子
弹编组。

注意 如果在循环内部创建这样的编组,游戏运行时将创建数千个子弹编组,导致游戏慢得像
蜗牛。如果游戏停滞不前,请仔细查看主 while 循环中发生的情况。

        我们将 bullets 传递给了 check_events() 和 update_screen() 。在 check_events() 中,需要在玩家按空格键时处理 bullets ;而在 update_screen() 中,需要更新要绘制到屏幕上的 bullets 。

        当你对编组调用 update() 时,编组将自动对其中的每个精灵调用 update() ,因此代码行bullets.update() 将为编组 bullets 中的每颗子弹调用 bullet.update() 。

8.4 开火

         在game_functions.py中,我们需要修改 check_keydown_events() ,以便在玩家按空格键时发射一颗子弹。我们无需修改 check_keyup_events() ,因为玩家松开空格键时什么都不会发生。我们还 需 修 改 update_screen() , 确 保 在 调用 flip() 前 在 屏 幕 上 重绘 每 颗 子 弹。 下 面 是 对 game_functions.py 所做的相关修改:

#game_functions.py
import sys
import pygame

from bullet import  Bullet

def check_keydown_events(event, ai_settings,screen,ship,bullets):  #1
    """响应按键"""
    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:        #2
        #创建一颗子弹,并将其加入到编组bullets中
        new_bullet = Bullet(ai_settings,screen,ship)
        bullets.add(new_bullet)

def check_keyup_events(event, ship):    #3
    """响应松开"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False

def check_events(event,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(event,ai_settings,screen,ship,bullets): #4
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    #在飞船和外星人后面重绘所有子弹
    for bullet in bullets.sprites():           #5
        bullet.draw_bullet()
    ship.blitme()

    # 让最近绘制的屏幕可见
    pygame.display.flip()

编组 bulltes 传递给了 check_keydown_events() (见1)。玩家按空格键时,创建一颗新子弹(一个名为 new_bullet 的 Bullet 实例),并使用方法 add() 将其加入到编组 bullets 中(见2);代码bullets.add(new_bullet) 将新子弹存储到编组 bullets 中。在 check_events() 的定义中,我们需要添加形参 bullets (见3);调用 check_keydown_events()时,我们也需要将 bullets 作为实参传递给它。在4 处 , 我 们 给 在 屏 幕 上 绘 制 子 弹 的 update_screen() 添 加 了 形 参 bullets 。 方 法bullets.sprites() 返回一个列表,其中包含编组 bullets 中的所有精灵。为在屏幕上绘制发射的所有子弹,我们遍历编组 bullets 中的精灵,并对每个精灵都调用 draw_bullet() (见5)。如果此时运行 alien_invasion.py ,将能够左右移动飞船,并发射任意数量的子弹。子弹在屏幕上向上穿行,抵达屏幕顶部后消失,如图所示。可在settings.py中修改子弹的尺寸、颜色和速度。

8.5 删除已消失的子弹

         当前,子弹抵达屏幕顶端后消失,这仅仅是因为Pygame无法在屏幕外面绘制它们。这些子弹实际上依然存在,它们的y坐标为负数,且越来越小。这是个问题,因为它们将继续消耗内存和处理能力。我们需要将这些已消失的子弹删除,否则游戏所做的无谓工作将越来越多,进而变得越来越慢。为此,我们需要检测这样的条件,即表示子弹的 rect 的 bottom 属性为零,它表明子弹已穿过屏幕顶端:
alien_invasion.py

 bullets.update()
        #删除已消失的子弹  
        for bullet in bullets.copy():          #1
            if bullet.rect.bottom <= 0:        #2
                bullets.remove(bullet)         #3
        print(len(bullets))                    #4
        
        gf.update_screen(ai_settings, screen, ship, bullets)

在 for 循环中,不应从列表或编组中删除条目,因此必须遍历编组的副本。我们使用了方法copy() 来设置 for 循环(见1)这让我们能够在循环中修改 bullets 。我们检查每颗子弹,看看它是否已从屏幕顶端消失(见2)。如果是这样,就将其从 bullets 中删除(见3)。在4处,我们使用了一条 print 语句,以显示当前还有多少颗子弹,从而核实已消失的子弹确实删除了。如果这些代码没有问题,我们发射子弹后查看终端窗口时,将发现随着子弹一颗颗地在屏幕顶端消失,子弹数将逐渐降为零。运行这个游戏并确认子弹已被删除后,将这条 print 语句删除。如果你留下这条语句,游戏的速度将大大降低,因为将输出写入到终端而花费的时间比将图形绘
制到游戏窗口花费的时间还多。

8.6 限制子弹的数量

        很多射击游戏都对可同时出现在屏幕上的子弹数量进行限制,以鼓励玩家有目标地射击。下面在游戏《外星人入侵》中作这样的限制。首先,在settings.py中存储所允许的最大子弹数:

settings.py

# 子弹设置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60

        这将未消失的子弹数限制为3颗。在game_functions.py的 check_keydown_events() 中,我们在创建新子弹前检查未消失的子弹数是否小于该设置:

game_functions.py

  #创建一颗子弹,并将其加入到编组bullets中
        if len(bullets) < ai_settings.bullets_allowed:
            new_bullet = Bullet(ai_settings,screen,ship)
            bullets.add(new_bullet)

        玩家按空格键时,我们检查 bullets 的长度。如果 len(bullets) 小于3,我们就创建一个新子弹;但如果已有3颗未消失的子弹,则玩家按空格键时什么都不会发生。如果你现在运行这个游戏,屏幕上最多只能有3颗子弹。

8.7 创建函数update_bullets()

         编 写 并 检 查 子 弹 管 理 代 码 后 , 可 将 其 移 到 模 块 game_functions 中 , 以 让 主 程 序 文 件alien_invasion.py 尽可能简单。我们创建一个名为 update_bullets() 的新函数,并将其添加到game_functions.py 的末尾:

def update_bullets(bullets):
    """更新子弹的位置,并删除已消失的子弹"""
    # 更新子弹的位置
    bullets.update()
    
    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

        update_bullets() 的代码是从 alien_invasion.py 剪切并粘贴而来的,它只需要一个参数,即编组 bullets 。alien_invasion.py中的 while 循环又变得很简单了:alien_invasion.py

   #开始游戏的主循环
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)        #1
        ship.update()                                              #2                             
        #删除已消失的子弹                                             #3
        gf.update_bullets(bullets)                 
        gf.update_screen(ai_settings, screen, ship, bullets)       #4

run_game()

        我们让主循环包含尽可能少的代码,这样只要看函数名就能迅速知道游戏中发生的情况。主循环检查玩家的输入(见1),然后更新飞船的位置(见2)和所有未消失的子弹的位置(见3)。接下来,我们使用更新后的位置来绘制新屏幕(见4)。

8.8 创建函数 fire_bullet()
        下面将发射子弹的代码移到一个独立的函数中,这样,在 check_keydown_events() 中只需使用一行代码来发射子弹,让 elif 代码块变得非常简单:

game_functions.py

def check_keydown_events(event, ai_settings,screen,ship,bullets):  #1
    """响应按键"""
    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:        #2
        #创建一颗子弹,并将其加入到编组bullets中
        fire_bullet(ai_settings, screen, ship, bullets)

def fire_bullet(ai_settings, screen, ship, bullets):
    """如果还没有到达限制,就发射一颗子弹"""
    # 创建新子弹,并将其加入到编组bullets中
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

        函数 fire_bullet() 只包含玩家按空格键时用于发射子弹的代码;在 check_keydown_events()中,我们在玩家按空格键时调用 fire_bullet() 。请再次运行 alien_invasion.py ,确认发射子弹时依然没有错误。

9 小结

  在本章中,你学习了:游戏开发计划的制定;使用Pygame编写的游戏的基本结构;如何设置背景色,以及如何将设置存储在可供游戏的各个部分访问的独立类中;如何在屏幕上绘制图像,以及如何让玩家控制游戏元素的移动;如何创建自动移动的元素,如在屏幕中向上飞驰的子弹,
以及如何删除不再需要的对象;如何定期重构项目的代码,为后续开发提供便利。

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

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

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

猜你喜欢

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