Python项目实践之一:武装飞船

Python项目实践之一:武装飞船

一、规划项目

1、游戏规则设定

在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕底部*的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键进行射击。游戏开始时,一群外星人出现在天空中,他们在屏幕中向下移动。玩家的任务是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,他们移动的速度更快。只要有外星人撞到了玩家的飞船或到达了屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。

2、前期准备

1、建立项目文件

游戏《外星人入侵》将包含很多不同的文件,因此请在你的系统中新建一个文件夹,并将其命名为alien_invasion。请务必将这个项目的所有文件都存储到这个文件夹中,这样相关的 import 语句才能正确地工作。

2、安装pip

1、安装pip
安装pip,请访问https://bootstrap.pypa.io/get-pip.py。如果出现对话框,请选择保存文件;如果get-pip.py的代码出现在浏览器中,请将这些代码复制并粘贴到文本编辑器中,再将文件保存为get-pip.py。将get-pip.py保存到计算机中后,你需要以管理员身份运行它,因为pip将在你的系统中安装新包。

2、确认是否安装了pip
在dos窗口输入:pip --version
Python项目实践之一:武装飞船

3、安装Pygame

1、前往python官网https://www.python.org/,点击PypI
Python项目实践之一:武装飞船
2、在搜索框搜索pygame
Python项目实践之一:武装飞船
3、选择要下载的pygame版本
Python项目实践之一:武装飞船
4、点击下载文件
Python项目实践之一:武装飞船
5、下载自己电脑对应的版本
Python项目实践之一:武装飞船
6、把下载好的文件放入python的pip目录下
Python项目实践之一:武装飞船
7、在dos窗口输入对文件对应的指令Python项目实践之一:武装飞船
8、提示安装成功Python项目实践之一:武装飞船

3、开始项目

1、创建 Pygame 窗口以及响应用户输入
import sys
import pygame

def run_game():
    #初始化游戏并创建一个窗口对象
    pygame.init()
    #设置窗口大小为1200*800像素
    screen = pygame.display.set_mode((1200, 700))
    #设置窗口名字
    pygame.display.set_caption("Alien Invasion")

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        for event in pygame.event.get():
            #设置退出窗口
            if event.type == pygame.QUIT:
                sys.exit()
        #绘制窗口
        pygame.display.flip()

run_game()

解释:
1、导入 sys 和 pygame模块, pygame 模块包含开发游戏所需的功能, 通过sys 模块实现退出游戏功能。
2、创建一个run_game()函数,用来提供游戏所需要的窗口界面并对玩家键盘鼠标进行监听。
3、screen是属于屏幕的一个面对象,用于显示游戏元素(飞船、外星人),游戏元素也是一个面对象,我们通过while True的死循环,实现对游戏元素在不同时间和状态在游戏界面里的显示。

2、设置背景颜色
import sys
import pygame

def run_game():
    #初始化游戏并创建一个窗口对象
    pygame.init()
    #设置窗口大小为1200*800像素
    screen = pygame.display.set_mode((1200, 700))
    #设置窗口名字
    pygame.display.set_caption("Alien Invasion")

    #设置背景颜色
    bg_color = (230, 230, 230)

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        for event in pygame.event.get():
            #设置退出窗口
            if event.type == pygame.QUIT:
                sys.exit()

        #绘制窗口颜色
        screen.fill(bg_color)
        #绘制窗口
        pygame.display.flip()

run_game()

解释:
1、创建一个背景颜色存于 bg_color 中,在Pygame中,颜色是以RGB值指定的。这种颜色由红色、绿色和蓝色值组成,其中每个值的可能取值范围都为0~255。颜色值(255, 0, 0)表示红色,(0, 255, 0)表示绿色,而(0, 0, 255)表示蓝色。通过组合不同的RGB值,可创建1600万种颜色。在颜色值(230, 230, 230)中,红色、蓝色和绿色量相同,它将背景设置为一种浅灰色。
2、调用方法 screen.fill() ,用背景色填充屏幕。

3、创建设置类

每次给游戏添加新功能时,通常也将引入一些新设置。下面来编写一个名为 settings 的模块,其中包含一个名为 Settings 的类,用于将所有设置存储在一个地方,以免在代码中到处添加设置。这样,我们就能传递一个设置对象,而不是众多不同的设置。另外,这让函数调用更简单,且在项目增大时修改游戏的外观更容易:要修改游戏,只需修改settings.py中的一些值,而无需查找散布在文件中的不同设置。

settings.py:

