Pygame 外星人入侵(5)发射子弹

Pygame 外星人入侵(5)发射子弹

引言

在之前的博文中,我们实现了游戏屏幕的绘制、飞船的绘制以及玩家通过按键来操控飞船移动的功能。
在这篇博文中,将会完成让玩家飞船发射子弹的功能。

一、定义子弹类

我们既然要 “发射”“子弹”,那么首先就必须要有可发射的“子弹”。
所以我们需要自己定义一个子弹类,用于生成子弹对象,以及对子弹动作的维护。
大概需要以下几个步骤:
1、在设置中新增子弹相关的设置参数
2、新增 bullet 模块,维护子弹类
3、编写子弹类

1、增加子弹相关的设置参数

由于我们计划通过 Pygame 绘制的矩形来代表一颗子弹,因此需要在设置类中增加子弹矩形的长、宽、颜色参数,以及子弹飞行的速度参数。

settings.py
class Settings():
	...
	# 5、和子弹有关的设置
    # 5.1 子弹的宽度
    self.bullet_width = 3
    # 5.2 子弹的长度
    self.bullet_height = 15
    # 5.3 子弹的速度 略微慢于飞机移动的速度
    self.bullet_speed = 1
    # 5.4 子弹的颜色
    self.bullet_color = (30, 30, 30)

至此,我们计划飞船发射的子弹是一个宽3像素、长15像素的黑色矩形,且它在被发射后会以 每次 1 像素的速度在屏幕上飞行。

2、新增子弹模块

由于子弹本身需要有很多方法,因此单独开设一个模块来保存子弹类,所有的子弹代码都被保存在这个模块中。
同时,因为我们打算使用 Pygame 中 sprite 模块中的 Sprite 类 来管理子弹,所有必须提前做相应的导入。

bullet.py
from pygame.sprite import Sprite

# 子弹类继承自精灵类
class Bullet(Sprite):
	...

3、定义子弹类

我们的子弹类计划继承自精灵类,原本我不太能理解为什么要用精灵类,甚至不知道精灵类是做什么的。
我们在之前制作的元素有:
1、游戏屏幕——Surafece
2、背景——Color
3、飞船——Surface——image
但是现在又多出一个精灵类,何必多此一举?为什么不找一张子弹的图片,然后像处理飞船一样来处理子弹呢?

其实是这样的,因为我们管理的飞船和子弹其实是有区别的,那就是飞船只有一架,而子弹会同时在屏幕上出现好多颗。
当我们把“子弹”用 Surface 来实现时,在批量处理子弹时,会遇到困难。
而精灵类就是为了方便我们批量管理游戏元素而存在的。除了精灵类本身外,Pygame 中的 sprite 模块中还有一个 Group 类,是一个精灵的容器,我们可以通过 Group 对象来批量管理一堆精灵(一堆子弹)。

现在来定义子弹类,子弹类包括如下几个方法:
1、初始化子弹,初始化的位置在飞船的顶部(用以模拟飞船发射的效果)
2、子弹运动的方法
3、绘制子弹到屏幕上的方法

# 初始化子弹,即生成子弹,需要在飞船的顶部中间位置初始化出一枚子弹
    def __init__(self, screen, ship, settings):
        super().__init__()
        self.screen = screen
        # 先在(0,0)处初始化一个子弹对象,之后再将它移到正确的位置上
        self.rect = pygame.Rect(0, 0, settings.bullet_width, settings.bullet_height)
        # 然后将初始化的子弹位置移动到飞船的中间顶部
        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top
        # 需要将子弹的纵坐标保存为小数类型,这样可以接受小数速度的移动
        self.y = float(self.rect.centery)
        # 设置子弹的速度和颜色
        self.speed = settings.bullet_speed
        self.color = settings.bullet_color
# 更新子弹的位置,延垂直方向递减
def update(self, settings):
	# 保证可以接受小数值的速度
    self.y -= self.speed
    self.rect.centery = self.y

这里子弹的坐标同样通过一个浮点型临时变量来保存,方便接收浮点数的速度值。

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

至此,就有了可以发射的“子弹”了。

二、发射子弹

有了“子弹”,现在要实现如何“发射”。

1、实现发射逻辑

在玩家按下空格键时,会从飞船顶部中间位置发射出一枚子弹。因此,需要做这样几个步骤:
1、响应玩家按下空格键的事件
2、响应后生成子弹的业务
3、通过 Group 对象来保存生成的全部子弹

def check_keydown_events(event, ship, screen, settings, bullets):
	...
	# 响应玩家按下空格键的事件:
	elif event.key == K_SPACE:
		# 生成一颗新子弹
		new_bullet = Bullet(screen, ship, bullets)
		# 将这颗新子弹加入子弹队列中
		bullets.add(new_bullet)

为了管理全部子弹对象,我们需要提前在主模块中创建一个子弹队列,通过 Group 类。
同时,要修改主游戏循环中的逻辑,在更新飞船后,更新子弹队列中所有子弹的位置,然后再绘制子弹。

