Python编程从入门到实践——第十二章

第十二章——武装飞船

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

12.1 规划项目

在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键进行射击。

游戏开始时,一群外星人出现在天空中,他们在屏幕中向下移动。玩家的任务是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,他们移动的速度更快。只要有外星人撞到了玩家的飞船或到达了屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。

12.2 安装 Pygame

具体教程看:pygame下载(非常详细)_翠小白的博客-CSDN博客

这里需要注意的是输入win+R输入cmd打开终端再执行其他操作,并不是win+r后就输入python,这样会出现以下问题

pip install pygame
File "<stdin>", line 1
pip install pygame
^
SyntaxError: invalid syntax
>>>

正确操作后的结果截图如下:

显示安装成功了之后就可以用了,可以开始开发游戏项目了

12.3 开始游戏项目

首先创建一个空的Pygame窗口,供后面用来绘制游戏元素。

12.3.1 创建 Pygame 窗口以及响应用户输入

alien_invasion.py

import sys

import pygame


def run_game():
    #初始化游戏并创建一个屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200,800))
    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()

我们导入了模块sys和pygame。模块pygame包含开发游戏所需的功能。玩家退出时,我们将使用模块sys来退出游戏。
pygame.init()初始化背景设置,pygame.display.set_mode()来创建一个名为screen的显示窗口,我们创建了一个宽1200像素、高800像素的游戏窗口。

对象screen是一个surface。在Pygame中, surface是屏幕的一部分,用于显示游戏元素。在这
个游戏中,每个元素(如外星人或飞船)都是一个surface。

display.set_mode()返回的surface表示整个游戏窗口。我们只需要整个屏幕的部分区域,他们把部分区域叫做surface。

我们编写一个事件循环——while循环,以侦听事件,并根据发生的事件执行相应的任务。 下面的for循环就是一个事件循环。

为访问Pygame检测到的事件,我们使用方法pygame.event.get()。将检测到pygame.QUIT事件,而我们调用sys.exit()来退出游戏。

pygame.display.flip(),命令Pygame让最近绘制的屏幕可见。在这里,它在每次执行while循环时都绘制一个空屏幕,并擦去旧屏幕,使得只有新屏幕可见。

运行代码,可以看到一个背景为黑色的空的pygame窗口。

12.3.2 设置背景色

alien_invasion.py

import sys

import pygame


def run_game():
    # 初始化游戏并创建一个屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption("Alien Invasion")

    # 设置背景色
    by_color = (230, 230, 230)

    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # 每次循环都重绘屏幕
        screen.fill(by_color)

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


run_game()

创建了一种背景色,并将其存储在bg_color中,调用方法screen.fill(),用背景色填充屏幕;这个方法只接受一个实参:一种颜色。

12.3.3 创建设置类

每次给游戏添加新功能时,通常也将引入一些新设置。下面来编写一个名为settings的模块,其中包含一个名为Settings的类,用于将所有设置存储在一个地方,以免在代码中到处添加设置。
setting.py

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

alien_invasion.py

import sys

import pygame

from setting import Settings


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


    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # 每次循环都重绘屏幕
        screen.fill(ai_setting.bg_color)

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


run_game()

实例化了一个类对象ai_setting,需要设置的属性都可以通过这个对象进行调用。

12.4 添加飞船图像

为游戏选择素材时,务必要注意许可。最安全、最不费钱的方式是使用http://pixabay.com/等网站提供的图形,这些图形无需许可,你可以对其进行修改。

在游戏中几乎可以使用任何类型的图像文件,但使用位图( .bmp)文件最为简单,因为Pygame
默认加载位图。大多数图像都为.jpg、 .png或.gif格式,但可使用Photoshop、 GIMP和Paint等工具将其转换为位图。

请在主项目文件夹( alien_invasion)中新建一个文件夹,将其命名为images,并将文件ship.bmp保存到这个文件夹中。
Python外星人入侵游戏——添加飞船和外星人图片_Ljt101222的博客-CSDN博客

12.4.1 创建 Ship 类

