python应用篇之外星人入侵项目——武装飞船(下)

Posted 一计之长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python应用篇之外星人入侵项目——武装飞船(下)相关的知识,希望对你有一定的参考价值。

前言

  上篇文章我们给大家详细介绍了飞船相关素材的下载以及创建了一个ship类,并且在屏幕上出现了飞船。最后,又向大家介绍了模块的重构,让我们的代码更加具有可用性和可扩展性,进一步使得我们的代码更具有鲁棒性。本文继续给大家介绍武装飞船的相关内容,首先给大家介绍驾驶飞船。

一、驾驶飞船

  下面来让玩家能够左右移动飞船。为此,我们将编写代码,在用户按左或右箭头键时作出响应。我们将首先专注于向右移动,再使用同样的原理来控制向左移动。通过这样做,你将学会如何控制屏幕图像的移动。

1、响应按键

  每当用户按键时,都将在Python中注册一个事件。事件都是通过方法pygame.event.get()获取的,因此在函数check_events()中,我们需要指定要检查哪些类型的事件。每次按键都被注册为一个KEYDOWN事件。
  检测到KEYDOWN事件时,我们需要检查按下的是否是特定的键。例如,如果按下的是右箭头键,我们就增大飞船的rect.centerx值,将飞船向右移动:

import sys
import pygame
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
def update_screen(ai_settings, screen, ship):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()
    # 让最近绘制的屏幕可见
    pygame.display.flip()

  我们在函数check_events()中包含形参ship,因为玩家按右箭头时,需要将飞机向右移动。在函数check_events()内部,我们在事件循环中添加了elif代码块,以便在Pygame检测到KEYDOWN事件时作出相应。我们读取属性event.key,以检查按下的是否是右箭头键(pygame.K_PIGHT)。如果按下的是右箭头键,就将ship.rect.centerx的值加1,从而将飞船向右移动。
  在alien_invasion.py中,我们需要更新调用的check_events()代码,将ship作为实参传递给它:

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()
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Alian Invasion")
    # 创建一艘飞船
    ship = Ship(screen)
    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship)
        # 每次循环时都重绘屏幕
        gf.update_screen(ai_settings, screen, ship)
run_game()

  如果我们现在运行alien_invasion.py,则每次按右箭头一次,飞船都将向右移动一个像素。这里实在不方便演示,表示抱歉。这是一个开端,但并非控制飞船的高效方式。下面我们来改进控制方式,允许持续移动。

2、允许不断移动

  玩家按住右键头不放时,我们希望飞船不断地向右移动,直到玩家松开为止。我们将让游戏检测pygame.KEYUP事件,以便玩家松开右箭头键时我们能够知道一点;然后,我们将结合使用KEYDOWNKEYUP事件,以及一个名为moving_right的标志来实现持续移动。
  飞船不栋时,标志moving_right将为FALSE。玩家按下右箭头键时,我们将这个标志设置为True;而玩家松开时,我们将这个标志重新设置为False
  飞船的属性都由ship类控制,因此,我们将给这个类添加了一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志着moving_right的状态。如果这个标志为True,就调整飞船的装置。每当需要调整飞船的位置时,我们都调用这个方法。我们接下来对对Ship类所做的修改:

import pygame

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

        # 加载飞船图像并获取其外接矩阵
        self.image = pygame.image.load('images/ship.bmp')
        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)

  在方法__init__()中,我们添加了属性self.moving_right,并将其初始值设置为False。接下来,我们添加了方法update(),它在前述标志为True时向右移动飞船。
  下面修改check_events(),使其再玩家按下右箭头键时将moving_right设置为True,并在玩家松开时将moving_right设置为False

import sys
import pygame


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

  我们首先修改了游戏在玩家按下右箭头键时响应的方式:不直接调整飞船的位置,而只是将moving_right设置为True。接着我们添加一个新的elif代码块,用于响应KEYUP事件;玩家松开右箭头键(K_RIGHT)时,我么将moving_right设置为False
  最后,我们需要修改alien_invasion.py中的while循环,以便每次执行循环时都会调用飞船的方法update()

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()
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Alian Invasion")
    # 创建一艘飞船
    ship = Ship(screen)
    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship)
        ship.update()
        # 每次循环时都重绘屏幕
        gf.update_screen(ai_settings, screen, ship)
run_game()

  飞船的位置将在检测到键盘事件后(但在更新屏幕前)更新。这样玩家输入时,飞船的位置将更新,从而确保使用更新后的位置将更新,从而确保使用更新后的位置将飞船绘制到屏幕上。
 &emsp如果我们现在运行alien_invasion.py并按住右箭头键,飞船将不断地向右移动,直到松开为止。不过,无法录屏,大家感兴趣的可以敲代码实现一下,自己可以看看效果。