class Settings():
    """存储《外星人入侵》的所有设置的类"""
    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1200
        self.screen_height = 600
        self.bg_color = (230, 230, 230)

alien_invasion.py:

import sys
import pygame
from settings import Settings

def run_game():
    #初始化游戏并创建一个窗口对象
    pygame.init()
    #初始化设置对象
    ai_settings = Settings()
    #设置窗口大小为1200*800像素
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    #设置窗口名字
    pygame.display.set_caption("Alien Invasion")

    #设置背景颜色
    bg_color = (230, 230, 230)

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        for event in pygame.event.get():
            #设置退出窗口
            if event.type == pygame.QUIT:
                sys.exit()

        #绘制窗口颜色
        screen.fill(ai_settings.bg_color)
        #绘制窗口
        pygame.display.flip()

run_game()

解释:在主程序文件中,我们导入 Settings 类,调用 pygame.init() ,再创建一个 Settings 实例,并
将其存储在变量 ai_settings 中。创建屏幕时,使用了 ai_settings 的属性screen_width 和 screen_height ;接下来填充屏幕时,也使用了 ai_settings 来访问背景色。

4、添加飞船图像
4.1、寻找一张飞船图像,使得图像背景处理为透明色,我找到的飞船图像如下:

Python项目实践之一:武装飞船

4.2、创建Ship类

ship.py:

class Ship():
    def __init__(self, screen):
        """初始化飞船并设置其初始位置"""
        self.screen = screen

        #加载飞船,并获取其外形矩阵
        self.imge = pygame.image.load(r'images/ship.png')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部* 
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

解释:
1、 pygame.image.load(path)方法:打开需要加载的图像,返回值是一个面对象

2、 get_rect()方法: 获取相应面对象的属性(Pygame的效率之所以如此高,一个原因是它让你能够像处理矩形( rect 对象)一样处理游戏元素,即便它们的形状并非矩形。像处理矩形一样处理游戏元素之所以高效,是因为矩形是简单的几何形状。这种做法的效果通常很好,游戏玩家几乎注意不到我们处理的不是游戏元素的实际形状。)

3、 通过处理rect 对象相对于screen_rect对象的位置来规定飞船的初始化位置位于底部中间,即:让rect 对象的centerx与screen_rect对象的centerx相等,rect 对象的bottom与screen_rect的bottom相等(要将游戏元素居中,可设置相应 rect 对象的属性 center 、 centerx 或 centery 。要让游戏元素与屏幕边缘对齐,可使用属性 top 、 bottom 、 left 或 right ;要调整游戏元素的水平或垂直位置,可使用属性 x 和 y ,它们分别是相应矩形左上角的x和y坐标)。