选择用于表示飞船的图像后,需要将其显示到屏幕上。我们将创建一个名为ship的模块,其中包含Ship类,它负责管理飞船的大部分行为。
导入游戏开发模块pygame。加载图像,调用pygame.image.load(),将加载出来的图像渲染到self.image上面。加载图像后,我们使用get_rect()获取相应surface的属性rect,也就是获取它的外接矩形。

ship.py

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
        
    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)
        
        

Pygame的效率之所以如此高,一个原因是它让你能够像处理矩形( rect对象)一样处理游戏元素,即便它们的形状并非矩形。
处理rect对象时,可使用矩形四角和中心的x和y坐标。可通过设置这些值来指定矩形的位置。

将游戏元素居中,可设置相应rect对象的属性center、 centerx或centery。要让游戏元素
与屏幕边缘对齐,可使用属性top、 bottom、 left或right;要调整游戏元素的水平或垂直位置,
可使用属性x和y,它们分别是相应矩形左上角的x和y坐标。
 

注意

在Pygame中,原点(0, 0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200× 800的屏幕上,原点位于左上角,而右下角的坐标为(1200, 800)。
 

定义了方法blitme(),它根据self.rect指定的位置将图像绘制到屏幕上。

12.4.2 在屏幕上绘制飞船

更新alien_invasion.py,使其创建一艘飞船,并调用其方法blitme():
alien_invasion.py

import sys

import pygame

from setting import Settings
from ship import Ship


def run_game():
    # 初始化pygame、设置和屏幕对象
    pygame.init()
    ai_setting = Settings()
    screen = pygame.display.set_mode((ai_setting.screen_width, ai_setting.screen_hight))
    pygame.display.set_caption("Alien Invasion")
    
    # 创建一艘飞船
    ship = Ship(screen)


    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # 每次循环都重绘屏幕
        screen.fill(ai_setting.bg_color)
        ship.blitme()
        # 让最近绘制的屏幕可见
        pygame.display.flip()


run_game()

导入Ship类,并在创建屏幕后创建一个名为ship的Ship实例。必须在主while循环前面创建该实例。
以免每次循环时都创建一艘飞船。填充背景后,我们调用ship.blitme()将飞船绘制到屏幕上,确保它出现在背景前面。

运行alien_invasion.py,将看到飞船位于空游戏屏幕底部中央。

12.5 重构:模块 game_functions

重构旨在简化既有代码的结构,使其更容易扩展。

在本节中,我们将创建一个名为game_functions的新模块,它将存储大量让游戏《外星人入侵》运行的函数。通过创建模块game_functions,可避免alien_invasion.py太长,并使其逻辑更容易理解。

12.5.1 函数 check_events()

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

将check_events()放在一个名为game_functions的模块中:
game_function.py

import sys

import pygame

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

修改alien_invasion.py,使其导入模块game_functions,并将事件循环替换为对函check_events()的调用:

alien_invasion.py

import pygame

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


def run_game():
    # 初始化pygame、设置和屏幕对象
    pygame.init()
    ai_setting = Settings()
    screen = pygame.display.set_mode((ai_setting.screen_width, ai_setting.screen_hight))
    pygame.display.set_caption("Alien Invasion")
    
    # 创建一艘飞船
    ship = Ship(screen)

    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        gf.check_events()
        
        # 每次循环都重绘屏幕
        screen.fill(ai_setting.bg_color)
        ship.blitme()
        # 让最近绘制的屏幕可见
        pygame.display.flip()


run_game()

主程序文件中,不再需要直接导入sys,因为当前只在模块game_functions中使用了它。出
于简化的目的,我们给导入的模块game_functions指定了别名gf。

12.5.2 函数 update_screen()

为进一步简化run_game(),下面将更新屏幕的代码移到一个名为update_screen()的函数中,并将这个函数放在模块game_functions.py中:
game_functions.py

import sys

import pygame


def check_events():
    """响应按键和鼠标事件"""
    ————snip————


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

将alien_invasion.py的while循环中更新屏幕的代码替换为对函数update_screen()的调用:
alien_invasion.py

import pygame

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


def run_game():
   ---snip---

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


run_game()

一开始将代码编写得尽可能简单,并在项目越来越复杂时进行重构。

12.5动手试一试

12-1 蓝色天空

创建一个背景为蓝色的 Pygame 窗口。

blue_bg.py

set_caption()是为窗口命名标题的函数

import pygame


def  blue_background():
    """创建一个背景为蓝色的Pygame窗口"""

    # 初始化pygame、屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption('blue_background')
        
    # 设置屏幕背景颜色
    bg_color = (94, 154, 195)
        
    # 每次循环都重绘屏幕
    screen.fill(bg_color)
    
    # 让最新的屏幕可视
    pygame.display.flip()
        
blue_background()        

输出报错,内容如下

这里给(1200,800)带个括号

   screen = pygame.display.set_mode((1200, 800))

再次运行,没有报错,但是窗口一闪而过,推测原因是没有写循环,窗口只显示一瞬间,写了while循环之后好了,窗口可以正常显示

此时的代码为

blue_bg.py

import pygame


def  blue_background():
    """创建一个背景为蓝色的Pygame窗口"""

    # 初始化pygame、屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption('blue_background')
        
    # 设置屏幕背景颜色
    bg_color = (97, 154, 195)
    
    while True:
        
        # 每次循环都重绘屏幕
        screen.fill(bg_color)
        # 让最新的屏幕可视
        pygame.display.flip()
        

blue_background()        

但是窗口在关闭的时候未响应,排查后原因是没有添加关闭模块

所以最终的代码为:

import sys
import pygame

from duck import Duck


def blue_background():
    """创建一个背景为蓝色的Pygame窗口"""

    # 初始化pygame、屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption('blue_background')


    # 设置屏幕背景颜色
    bg_color = (105, 87, 135)

    while True:
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
                
        # 每次循环都重绘屏幕
        screen.fill(bg_color)
        # 让最新的屏幕可视
        pygame.display.flip()


blue_background()
12-2 游戏角色

找一幅你喜欢的游戏角色位图图像或将一幅图像转换为位图。创建一个类,将该角色绘制到屏幕中央,并将该图像的背景色设置为屏幕背景色,或将屏幕背景色设置为该图像的背景色。

blue_bg.py

import sys
import pygame

from duck import Duck


def blue_background():
    """创建一个背景为蓝色的Pygame窗口"""

    # 初始化pygame、屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption('blue_background')
    my_duck = Duck(screen)

    # 设置屏幕背景颜色
    bg_color = (105, 87, 135)

    while True:
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
                
        # 每次循环都重绘屏幕
        screen.fill(bg_color)
        my_duck.blitme()
        # 让最新的屏幕可视
        pygame.display.flip()


blue_background()

duck.py

import pygame

class Duck():
    """初始化鸭嘴兽图片并设置其初始位置"""
    
    def __init__(self, screen):
        self.screen = screen
        
        # 加载鸭嘴兽图像并获取它的外接矩形
        self.image = pygame.image.load('images\duck.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = self.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)
        
        

结果如下

注意:python在windows系统上,斜杠应该是”\“反斜杠

12.6 驾驶飞船

编写代码,在用户按左或右箭头键时作出响应。

12.6.1 响应按键

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

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

在函数check_events()中包含形参ship,因为玩家按右箭头键时,需要将飞船向右移动。在事件循环中添加了一个elif代码块,以便在Pygame 检测到KEYDOWN事件时作出响应。我们读取属性event.key,以检查按下的是否是右箭头键( pygame.K_RIGHT)。如果按下的是右箭头键,就将ship.rect.centerx的值加1,从而将飞船向右移动。

在alien_invasion.py中,我们需要更新调用的check_events()代码,将ship作为实参传递给它:

运行代码,右箭头按下一次,飞船向右移动一个像素。这样的移动方式过于麻烦了,需要不停的按下按键进行移动。

12.6.2 允许不断移动

玩家按住右箭头键不放时,我们希望飞船不断地向右移动,直到玩家松开为止。结合使用KEYDOWN和KEYUP事件,以及一个名为moving_right的标志来实现持续移动。

飞船不动和玩家松开右箭头时时,标志moving_right将为False。玩家按下右箭头键时,我们将这个标志设置为True。添加一个名为moving_right的属性和一个名为update()的方法。update()检查标志moving_right的状态。

在ship.py添加以下内容

ship.py

订正,这里update函数里面位置移动写错了,应该给self.rect.centerx加1,这里错误的将它加在了外接矩形这个整体,计算机就会懵了,不知道怎么算。

正确的update函数的代码应该是

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

更新game_function.py的check_event()函数

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.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False

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

alien_invasion.py

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

现在运行程序,按住右箭头飞船会一直向右移动。

12.6.3 左右移动

既然飞船以及可以连续向右移动了,那么就按照同样的逻辑让飞船向左移动。
首先更新ship.py

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

其次更新game_function.py

    """响应按键和鼠标事件"""
    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

现在运行代码,就会发现飞船可以左右连续移动。

12.6.4 调整飞船的速度

当前,每次执行while循环时,飞船最多移动1像素,但我们可以在Settings类中添加属性ship_speed_factor,用于控制飞船的速度。
在settings.py中添加这个新属性:这里飞船每次循环都可以移动1.5个像素

class Settings():
    """存储《外星人入侵》游戏的所有设置的类"""

    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
         ---snip---

        # 设置飞船速度
        self.ship_speed_factor = 1.5

然而,rect的centerx、centery等属性只能存储整数值rect将只存储这个值的整数部
分,
因此我们需要对Ship类做些修改:

import pygame


class Ship():

    def __init__(self,ai_setting, screen):
        """初始化飞船并设置其初始位置"""
        self.screen = screen
        self.ai_setting = ai_setting
        # 加载飞船图像并获取其外接矩形
        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值而不是centerx
        if self.moving_right == True:
            self.center += self.ai_setting.ship_speed_factor
        elif self.moving_left == True:
            self.center -= self.ai_setting.ship_speed_factor
        # 根据self.center更新飞船对象
        self.rect.centerx = self.center
    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

在__init__()的形参列表中添加了ai_settings,让飞船能够获取其速度设置。

为准确地存储飞船的位置,我们定义了一个可存储小数值的新属性self.center。我们使用函数float()将self.rect.centerx的值转换为小数,并将结果存储到self.center中。

更 新 self.center 后 , 我 们 再 根 据 它 来 更 新 控 制 飞 船 位 置 的self.rect.centerx。 self.rect.centerx将只存储self.center的整数部分,但对显示飞船而言,这问题不大。

在alien_invasion.py中创建Ship实例时,需要传入实参ai_settings:
 

运行alien_invasion.py,你会发现飞船的飞行速度比以前快很多。

12.6.5 限制飞船的活动范围

让飞船到达屏幕边缘后停止移动。为此,我们将修改Ship类的方法update():

self.rect.right的意思是外接矩形的右边x值,修改self.center的值之前检查飞船的位置。 self.rect.right返回飞船外接矩形的右边缘的x坐标,如果这个值小于self.screen_rect.right的值,就说明飞船未触及屏幕右边缘,左边缘的情况与此类似。

12.6.6 重构 check_events()

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

函数的形参要填的东西和你在函数内部要写的形参要保持一致哦,像这里这个函数的形参是event和ship,def check_keydown_events(event, ship),是因为在下面的使用中要引用。

12.7 简单回顾

12.7.1 alien_invasion.py

导入相关文件,创建游戏所需的对象:设置类,屏幕显示类,还包括游戏主循环。

12.7.2 settings.py

设置类,只包含一个初始化的函数,设置屏幕的尺寸和飞船速度。

12.7.3 game_functions.py

包含游戏运行的一系列函数,屏幕刷新显示,鼠标事件响应。

12.7.4 ship.py
12-3 火箭

 编写一个游戏,开始时屏幕中央有一个火箭,而玩家可使用四个方向键上下左右移动火箭。请务必确保火箭不会移到屏幕外面。

项目规划

问题一

敲代码时遇到的问题,不明白为什么一个对象没有被实例化就可以在其他地方被调用,他其实在其他地方被调用了!

问题二

定位问题代码:

订正

问题三

定位问题代码

订正:一个对象怎么怎么能够给坐标赋值呢,应该是外接矩形对象的坐标

问题四

定位问题

订正:blit()函数是将一个图像绘制到另一个图像上方,主要的格式为blit(图像名称,图像位置)Pygame中blit( )方法讲解(Surface对象)_pygame blit_一个兴趣使然的程序猿罢了的博客-CSDN博客

问题五

运行窗口正常,但是当图片被进行向右移动的操作时,就没有办法向左移动了。

定位问题:说明在向右操作的按键对于向左移动的状态有影响,果然,错误代码如下

一旦使用了右键,状态就会一致被重置为false,没有办法进行左键操作

订正

至此,运行成功。

rocket_game.py

import pygame

from rocket_setting import Settings
from roket import Rocket
import rocket_gamefunctions as rs


def run_game():
    """初始化pygame,屏幕,设置"""
    pygame.init()
    rocket_setting = Settings()
    screen = pygame.display.set_mode(
        (rocket_setting.screen_width, rocket_setting.screen_hight))
    pygame.display.set_caption('Rocket_game')

    # 创建一个飞船
    rocket = Rocket(rocket_setting, screen)

    while True:
        rocket.update()
        rs.check_events(rocket)
        rs.update_screen(rocket_setting, screen, rocket)


run_game()

rocket_gamefunction.py

import sys

import pygame


def check_keydown_events(event, rocket):
    """按下按键"""
    if event.key == pygame.K_RIGHT:
        # 火箭向右移动
        rocket.moving_right = True
    elif event.key == pygame.K_LEFT:
        # 火箭向左移动
        rocket.moving_left = True


def check_keyup_events(event, rocket):
    """松开按键"""
    if event.key == pygame.K_RIGHT:
        # 火箭不发生移动
        rocket.moving_right = False
    elif event.key == pygame.K_LEFT:
        rocket.moving_left = False


def check_events(rocket):
    """响应按键事件和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, rocket)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, rocket)


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

rocket_setting.py

​class Settings():
    """初始化屏幕尺寸和火箭速度"""

    def __init__(self):
        """游戏初始设置"""
        # 初始化屏幕
        self.screen_width = 1200
        self.screen_hight = 800
        self.bg_color = (230, 230, 230)

        # 设置飞船速度
        self.rocket_speed_factor = 1.5

​

rocket.py

import pygame


class Rocket():
    """火箭的相关设置"""

    def __init__(self, rocket_setting, screen):
        """初始化火箭初始位置和火箭速度"""
        self.rocket_setting = rocket_setting
        self.screen = screen

        # 加载图像和外接矩形
        self.image = pygame.image.load('images/rocket.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = self.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 and self.rect.right < self.screen_rect.right:
            self.center += self.rocket_setting.rocket_speed_factor
        elif self.moving_left and self.rect.left > 0:
            self.center -= self.rocket_setting.rocket_speed_factor
        # 根据center更新centerx
        self.rect.centerx = self.center

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

显示结果

12-4 按键

创建一个程序,显示一个空屏幕。在事件循环中,每当检测到pygame.KEYDOWN 事件时都打印属性 event.key。运行这个程序,并按各种键, 看看 Pygame如何响应。

eventkey.py

import sys

import pygame


def test_eventkey():
    """按各种键,看看Pygame如何响应"""
    # 初始化pygame
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption('test_key')
    bg_color = (230, 230, 230)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    print(event.key)
                elif event.key == pygame.K_RIGHT:
                    print(event.key)
                elif event.key == pygame.K_UP:
                    print(event.key)
                elif event.key == pygame.K_DOWN:
                    print(event.key)
                
        # 显示屏幕
        screen.fill(bg_color)
        # 让最近的屏幕可见
        pygame.display.flip()


test_eventkey()

12.8 射击

按空格键时发射子弹(小矩形)的代码。子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。

12.8.1 添加子弹设置

更新settings.py,添加子弹的设置:

class Settings():
    """存储《外星人入侵》游戏的所有设置的类"""

    def __init__(self):
        """初始化游戏的设置"""
       ---snip---
        
        # 添加子弹设置
        self.bullet_speed_factor = 1
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = 60, 60, 60
        

12.8.2 创建 Bullet 类

创建存储Bullet类的文件bullet.py。

super().__init__() 就是调用父类的init方法, 同样可以使用super()去调用父类的其他方法。

Bullet类继承了我们从模块pygame.sprite中导入的Sprite类。为创建子弹实例,需要向__init__()传递ai_settings、 screen和ship实例,还调用了super()来继承Sprite。

bullet.py

import pygame
from pygame.sprite import Sprite

class Bullet(Sprite):
    """一个对飞船发射的子弹进行管理的类"""
    
    def __init__(self,ai_setting,screen,ship):
        # 在飞船所处位置创建一个子弹对象
        super(Bullet, self).__init__()
        self.screen = screen
        
        # 在(0 , 0)处创建一个表示子弹的矩形,再设置正确的位置
        self.rect = pygame.Rect(0, 0, ai_setting.bullet_width,ai_setting.bullet.hight)
        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top
        
        # 存储小数表示的子弹位置
        self.y = float(self.rect.y)
        self.color = ai_setting.bullet_color
        self.speed_factor = ai_setting.bullet.speed_factor
        

注意

代码super(Bullet, self).__init__()使用了Python 2.7语法。这种语法也适用于Python 3,
但你也可以将这行代码简写为super().__init__()。
子弹并非基于图像的,因此我们必须使用pygame.Rect()类从空白开始创建一个矩形。

子弹并非基于图像的,因此我们必须使用pygame.Rect()类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的x坐标和y坐标,还有矩形的宽度和高度。
下面是bullet.py的第二部分——方法update()和draw_bullet():

    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)

补充pygame.draw.rect()

 12.8.3 将子弹存储到编组中

在alien_invasion.py中创建一个编组( group),用于存储所有有效的子弹,以便能够管理发射出去的所有子弹。这个编组将是pygame.sprite.Group类的一个实例。


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

12.8.4 开火

在game_functions.py中,我们需要修改check_keydown_events(),以便在玩家按空格键时发射一颗子弹。主要修改的代码如下所示:

如果此时运行alien_invasion.py,将能够左右移动飞船,并发射任意数量的子弹。子弹在屏
幕上向上穿行,抵达屏幕顶部后消失

12.8.5 删除已消失的子弹

子弹抵达屏幕顶端后消失,这仅仅是因为Pygame无法在屏幕外面绘制它们。这些子弹实际上依然存在,它们的y坐标为负数,且越来越小。

检测这样的条件,即表示子弹的rect的bottom属性为零,它表明子弹已穿过屏幕顶端:

    # 开始游戏的主循环
    while True:
        gf.check_events(ai_setting, 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_setting, screen, ship, bullets)

在for循环中,不应从列表或编组中删除条目,因此必须遍历编组的副本。我们使用了方法
copy()来设置for循环,这让我们能够在循环中修改bullets。

为什么python中不建议在for循环中修改列表? - 知乎 (zhihu.com)

运行这个游戏并确认子弹已被删除后,将这条print语句删除。如果你留下这条语句,游戏的速度将大大降低,因为将输出写入到终端而花费的时间比将图形绘制到游戏窗口花费的时间还多。

12.8.6 限制子弹数量

在settings.py中存储所允许的最大子弹数:

        self.bullets_allowed = 3

在game_functions.py的check_keydown_events()中,我们在创建新子弹前检查未消失的子弹数是否小于该设置,只有小于规定的数量才能够创建新的子弹:

12.8.7 创建函数 update_bullets()

编写并检查子弹管理代码后,可将其移到模块game_functions中。game_functions.py更新子弹删除子弹部分如下:

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

在alien_invasion.py调用更新子弹并删除子弹的函数。

update_bullets()的代码是从alien_invasion.py剪切并粘贴而来的,它只需要一个参数,即编组bullets。我们让主循环包含尽可能少的代码,这样只要看函数名就能迅速知道游戏中发生的情况。

12.8.8 创建函数 fire_bullet()

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

12.8 动手试一试

12-5 侧面射击

逻辑梳理

编写一个游戏,将一艘飞船放在屏幕左边,并允许玩家上下移动飞船。在玩家按空格键时,让飞船发射一颗在屏幕中向右穿行的子弹,并在子弹离开屏幕而消失后将其删除。
补充知识点:pygame模块参数汇总(python游戏编程) - 覆手为云p - 博客园 (cnblogs.com)

首先需要旋转图像

其次需要修改初始位置

出现的问题

问题一

定位问题

在rocket.bullets.py导入sprite的时候误输入了_Group

订正

from pygame.sprite import Sprite

问题二:

不绘制子弹

定位问题:刷新屏幕的时候没有添加绘制子弹的函数

订正:rocket_gamefunction.py里面添加绘制子弹函数

def update_screen(rocket_setting, screen, rocket, bullets):
    """更新屏幕上的图像,并切换到新的屏幕"""
    # 每次循环都重绘屏幕
    screen.fill(rocket_setting.bg_color)
    # 绘制火箭
    rocket.blitme()
    # 绘制子弹
    bullets.draw_bullet()
    # 让绘制的屏幕可见
    pygame.display.flip()

问题三

定位问题

订正

需要遍历bullets的列表,还需要是精灵列表,方法bullets.sprites()返回一个列表,其中包含编组bullets中的所有精灵。为在屏幕上绘制发射的所有子弹,我们遍历编组bullets中的精灵,并对每个精灵都调用draw_bullet()

在rocket_gamefunction.py更新

def update_screen(ai_setting, screen, ship, bullets):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环都重绘屏幕
    screen.fill(ai_setting.bg_color)
    # 在飞船和外星人后面重绘所有子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    ship.blitme()
    # 让最近绘制的屏幕可见
    pygame.display.flip()

问题四

定位问题:

在rocket_setting写的是bullet_width,但是在rocket_bullets.py调用的确是width,所以以后这种引用最好还是复制粘贴过来。

订正

在rocket_bullets.py更新引用

        self.rect = pygame.Rect(
            0, 0, rocket_setting.bullet_width, rocket_setting.bullet_height)

问题五

定位问题

screen_rect并不是bullets的属性,它是rocket的属性,所以这里需要修改一下

订正:在rocket_gamefunction.py中修改

def update_bullet(bullets, rocket):
    """更新子弹并删除消失的子弹"""
    # 更新子弹
    bullets.update()
    # 删除消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.left >= rocket.screen_rect.left:
            bullets.remove(bullet)

问题六

火箭应该上下移动而不是左右移动

定位问题:更新火箭位置的时候应该是Y值发生移动,没有修正火箭移动的方式

而且这里按键的交互还是左右键,需要改成上下键

订正

需要修改移动标志,修改火箭移动的范围和操作按键,将按键的响应函数对应的参数也要进行修改,由于这里需要修改的地方比较多,所以不一一展示,按照,最终的代码为准。

最终的代码如下:

rocket_setting.py

class Settings():
    """初始化屏幕尺寸和火箭速度"""

    def __init__(self):
        """游戏初始设置"""
        # 初始化屏幕
        self.screen_width = 1200
        self.screen_hight = 800
        self.bg_color = (230, 230, 230)

        # 设置飞船速度
        self.rocket_speed_factor = 1.5
        
        # 设置子弹基本信息
        self.bullet_color = (60, 60, 60)
        self.bullet_width = 15
        self.bullet_height = 3
        self.bullet_speed_factor = 1
        self.bullets_allowed = 3

rocket_gamefunction.py

import sys

import pygame
from rocket_bullets import Rocket_Bullets


def check_keydown_events(event, rocket_setting, screen, rocket, rocket_bullets):
    """按下按键"""
    if event.key == pygame.K_UP:
        # 火箭向上移动
        rocket.moving_up = True
    elif event.key == pygame.K_DOWN:
        # 火箭向下移动
        rocket.moving_down = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(rocket_setting, screen, rocket, rocket_bullets)


def fire_bullet(rocket_setting, screen, rocket, rocket_bullets):
    """判断子弹数量,如果没有达到限制就绘制一颗子弹"""
    if len(rocket_bullets) < rocket_setting.bullets_allowed:
        # 创建新的子弹
        new_bullet = Rocket_Bullets(rocket_setting, screen, rocket)
        rocket_bullets.add(new_bullet)


def check_keyup_events(event, rocket):
    """松开按键"""
    if event.key == pygame.K_UP:
        # 火箭不发生移动
        rocket.moving_up = False
    elif event.key == pygame.K_DOWN:
        rocket.moving_down = False


def check_events(rocket_setting, screen, rocket, rocket_bullets):
    """响应按键事件和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, rocket_setting, screen, rocket, rocket_bullets)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, rocket)


