前言
??上篇文章,我们给大家介绍了飞船的左右移动,以及配置相应的飞船速度以及相应的限制飞船的运动范围。最后将方法check_events 方法进行了重构;并且对最近的几个文件进行了简要的总结。本文给大家介绍武装飞船的最后一个部分——飞船子弹射击的部分。
射击
??接下来,我们添加射击功能。我们将编写玩家按空格键时发射子弹的代码。子弹将在屏幕中向上空行,抵达屏幕上边缘后消失。
1、添加子弹设置
??首先,更新settings.py ,在其方法__init__() 末尾存储新类Bullet所需的值:
class Settings():
"""存储《外星人入侵》的所有设置类"""
def __init__(self):
"""初始化游戏的设置"""
self.screen_width = 900
self.screen_height = 600
self.bg_color = (230, 230, 230)
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
??这些设置创建3像素、高15像素的深灰色子弹。子弹的速度比飞船稍低。
2、创建Bullet类
??下面创建存储Bullet类的文件bullet.py,具体如下:
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""一个对飞船发射的子弹进行管理的类"""
def __init__(self, ai_settings, screen, ship):
"""在飞船所处的位置创建一个子弹对象"""
super().__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
def update(self):
"""向上移动子弹"""
self.y -= self.speed_factor
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子弹"""
pygame.draw.rect(self.screen, self.color, self.rect)
??Bullet类继承了我们从模块pygame.sprite 中导入的Sprite 类。通过使用精灵,可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,需要向__init__() 传递ai_settings、screen和ship 实例,还调用super() 来继承Sprite。 ??首先,我们创建了子弹的属性rect 。子弹并非基于图像的,因此,我们必须使用pygame.Rect() 类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的x坐标和y坐标,还有矩形的宽度和高度。我们在(0,0)处创建这个矩形,但接下来的两行代码将其移动到了正确的位置,因为子弹的初始位置取决于飞船当前的位置。子弹的宽度和高度是从ai_settings 中提取的。 ??另外,我们将子弹的centerx 设置为飞船的rect.centerx 。子弹应从飞船顶部射出,因此,我们将表示子弹的rect 的top属性设置为飞船的rect的top属性,让子弹看起来像是从飞船中射出的。 ??我们将子弹的y坐标存储为小数值,以便能够微调子弹的速度。我们将子弹的颜色和速度设置分别存储到self.color 和self.speed_factor 中。 ??方法update()管理子弹位置,发射出去后,子弹在屏幕中向上移动,这意味着y坐标将不断减少,因此为更新子弹的位置,我们从self.y 中减去self.speed_factor 的值。接下来,我们将self.rect.y 设置为self.y 的值。属性speed_factor 让我们能够随着游戏的进行或根据需要提高子弹的速度,以调整游戏的行为。发射子弹后,其x坐标始终不变,因此子弹将沿直线垂直地往上穿行。 ??需要绘制子弹时,我们调用draw_bullet() 。函数draw.rect() 使用存储在self.color 中的颜色填充表示子弹的rect占据屏幕部分。
3、将子弹存储到编组中
??定义Bullet类和必要的设置后,就可以编写代码了,在玩家每次按空格键时都要射出一发子弹。首先,我们将在alien_invasion.py 中创建了一个编组(group),用于存储所有有效的子弹,以便能够管理发射出去的所有子弹。这个编组将是pygame.sprite.Group类 的一个实例;pygame.sprite.Group 类似于列表,但提供了有助于开发游戏的额外功能。在主循环中,我们将使用这个编组在屏幕上绘制子弹,以及更新每颗子弹的位置:
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions 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_height))
pygame.display.set_caption("Alian Invasion")
ship = Ship(ai_settings, screen)
bullets = Group()
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_screen(ai_settings, screen, ship, bullets)
run_game()
??我们导入了pygame.sprite 中的Group类 。首选,我们创建了一个Group实例,并将其命名为bullets。这个编组是在while循环外面创建的,这样就无需每次运行该循环时都创建一个新的子弹编组。
??这里需要我们注意的是:如果在循环内部创建这样的编组,游戏运行时将创建数千个子弹编组,导致游戏慢得像蜗牛。如果游戏停滞不前,请仔细查看主while循环中发生的情况。
??我们将bullets传递给了check_events() 和update_screen() 。在check_events() 中,需要在玩家按空格键时处理bullets;而在update_screen() 中,我们需要更新绘制带屏幕上的bullets。 ??当我们对编组调用update()时,编组将自动的对其中的每个精灵调用update(),因此,代码行bullets.update() 将组编为bullets中的每颗子弹调用bullet.update() 。
4、开火
??在game_funcation.py 中,我们还需要修改check_keydown_events() ,以便在玩家按空格键时发射一颗子弹。我们无需修改check_keyup_events() ,因为玩家松开空格键时什么都不会发生。我们还需修改update_screen() ,确保在调用flip() 前在屏幕上重绘每颗子弹。下面是对game_function.py 所做的相关代码修改:
import sys
import pygame
from bullet import Bullet
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:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
screen.fill(ai_settings.bg_color)
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
pygame.display.flip()
??编组bullets传递给了check_keydown_events() 。玩家按空格键时,创建一颗新子弹(一个名为new_bullet的Bullet实例),并使用方法add()将其加入到编组bullets中。代码bullets.add(new bullet) 将新子弹存储到了编组bullets中。 ??在check_eventd() 的定义中,我们需要添加形参bullets;调用check_keydown_events() 时,我们也需要将bullets作为实参传递给它。 ??另外,我们给在屏幕上绘制子弹的update_screen() 添加了形参bullets。方法bullets.sprites() 返回一个列表,其中包含编组bullets 中的所有精灵。为在屏幕上绘制发射的所有子弹,我们遍历编组bullets中的精灵,并对每个精灵都调用draw_bullet() 。 ??如果此时运行alien_invasion.py ,将能够左右移动飞船,并发射任意数量的子弹。子弹在屏幕上向上穿行,抵达屏幕顶部后消失,当然我们在settings.py 中修改子弹的尺寸、颜色和速度。
5、删除已消失的子弹
??当前子弹抵达屏幕顶端就会消失,这仅仅是因为pygame无法在屏幕外面绘制他们。这些子弹实际上依然存在,他们的y坐标为负数,且越来越小。这是个问题,因为它们将继续消耗内存和处理能力。 ??我们需要将这些已经消失的子弹删除,否则游戏所做的无谓工作将越来越多,进而变得越来越慢。为此,我们需要检测这样的条件,即表示子弹的rect的bottom属性为零,它表名子弹已经穿过屏幕顶端:
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions 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_height))
pygame.display.set_caption("Alian Invasion")
ship = Ship(ai_settings, screen)
bullets = Group()
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
gf.update_screen(ai_settings, screen, ship, bullets)
run_game()
??在for循环中,不应从列表或编辑组中删除条目,因此必须遍历编组的副本。我们使用了方法copy()来设置for循环,这让我们能够在循环中修改了bullets。我们检查每颗子弹,看看它是否已从屏幕顶端消失。如果是这样,就将从bullets中删除。最后,我们使用一条print语句,以显示当前我们还有多少颗子弹,从而核实已消失的子弹确实删除了。 ??如果这些代码没有问题,我们发射子弹后查看终端窗口时,将发现随着子弹一颗颗地在屏幕顶端消失,子弹数将逐渐降为0.运行这个游戏,并确认子弹已被删除后,将这条print语句删除。如果我们留下这条语句,游戏速度将大大降低,因为将输出写入到终端而花费的时间比将图形绘制到游戏窗口花费的时间还多。
6、限制子弹数量
??很多射击游戏都对可同时出现在屏幕上的子弹数量进行限制,以鼓励玩家有目标地射击。下面在游戏《外星人入侵》中作这样的限制。 ??首先,在settings.py 中存储所允许的最大子弹数:
class Settings():
"""存储《外星人入侵》的所有设置类"""
def __init__(self):
"""初始化游戏的设置"""
self.screen_width = 900
self.screen_height = 600
self.bg_color = (230, 230, 230)
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
self.bullets_allowed = 3
??这将未消失的子弹数限制为3颗。在game_functions.py 的check_keydown_events() 中,我们在创建新子弹前检查未消失的子弹数是否小于该设置:
import sys
import pygame
from bullet import Bullet
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:
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
screen.fill(ai_settings.bg_color)
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
pygame.display.flip()
??玩家按空格键时,我们检查bullets的长度。如果len(bullets) 小于3,我们就创建一个新子弹;但如果已有3颗未消失的子弹,则玩家按空格键时什么都不会发生。如果你现在运行这个游戏,屏幕上最多只能有3颗子弹。
7、创建函数update_bullets()
??编写并检查子弹管理代码后,可将其移到模块game_functions 中,以让主程序文件alien_invasion.py 尽可能简单。我们创建一个名为update_bullets 的新函数,并将其添加到game_function.py 的末尾:
import sys
import pygame
from bullet import Bullet
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:
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
screen.fill(ai_settings.bg_color)
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
pygame.display.flip()
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循环变的更简单了:
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions 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_height))
pygame.display.set_caption("Alian Invasion")
ship = Ship(ai_settings, screen)
bullets = Group()
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
run_game()
??我们让主循环包含尽量可能少的代码,这样只要看函数名就能迅速知道游戏中发生的情况。主循环检查玩家的输入,然后更新飞船的位置和所有未消失的子弹位置。接下来,我们使用更新后的位置来绘制新的屏幕。
8、创建函数fire_bullet()
??下面将发射子弹的代码移到一个独立的函数中,这样,在check_keydown_events() 中只需使用一行代码来发射子弹,让elif代码块变得非常简单:
import sys
import pygame
from bullet import Bullet
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)
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 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, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
screen.fill(ai_settings.bg_color)
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
pygame.display.flip()
def update_bullets(bullets):
"""更新子弹的位置,并删除已消失的子弹"""
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
??函数fire_bullets 只包含玩家按空格键时用于发射子弹的代码;在check_keydown_events() 中,我们在玩家按空格键时调用fire_bullet() 。我们再次运行alien_invasion.py ,确认发射子弹时依然没有错误。
总结
??上篇文章,我们给大家介绍了飞船的左右移动,以及配置相应的飞船速度以及相应的限制飞船的运动范围。最后将方法check_events 方法进行了重构;并且对最近的几个文件进行了简要的总结。本文给大家实现了飞船的子弹射击的功能;其中主要包括添加子弹设置、创建Bullet类、将子弹存储到编组中、开火以及删除已消失的子弹和限制子弹的数量。最后在game_function.py 中创建了两个新的函数,进而进一步简化了alien_invasion.py 中的代码。至此,我们的《外星人入侵》中的第一个大的模块——武装飞船为大家介绍完毕。我们通过四篇文章将这个功能做了详细的介绍。由于这个项目的效果是动态的,因此,无法截屏,大家感兴趣的话,亲自可以用该功能涉及到的代码实现一下,看看该模块的动态效果。为了让大家更好的吸收项目所用到的知识点,我们每一篇文章只给大家实现《外星人入侵》的一个功能,所以,希望大家能够仔细阅读,认真跟着写代码,理解其中的深入含义,吧这个项目的价值发挥到最大。其实这个项目已经很典型,代码到处都是,但是,如果你只是简单的粘贴复制,对你知识的学习没有任何的价值,你还是得跟着过一遍,然后要知道每行代码的含义或者是用到了前面我们介绍的哪一块知识点,只有这样,这个项目才会发挥不一样的价值,希望大家认真学习,把基础知识打扎实一点。Python是一门注重实际操作的语言,它是众多编程语言中最简单,也是最好入门的。当你把这门语言学会了,再去学习java、go以及C语言就比较简单了。当然,Python也是一门热门语言,对于人工智能的实现有着很大的帮助,因此,值得大家花时间去学习。生命不息,奋斗不止,我们每天努力,好好学习,不断提高自己的能力,相信自己一定会学有所获。加油!!!
|