4、在Pygame中,原点(0, 0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200×800的屏幕上,原点位于左上角,而右下角的坐标为(1200, 800),如下图:
Python项目实践之一:武装飞船

4.3、在屏幕上绘制飞船

alien_invasion.py:

import sys
import pygame
from settings import Settings
from ship import Ship

def run_game():
    #初始化游戏并创建一个窗口对象
    pygame.init()
    #初始化设置对象
    ai_settings = Settings()
    #设置窗口大小为1200*600像素
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    #设置窗口名字
    pygame.display.set_caption("Alien Invasion")

    # 创建一艘飞船
    ship = Ship(screen)

    #设置背景颜色
    bg_color = (230, 230, 230)

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        for event in pygame.event.get():
            #设置退出窗口
            if event.type == pygame.QUIT:
                sys.exit()

        #每次循环重绘屏幕
        screen.fill(ai_settings.bg_color)
        ship.blitme()
        #绘制窗口
        pygame.display.flip()

run_game()
5、重构:模块 game_functions

在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。在本节中,我们将创建一个名为 game_functions 的新模块,它将存储大量让游戏《外星人入侵》运行的函数。通过创建模块 game_functions ,可避免alien_invasion.py太长,并使其逻辑更容易理解。

5.1、管理事件

把管理事件的代码移到一个名为 check_events() 的函数中,以简化 run_game() 并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面(如更新屏幕)分离。

5.2、更新屏幕

为进一步简化 run_game() ,下面将更新屏幕的代码移到一个名为 update_screen() 的函数中。

game_functions.py:

import sys
import pygame

def check_events():
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

def update_screen(ai_settings, screen, ship):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()
    # 让最近绘制的屏幕可见
    pygame.display.flip()
5.3将分离的功能添加到主函数中

alien_invasion.py:

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

def run_game():
    #初始化游戏并创建一个窗口对象
    pygame.init()
    #初始化设置对象
    ai_settings = Settings()
    #设置窗口大小为1200*800像素
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    #设置窗口名字
    pygame.display.set_caption("Alien Invasion")

    # 创建一艘飞船
    ship = Ship(screen)

    #设置背景颜色
    bg_color = (230, 230, 230)

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        gf.check_events()

        #每次循环重绘屏幕
        gf.update_screen(ai_settings, screen, ship)

run_game()
6、驾驶飞船
6.1、响应按键

每当用户按键时,都将在Pygame中注册一个事件。事件都是通过方法 pygame.event.get() 获取的,因此在函数 check_events() 中,我们需要指定要检查哪些类型的事件。每次按键都被注册为一个 KEYDOWN 事件。
game_ functions.py中的check_events函数:

def check_events(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                # 向右移动飞船
                ship.rect.centerx += 1

解释:
1、在函数 check_events() 中添加了形参 ship ,因为按右箭头键时,需要将飞船向右移动,飞船是操作对象,所以要添加这个形参。

2、KEYDOWN 事件:键盘按键监听事件,每次按键都被注册为一个 KEYDOWN 事件。

3、通过循环不断的监听KEYDOWN 事件,当按下键盘右箭头时,程序作出对应的响应,即:将 ship.rect.centerx 的值加1,从而将飞船向右移动。

6.2、允许不断移动

实现按住右箭头键不放时,飞船不断地向右移动,直到松开为止。要实现这个功能,就必须同时监听KEYDOWN 和 KEYUP事件,根据键盘不同的状态,程序作出不同的响应。为此,我们还需要添加一个 moving_right的标志,它标志着飞船的状态:True为运动/False为静止。

由于 moving_right的标志是飞船的属性,我们现在修改ship.py的内容:

import pygame

class Ship():
    def __init__(self, screen):
        """初始化飞船并设置其初始位置"""
        self.screen = screen

        #加载飞船,并获取其外形矩阵
        self.image = pygame.image.load(r'images/ship.png')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部* 
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

        # 移动标志 
        self.moving_right = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.centerx += 1

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

下面,我们只需要监听键盘按键,对self.moving_right来进行修改就能完成飞船的不断移动,修改game_functions.py中的check_events函数:

def check_events(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = True
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False

最后,我们只需要修改 alien_invasion.py 中的 while 循环,以便每次执行循环时都调用飞船的方法 update() :

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        gf.check_events(ship)

        #调用飞船目前位置
        ship.update()

        #每次循环重绘屏幕
        gf.update_screen(ai_settings, screen, ship)
6.3、左右移动

现在飞船只能向右移动,那么向左移动的思路和向右移动的思路是一致的,只需要修改Ship类和game_functions.py中的check_events函数即可。

1、修改Ship类:

import pygame

class Ship():
    def __init__(self, screen):
        """初始化飞船并设置其初始位置"""
        self.screen = screen

        #加载飞船,并获取其外形矩阵
        self.image = pygame.image.load(r'images/ship.png')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部* 
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

        # 移动标志
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.centerx += 1
        if self.moving_left:
            self.rect.centerx -= 1

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

2、修改game_functions.py中的check_events函数:

def check_events(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = True
            elif event.key == pygame.K_LEFT:
                ship.moving_left = True
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False
            elif event.key == pygame.K_LEFT:
                ship.moving_left = False
6.4、调整飞船的速度

目前飞船的移动速度是1像素,随着游戏的进行,我们需要加快游戏节奏,所以需要设置飞船的速度为一个可变的值,而且要便于修改,所以我们可以在 Settings 类中添加属性ship_speed_factor ,用于控制飞船的速度。
setting.py中的Settings 类:

class Settings():
    """存储《外星人入侵》的所有设置的类"""
    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1200
        self.screen_height = 600
        self.bg_color = (230, 230, 230)
        
        # 飞船的设置
        self.ship_speed_factor = 1.5

我们将飞船的默认速度设置成了1.5像素,由于飞船移动像素的大小是通过ship.py中的update函数控制的,但是rect 的 centerx 等属性只能存储整数值,所以我们需要自顶一个center变量来使得移动像素可以是浮点数。
ship.py中的Ship类:

import pygame

class Ship():
    def __init__(self, ai_settings, screen):
        """初始化飞船并设置其初始位置"""
        self.screen = screen
        #获取飞船目前的速度
        self.ai_settings = ai_settings

        #加载飞船,并获取其外形矩阵
        self.image = pygame.image.load(r'images/ship.png')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部* 
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

        # 在飞船的属性center中存储小数值
        self.center = float(self.rect.centerx)

        # 移动标志
        self.moving_right = False
        self.moving_left = False


    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.center += self.ai_settings.ship_speed_factor
        if self.moving_left:
            self.center -= self.ai_settings.ship_speed_factor
        # 根据self.center更新rect对象
        self.rect.centerx = self.center

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

解释:
1、首先需要把形参ai_settings传入ship的__init__中,这样飞船在初始化的时候才能获取到当前的速度设置
2、由于rect 的 centerx、center、centery属性只能存储整数值(如果是小数自动取整数部分),而我们设置的初始化速度是1.5像素(浮点数),所以我们要定义一个新的属性center来存储浮点数,通过center来控制rect的属性。
3、现在在 update() 中调整飞船的位置时,将 self.center 的值增加或减去的值就可以是浮点数(如果不这么做,直接用centerx加上或减去1.5像素,那么实际移动还是1像素,原因是rect 的 centerx属性自动取整数部分的值)。
4、最后将移动后的center的值传给rect 的 centerx属性即可,其实这里也是有误差的,但是[0,1)像素的误差肉眼是观察不到的。

最后, alien_invasion.py 中创建 Ship 实例时,把实参 ai_settings 传入:
alien_invasion.py实例Ship:

# 创建一艘飞船
    ship = Ship(ai_settings, screen)
6.5、限制飞船的活动范围

当前情况下,如果一直按住左箭头键或者右箭头键,飞船会飞出游戏界面的范围,下面我们需要通过设置使得飞船只能在游戏界面内移动。

其实很简单,我们只需要在Ship类的标志飞船位置的函数update中添加条件限定就可以实现,即飞船向右移动时,飞船的右边界小于游戏界面的右边界,飞船向左移动时,飞船的左边界大于0。
Ship类的函数update:

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.center += self.ai_settings.ship_speed_factor
        if self.moving_left and self.rect.left > 0:
            self.center -= self.ai_settings.ship_speed_factor
        # 根据self.center更新rect对象
        self.rect.centerx = self.center
6.6、重构 check_events()

随着游戏开发的进行,函数 check_events() 将越来越长,我们将其部分代码放在两个函数中:一个处理 KEYDOWN 事件,另一个处理 KEYUP 事件。这么做有助于代码结构更新清晰,后期维护起来更方便。
game_functions.py:

import sys
import pygame

def check_keydown_events(event, ship):
    """响应按键事件"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True

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(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ship)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)

def update_screen(ai_settings, screen, ship):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()
    # 让最近绘制的屏幕可见
    pygame.display.flip()
7、射击

射击功能:子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。

7.1、 添加子弹设置

在settings.py中添加子弹设置:

class Settings():
    """存储《外星人入侵》的所有设置的类"""
    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1200
        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像素的深灰色子弹,默认速度为1像素

7.2 创建 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, 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

解释:
1、Bullet 类继承了我们从模块 pygame.sprite 中导入的 Sprite 类。通过使用精灵,可将游戏中相关的元素编组,进而同时操作编组中的所有元素。
2、我们需要获取子弹的设置、相对于游戏界面的位置、相对于飞船的位置,所以需要将对应参数ai_settings、screen、 ship传入到Bullet类的__init__方法中。
3、由于子弹不是我们导入的图像,而是直接创建的,所以首先需要创建一个子弹矩形,然后再通过此子弹矩形调整它相对于飞船的位置
4、与飞船移动一样,rect中的center等方法只能接收int类型参数,如果不是int类型就取整数,所以为了使子弹移动能接收浮点数,所以要进行float转化。
5、最后把获取的子弹的颜色和速度存入变量中

子弹的初始位置设置好了,它和ship一样,也需要重新定义一个子弹的位置函数update,方便于后期维护。
定义bullet.py中Bullet类的update函数:

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

还需要定义一个函数draw_bullet,用来在游戏界面绘制子弹
定义bullet.py中Bullet类的draw_bullet函数:

def draw_bullet(self):
    """在屏幕上绘制子弹"""
    pygame.draw.rect(self.screen, self.color, self.rect)
7.3、 将子弹存储到编组中

需要给子弹储存到一个编组中,就需要用到 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.init()
    #初始化设置对象
    ai_settings = Settings()
    #设置窗口大小为1200*800像素
    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()

    #设置背景颜色
    bg_color = (230, 230, 230)

    #开始游戏主循环
    while True:
        #监听键盘和鼠标事件
        gf.check_events(ai_settings, screen, ship, bullets)

        #调用飞船目前位置
        ship.update()

		#调用编组中的有效子弹目前位置
        bullets.update()

        #每次循环重绘屏幕
        gf.update_screen(ai_settings, screen, ship, bullets)

run_game()

解释:
1、我们将 bullets 传递给了 check_events() 和 update_screen() 。在 check_events() 中,需要在玩家按空格键时处理 bullets ;而在 update_screen() 中,需要更新要绘制到屏幕上的 bullets 。
2、当你对编组调用 update() 时,编组将自动对其中的每个精灵调用 update() ,因此代码行bullets.update() 将为编组 bullets 中的每颗子弹调用 bullet.update() 。

7.4、开火

开火:玩家按下空格键就会发射一颗子弹出去,因此,我们需要对空格键按下事件进行监听,并且每次发射出去的子弹都需要重绘到游戏界面,所以我们只需要修改game_functions.py就可以实现。
由于发射子弹只用监听空格键按下的响应,不用监听空格键松开的响应,所以修改game_functions.py中的check_keydown_events方法:

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)

由于check_events方法调用了check_keydown_events方法,所以也需要修改game_functions.py中的check_events方法:

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)

最后,修改game_functions.py中的绘制update_screen方法即可:

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()

解释:
1、编组 bulltes (注意这里是编组,不是Bullet 的实例)传递给了 check_keydown_events() 。玩家按空格键时,创建一颗新子弹(一个名为 new_bullet 的 Bullet 实例),并使用方法 add() 将其加入到编组 bullets 中。
2、 方 法bullets.sprites() 返回一个列表,其中包含编组 bullets 中的所有精灵。为在屏幕上绘制发射的所有子弹,我们遍历编组 bullets 中的精灵,并对每个精灵都调用 draw_bullet()。

7.5、删除已消失的子弹

目前从视觉效果上可以看到子弹只要飞出游戏界面上边界就消失了,但是实际上它并没有消失,就好比不给飞船设定移动边界,飞船会移出游戏界面,但是它确实是存在的,只不过是超出了游戏界面的范围。我们需要将这些已消失的子弹删除,否则游戏所做的无谓工作将越来越多,进而变得越来越慢。

需要删除这些子弹,直接判断每个子弹的 rect 的 bottom 属性是否小于等于零即可,如果小于等于零,则证明子弹已经穿出上边界。

在主程序alien_invasion.py的while循环中添加此判断:

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

        #每次循环重绘屏幕
        gf.update_screen(ai_settings, screen, ship, bullets)

解释:
1、在 for 循环中,不应从列表或编组中删除条目,因此必须遍历编组的副本(python在删除第一个元素后,后面的所有元素会向前移一位, 相应的,索引值也会发生改变)。我们使用了方法copy() 来设置 for 循环,这让我们能够在循环中修改 bullets 。

7.6、限制子弹数量

为了让游戏体验性更好,我们应该限制子弹的数量,以鼓励玩家能有效的发射子弹,还能减少服务器的压力。
首先,在settings.py中存储所允许的最大子弹数:

#子弹设置
        self.bullet_speed_factor = 1
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color =(60, 60, 60)
        self.bullets_allowed = 3

其次,在game_functions.py的 check_keydown_events() 中,我们在创建新子弹前检查未消失的子弹数是否小于该设置:

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)
7.7、 创建函数 update_bullets()

为了让让主程序文件alien_invasion.py 尽可能简单,而且逻辑层次明确,我们应将子弹管理代码移到模块 game_functions 中。
在game_functions.py添加一个新函数 update_bullets(),用于更新子弹的位置,并删除已消失的子弹:

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

然后再主程序alien_invasion.py 的while循环把子弹管理相关的代码替换成调用格式:

    while True:
        #监听键盘和鼠标事件
        gf.check_events(ai_settings, screen, ship, bullets)

        #调用飞船目前位置
        ship.update()

        #调用管理子弹的函数
        gf.update_bullets(bullets)

        #每次循环重绘屏幕
        gf.update_screen(ai_settings, screen, ship, bullets)
7.8、创建函数 fire_bullet()

将发射子弹的代码移到一个独立的函数中,这样,在 check_keydown_events() 中只需使
用一行代码来发射子弹,让 elif 代码块变得非常简单:
给game_functions.py添加一个新函数 fire_bullet,用于创建子弹:

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)

然后,在game_functions.py中的check_keydown_events调用以上函数即可:

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)

这是一篇读书笔记,如有不妥之处,请各位指正!!!

上一篇:俄罗斯方块


下一篇:博雅数据机器学习04