3、左右移动

  飞船能够不断地向右移动后,添加向左移动的逻辑很容易。我们将再次修改ship类和函数check_events()。下面显示了对Ship类的方法__init__()update()所做的相关修改:

import pygame

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

        # 加载飞船图像并获取其外接矩阵
        self.image = pygame.image.load('images/ship.bmp')
        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)

  在方法__init__()中,我们添加了属性self.moving_left,在方法update()中,我们添加了一个if代码块而不是elif代码块,这样如果玩家同时按下左右键箭头,将先增大飞船的rect_centrix值,再降低这个值,即飞船的位置保持不变。如果使用一个elif代码块来处理向右左移动的情况,右箭头键将始终处于优先地位。从向左移动切换到向右移动时,玩家可能同时按住左右箭头键,在这种情况下,前面介绍的方法让移动更加准确。
  我们还需要对check_events()作两方面的调整:

import sys
import pygame


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

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

  如果因玩家按下K_LEFT键而触发了KEYDOWN事件,我们就将moving_left设置为True;如果因玩家K_LEFT而触发了KEYUP事件,我们就将moving_left设置为False。这里之所以可以使用elif代码块,是因为每个事件都只与一个键相关联;如果玩家同时按下了左右箭头键,将会检测到两个不同的事件。
  如果此时运行alien_invasion.py,就能够不断地左右移动飞船;如果我们同时按左右箭头键,飞船将纹丝不动。
  下面来进一步优化飞船的移动方式,调整飞船的速度;限制飞船的移动距离,以免它移动到屏幕外面去。

4、调整飞船的速度

  当前,每次执行while循环时,飞船最多移动一个像素,但我们可以在Settings类中添加属性ship_speed_factor,用于控制飞船的速度。我们将根据这个属性决定飞船在每次循环时最多移动多少距离,下面演示了如何在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

  我们将ship_speed_factor的初始值设置成了1.5.需要移动飞船时,我们将移动1.5像素而不是1像素。通过将速度设为指定的小数值,可在后面加快游戏的节奏时更细致地控制飞船的速度。然而,rect的centerx等属性只能存储数值,因此,我们需要对视频类做些修改:

import pygame

class Ship():
    def __init__(self, ai_settings, screen):
        """初始化飞船并设置其初始化位置"""
        self.screen = screen
        self.ai_settings = ai_settings
        # 加载飞船图像并获取其外接矩阵
        self.image = pygame.image.load('images/ship.bmp')
        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):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的center值,而不是rect
        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)

  我们首先在__init__()的形参列表中添加了ai_settings,让飞船能够获取其速度设置。接下来,我们将形参ai_settings的值存储在一个属性中,以便能够在updates()中使用它。鉴于现在调整飞船的位置时,将增加或减去一个单位的像素的小数值,因此,需要将位置存储在一个能够存储小数值的变量中。可以使用小数来设置rect的属性,但rect将只存储这个值的整数部分。为准确地存储飞船的位置,我们定义了一个可存储小数值的新属性self.center。我们使用函数float()将self.rect.centerx的值转换为小数,并将结果存储到self.center中。
  现在在update()中调整了飞船的位置时,将self.center的值增加或减去ai_settings.ship_speed_factor的值。更新self.center后,我们再根据它来更新控制飞船的位置的self.rect.centerxself.rect.centerx将存储self.center的整数部分,但对于显示飞船而言,则问题不是很大。
self.center在alien_invasion.py中创建Ship实例时,需要传入实参ai_settings

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()
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Alian Invasion")
    # 创建一艘飞船
    ship = Ship(ai_settings, screen)
    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship)
        ship.update()
        # 每次循环时都重绘屏幕
        gf.update_screen(ai_settings, screen, ship)
run_game()

  现在,只要ship_speed_factor的值大于1,飞船的速度就会比以往更快。这有助于让飞船的反应速度足够快,能够将外星人射下来,还让我们能够随着游戏的进行加快游戏的节奏。

5、限制飞船的活动范围

  当前,如果玩家按住箭头键的时间足够长,飞船将移到屏幕外面,消失得无影无踪。下面来修复这种问题,让飞船到达屏幕边缘后停止移动。为此,我们将修改Ship类的方法update():

import pygame

class Ship():
    def __init__(self, ai_settings, screen):
        """初始化飞船并设置其初始化位置"""
        self.screen = screen
        self.ai_settings = ai_settings
        # 加载飞船图像并获取其外接矩阵
        self.image = pygame.image.load('images/ship.bmp')
        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):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的center值,而不是rect
        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
    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

  上述代码在修改self.center的值之前先检查飞船的位置。self.rect.right返回飞船外接矩阵的右边缘的x坐标,如果这个值小于self.screen_rect.right的值,就说明飞船未触及屏幕右边缘。左边缘的情况与此类似;如果rect的左边缘的x坐标大于零,就说明飞船未触及屏幕左边缘。这确保仅当飞船在屏幕内时,才调整self.center的值。
  如果此时运行alien_invasion.py,飞船将在触及屏幕左边缘或右边缘后停止移动。