def update_screen(rocket_setting, screen, rocket, rocket_bullets):
    """更新屏幕上的图像,并切换到新的屏幕"""
    # 每次循环都重绘屏幕
    screen.fill(rocket_setting.bg_color)
    # 绘制火箭
    rocket.blitme()
    # 在绘制火箭和外星人后面绘制子弹
    for bullet in rocket_bullets.sprites():
        bullet.draw_bullet()
    # 让绘制的屏幕可见
    pygame.display.flip()


def update_bullet(rocket_bullets, rocket):
    """更新子弹并删除消失的子弹"""
    # 更新子弹
    rocket_bullets.update()
    # 删除消失的子弹
    for bullet in rocket_bullets.copy():
        if bullet.rect.left >= rocket.screen_rect.right:
            rocket_bullets.remove(bullet)
    print(len(rocket_bullets))

roket.py

import pygame


class Rocket():
    """火箭的相关设置"""

    def __init__(self, rocket_setting, screen):
        """初始化火箭初始位置和火箭速度"""
        self.rocket_setting = rocket_setting
        self.screen = screen

        # 加载图像和外接矩形
        self.image = pygame.image.load('images/rocket.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = self.screen.get_rect()

        # 将每艘火箭放在屏幕左端中央
        self.rect.midleft = self.screen_rect.midleft

        # 在飞船center属性下存储小数
        self.center = float(self.rect.centery)

        # 移动标志
        self.moving_up = False
        self.moving_down = False

    def update(self):
        """根据火箭移动标志移动火箭位置"""
        if self.moving_up and self.rect.top > 0:
            self.center -= self.rocket_setting.rocket_speed_factor
        elif self.moving_down and self.rect.bottom < self.screen_rect.bottom:
            self.center += self.rocket_setting.rocket_speed_factor
        # 根据center更新centerx
        self.rect.centery = self.center

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

rocket_bullets.py

import pygame
from pygame.sprite import Sprite


class Rocket_Bullets(Sprite):
    """一个对子弹可以管理的类"""

    def __init__(self, rocket_setting, screen, rocket):
        super().__init__()
        self.screen = screen
        
        # 在(0,0)处创建一个子弹的矩形,再设置正确位置
        self.rect = pygame.Rect(0, 0, rocket_setting.bullet_width, rocket_setting.bullet_height)
        self.rect.centery = rocket.rect.centery
        self.rect.right = rocket.rect.right
        # 用小数存储子弹位置
        self.x = float(self.rect.x)
        # 子弹颜色
        self.color = rocket_setting.bullet_color
        # 子弹速度
        self.speed_factor = rocket_setting.bullet_speed_factor

    def update(self):
        """更新子弹位置"""
        self.x += self.speed_factor
        self.rect.centerx = self.x
    
    def draw_bullet(self):
        """在屏幕绘制子弹"""
        pygame.draw.rect(self.screen, self.color, self.rect)
        

rocket_game.py

import pygame
from pygame.sprite import Group
from rocket_setting import Settings
from rocket import Rocket
import rocket_gamefunctions as rs


def run_game():
    """初始化pygame,屏幕,设置"""
    pygame.init()
    rocket_setting = Settings() 
    screen = pygame.display.set_mode(
        (rocket_setting.screen_width, rocket_setting.screen_hight))
    pygame.display.set_caption('Rocket_game')

    # 创建一个飞船
    rocket = Rocket(rocket_setting, screen)

    # 创建一个子弹对象组
    rocket_bullets = Group()

    while True:
        rs.check_events(rocket_setting, screen, rocket, rocket_bullets)
        rocket.update()
        rs.update_bullet(rocket_bullets, rocket)
        rs.update_screen(rocket_setting, screen, rocket, rocket_bullets)


run_game()

12.9 小结

这一章学习了项目的规划,把混合在一起的整体代码拆分成几个模块,不同模块之间互不干扰,有利于后期代码的修改。主要实现的功能是绘制窗口并设置窗口背景颜色,加载外部图片,实现飞船的左右连续移动功能,实现子弹的发射、删除和数量限制。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值