from pygame.sprite import Group
...
# 创建一个空的子弹队列
bulltes = Group()
...
while True:
	...
	# 更新所有子弹的位置
	bullets.update()
	# 最后绘制所有元素到屏幕上
	update_screen(my_screen, my_settings, my_ship, bullets)

这样处理,我们就还需要修改 update_screen 方法的代码:

def update_screen(screen, setting, ship, bullets):
    # 为纯黑的游戏屏幕填充上不一样的颜色
    screen.fill(setting.background_color)

    # 在背景之上绘制我们的飞船,注意这里的逻辑,必须是飞船在背景之后绘制,确保飞船在背景的上层
    ship.blitme()

    # 在屏幕和飞船之上,绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    
    # 刷新屏幕,使得元素能够不断刷新位置
    pygame.display.flip()

2、优化1:删除出界的子弹

现在运行游戏已经可以自由地发射子弹了,但是随着我们发射越来越多的子弹,游戏会变得越来越卡顿,这是为什么呢?
因为子弹一直向上飞行,直到飞出屏幕外后,它仍然在飞行,只是我们看不到罢了,子弹队列中的子弹数量也是一直在增加。
这样显然不利于游戏的运行,那么我们就要考虑将出界的子弹都删除。

我们可以在主游戏循环中更新子弹后,判断子弹的位置是不是超出游戏屏幕了,如果超出了游戏屏幕,则将这颗子弹从队列中删除。

# 更新子弹队列中的所有子弹位置
bullets.update(settings)

# 如果子弹超出了屏幕范围,那么就删除这枚子弹
for bullet in bullets.copy():  # 注意这里我们判断的是副本中的子弹,但是删除的是本体的子弹
	if bullet.rect.bottom <= 0:
		bullets.remove(bullet)

这里注意的是,我们执行判断的是队列的副本,而执行删除操作的是队列的本体。

现在当我们生成再多子弹,也不会觉得卡顿了,因为出界的子弹都会被删除。

3、优化2:限制子弹数量

很多游戏都会有限制玩家可以发射的子弹数量,旨在让玩家有目的的射击,而不是无脑发射子弹。现在我们尝试做一下这个功能。
1、在设置类中新增最大子弹数参数
2、修改响应玩家按下空格键后的业务逻辑
3、生成新子弹前,先判断当前子弹队列中的子弹数是否超过限制

设置类
	# 5.5 最大子弹数
	self.max_bullets = 3
check_keydown_events
if len(bullets) < settings.max_bullets:
	# 只有当前子弹数小于限制数时,才会发射新子弹出来
	new_bullet = Bullet(screen, ship, settings)
	bullets.add(new_bullet)

这样就可以实现,当我们屏幕上已有 3 颗子弹时,按空格键也不会发射新子弹的效果。

三、代码封装

在优化1 和 优化2 两个部分,我们对更新子弹和发射子弹两部分的代码进行了扩充,这样显得有些冗长,本着这本书一贯的作风,我们需要对这两部分的代码进行封装
1、封装 更新子弹、删除出界子弹 的代码
2、封装 玩家按下空格键后的业务代码

def update_screen(screen, setting, ship, bullets):
    # 为纯黑的游戏屏幕填充上不一样的颜色
    screen.fill(setting.background_color)

    # 在背景之上绘制我们的飞船,注意这里的逻辑,必须是飞船在背景之后绘制,确保飞船在背景的上层
    ship.blitme()

    # 在屏幕和飞船之上,绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
        
    # 刷新屏幕,使得元素能够不断刷新位置
    pygame.display.flip()
# 发射子弹的方法
def fire_bullet(bullets, settings, screen, ship):
    # 发射前检查当前子弹数是否超过最大限制的数量
    if len(bullets) < settings.max_bullets:
        # 只有当前子弹数小于限制数时,才会发射新子弹出来
        new_bullet = Bullet(screen, ship, settings)
        bullets.add(new_bullet)

如此一来,我们原本相应位置的代码就可以得到有效减少。

四、小结

在这篇博文的小结中,先说一下 Bullet 类 update 方法的理解。

我们在子弹类中编写了一个修改子弹位置的方法 update,之所以要用这个函数名,是因为 Pygame 的精灵类本身就有一个方法叫做 update ,只是这个方法是一个空函数,里面什么功能都没有,那么这个函数到底有什么意义呢?

其实是因为,当我们把一系列精灵对象放入一个 Group 对象的队列中后,可以直接对这个队列对象调用 update 方法,这样一来,会自动执行队列中所有精灵对象的 update 方法。实现了方便精灵类批量管理的功能。而方便批量管理,也是我们在开头就叙述过的,这里只是阐述一下具体是如何实现的。

所以我们在更新子弹位置时,写的是这样的代码:

bullets.update()

这样,队列中的每一个子弹,都会执行 update 方法。
精灵类中原生的 update 虽然没有任何功能,但是却可以实现个体和群体之间的联系,因此我们改写 update 函数体,以实现批量移动子弹。
这种做法有点类似于我们继承某个类时,改写构造函数的做法。

至此,我们实现了飞船发射子弹的功能,接下来,就要尝试生成入侵的外星人,以及如何通过子弹来击杀外星人了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值