6、重构check_envents()

  随着游戏开发的进行,函数check_events()将越来越长,我们将其部分代码放在两个函数中;一个处理KEYDOWN事件,另一个处理KEYUP事件:

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

  我们创建了两个新函数:check_keydown_events()和check_keyup_events()。它们都包含形参event和ship。这两个函数的代码是从check_events()中复制而来的,因此我们将函数check_events中相应的代码替换成了对这两个函数的调用。现在,函数check_events()更简单,代码的结构更加的清晰。这样,在其中响应其他玩家输入时将更容易。也让我们的代码更有逻辑性,扩展性更强,鲁棒性更好。

二、简单回顾

  我们飞船的所有代码已经结束,下篇文章就是给大家介绍飞船设计子弹的功能,在这之前,我们在本文即将结束之际给大家简单回顾一下之前我们飞船中所有文件的主要功能。目前我们有四个文件,其中包含很多类、函数和方法。添加其他功能之后,为了让大家清楚这个项目的组织结构,我们一起回顾一下这写文件:

1、alien_inversion.py

  主文件alien_inversion.py创建了一系列整个游戏都要用到的对象;存储在ai_settings中的设置、存储在screen中的主要显示surface以及一个飞船的实例。文件alien_inversion.py还包含游戏中的主循环,这是一个要调用check_events()、ship.update()和update_screen()的while循环。
  要玩游戏《外星人入侵》,我们只需要运行文件alien_inversion.py。其他文件(settings.py、game_funcation.py、ship.py)包含的代码被直接或间接地导入这个文件中。

2、setting.py

  文件settings.py包含settings类,这个类只包含方法__init__(),它初始化控制游戏外观和飞船速度的属性。

3、game_funcation.py

  文件game_funcation.py包含一系列函数,游戏的大部分工作都是由它们完成的。函数check_events()检测相关的事件,如按键和松开,并使用辅助函数check_keydown_events()check_keyup_events()来处理这些事情。就目前而言,这些函数管理飞船的移动。模块game_funcations还包含函数update_screen(),它用于在每次执行主循环时都重绘屏幕。

4、ship.py

  文件ship.py包含Ship类,这个类包含方法__init__()、管理飞船位置的方法update()以及在屏幕上绘制飞船的方法blitme()。表示飞船的图像存储在images下的文件ship.bmp中。

总结

  上篇文章我们给大家详细介绍了飞船相关素材的下载以及创建了一个ship类,并且在屏幕上出现了飞船。最后,又向大家介绍了模块的重构,让我们的代码更加具有可用性和可扩展性,进一步使得我们的代码更具有鲁棒性。本文给大家介绍飞船驾驶功能的实现;即通过左右箭头键来控制我们的飞船,并且提高了飞船的运行速度,最后限制飞船只能在本屏幕之内。由于本文的效果均是动态的,本人受条件所限,无法录视频,因此,本文只有代码,没有视频,大家感兴趣的可以自己动手将代码敲一遍,感受一下该游戏的效果。为了让大家更好的吸收项目所用到的知识点,我们每一篇文章只给大家实现《外星人入侵》的一个功能,所以,希望大家能够仔细阅读,认真跟着写代码,理解其中的深入含义,吧这个项目的价值发挥到最大。其实这个项目已经很典型,代码到处都是,但是,如果你只是简单的粘贴复制,对你知识的学习没有任何的价值,你还是得跟着过一遍,然后要知道每行代码的含义或者是用到了前面我们介绍的哪一块知识点,只有这样,这个项目才会发挥不一样的价值,希望大家认真学习,把基础知识打扎实一点。Python是一门注重实际操作的语言,它是众多编程语言中最简单,也是最好入门的。当你把这门语言学会了,再去学习java、go以及C语言就比较简单了。当然,Python也是一门热门语言,对于人工智能的实现有着很大的帮助,因此,值得大家花时间去学习。生命不息,奋斗不止,我们每天努力,好好学习,不断提高自己的能力,相信自己一定会学有所获。加油!!!

以上是关于python应用篇之外星人入侵项目——武装飞船(下)的主要内容,如果未能解决你的问题,请参考以下文章

python应用篇之外星人入侵项目——外星人(上)

python应用篇之外星人入侵项目——外星人(中)

python应用篇之外星人入侵项目——记分(上)

python应用篇之数据可视化——生成数据(上)

简单的Python项目——《外星人入侵》(关键词:pygame,类,函数,编组,图像)

Python编程:《外星人入侵》