Python笔记(三)飞机大战

一、pygame入门

1、准备工作

安装pygame:

$ sudo pip3 install pygame

验证是否安装:

$ python3 -m pygame.examples.aliens

游戏思路:

  • 把一些 静止的图像 绘制到 游戏窗口

  • 根据 用户的交互 或其他情况,移动 这些图像,产生动画效果

  • 根据 图像之间 是否发生重叠,判断 敌机是否被摧毁 等其他情况

 

2、初始化和退出

要使用 pygame 提供的所有功能之前,需要调用 init 方法

在游戏结束前需要调用一下 quit 方法

方法说明
pygame.init()导入并初始化所有 pygame 模块,使用其他模块之前,必须先调用 init 方法
pygame.quit()卸载所有 pygame 模块,在游戏结束之前调用!
import pygame

pygame.init()

print("游戏内容")

pygame.quit()

 

3、游戏中的坐标系

原点 在 左上角 (0, 0)

x 轴 水平方向向 右,逐渐增加

y 轴 垂直方向向 下,逐渐增加

在游戏中,所有可见的元素 都是以矩形区域来描述位置的

 

要描述一个矩形区域有四个要素:(x, y) (width, height)

pygame 专门提供了一个类 pygame.Rect 用于描述 矩形区域

Rect(x, y, width, height) -> Rect

pygame.Rect 是一个比较特殊的类,内部只是封装了一些数字计算,不执行 pygame.init() 方法同样能够直接使用

描述英雄:

import pygame

hero_rect = pygame.Rect(100, 500, 120, 125)

print("英雄的原点:%d %d" % (hero_rect.x, hero_rect.y))
print("英雄的尺寸:%d %d" % (hero_rect.width, hero_rect.height))
# size属性会返回矩形的(宽,高)元组
print("英雄大小:%d %d" % hero_rect.size)

 

4、创建游戏主窗口

pygame提供了pygame.display模块用于创建、管理游戏窗口

方法说明
pygame.display.set_mode()初始化游戏显示窗口
pygame.display.update()刷新屏幕内容显示

set_mode 方法:set_mode(size=(0,0), flags=0, depth=0) -> Surface

  • 作用 —— 创建游戏显示窗口

  • 参数

    • size 指定屏幕的 ,默认创建的窗口大小和屏幕大小一致

    • flags 参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递

    • depth 参数表示颜色的位数,默认自动匹配

  • 返回值

    • surface,暂时 可以理解为 游戏的屏幕,游戏的元素 都需要被绘制到 游戏的屏幕 上

  • 注意:必须使用变量记录 set_mode 方法的返回结果!因为:后续所有的图像绘制都基于这个返回结果

import pygame

pygame.init()

# 创建游戏窗口,并指定屏幕的宽高
screen = pygame.display.set_mode((480, 700))

# 游戏循环,保证窗口不会因为代码继续向下执行而退出
while True:
    pass

pygame.quit()

 

5、图像绘制

保存在磁盘上的图像文件首先应该被加载到内存:

要在屏幕上 看到某一个图像的内容,需要按照三个步骤:

  使用 pygame.image.load(file_path) 加载图像的数据

  使用 游戏屏幕 对象,调用 blit(图像,位置) 方法 将图像绘制到指定位置

  调用 pygame.display.update() 方法更新整个屏幕的显示

 

(1)绘制背景图像

  加载background.png创建背景

  将背景绘制在屏幕的(0,0)位置

  更新屏幕显示

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

# 加载图像数据
bg = pygame.image.load("./images/background.png")

# blit绘制图像
screen.blit(bg, (0, 0))

# 更新屏幕显示
pygame.display.update()

while True:
    pass

pygame.quit()

 

(2)绘制英雄图像

  加载me1.png创建英雄飞机

  将飞机绘制在(200,500)位置

  调用屏幕更新显示飞机图像

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

hero = pygame.image.load("./images/me1.png")

screen.blit(hero, (200, 500))

pygame.display.update()

while True:
    pass

pygame.quit()

png 格式的图像是支持透明的,在绘制图像时,透明区域不会显示任何内容。但是如果下方已经有内容,会透过透明区域显示出来;

 

(3)案例调整

可以在 screen 对象完成 所有 blit 方法之后,统一调用一次 display.update 方法,同样可以在屏幕上 看到最终的绘制结果

使用display.set_mode()创建的screen对象是一个内存中的屏幕数据对象,

screen.blit方法可以在上面绘制很多图像(这些图像有可能会彼此重叠或覆盖),

display.update()会将画布的最终结果绘制在屏幕上,这样可以提高屏幕绘制效率,增加游戏的流畅度;

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (200, 500))

pygame.display.update()

while True:
    pass

pygame.quit()

 

6、游戏循环和游戏时钟

跟电影的原理类似,游戏中的动画效果,本质上是快速的在屏幕上绘制图像,每次绘制的结果被称为 帧Frame;

一般在电脑上每秒绘制60次,就能达到非常连续、高品质的动画效果;

 

(1)游戏循环

游戏循环的开始就意味着游戏的真正开始。

游戏的两个重要组成部分:

  游戏初始化(设置游戏窗口、绘制图像初始位置、设置游戏时钟)

  游戏循环(设置刷新帧率、检测用户交互、更新所有图像位置、更新屏幕显示)

游戏循环的作用:

  保证游戏 不会直接退出

  变化图像位置 —— 动画效果

    每隔 1 / 60 秒 移动一下所有图像的位置

    调用 pygame.display.update() 更新屏幕显示

  检测用户交互 —— 按键、鼠标等...

 

(2)游戏时钟

pygame提供了pygame.time.Clock可以非常方便地设置屏幕绘制速度——刷新帧率

要使用时钟对象需要两步:

  在游戏初始化创建一个时钟对象

  在游戏循环中让时钟对象调用tick(帧率)方法

tick方法会根据上次被调用的时间,自动设置游戏循环中的延时

clock = pygame.time.Clock()

i = 0
while True:
    # 可以指定循环体内部的代码执行的频率(每秒60次)
    clock.tick(60)
    print(i)
    i += 1

 

7、英雄的简单动画实现

需求:

在 游戏初始化 定义一个 pygame.Rect 的变量记录英雄的初始位置

在 游戏循环 中每次让 英雄 的 y - 1 —— 向上移动

y <= 0 将英雄移动到屏幕的底部

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (150, 300))

pygame.display.update()

clock = pygame.time.Clock()

# 定义Rect记录飞机的初始位置
hero_rect = pygame.Rect(150, 300, 102, 126)

while True:
    clock.tick(60)

    # 修改飞机的位置
    hero_rect.y -= 1
    # 当飞机完全飞出屏幕时,重置y值
    if hero_rect.y + hero_rect.height <= 0:
        hero_rect.y = 700

    # 重新绘制背景图像,遮挡住上一次绘制的飞机图案
    screen.blit(bg, (0, 0))
    screen.blit(hero, hero_rect)

    pygame.display.update()

pygame.quit()

每一次调用 update() 方法之前,需要把所有的游戏图像都重新绘制一遍,而且应该最先重新绘制背景图像

 

8、在游戏循环中监听事件

事件Event:就是游戏启动后,用户针对游戏所做的操作,例如点击关闭按钮、点击鼠标、按下键盘

监听:在游戏循环中,判断用户具体的操作,捕获到用户具体的操作,才能有针对性的做出响应

在pygame中通过pygame.event.get()可以获得用户当前所做动作的事件列表(用户可以同一时间做很多事情)

  这段代码非常的固定,几乎所有的pygame游戏都大同小异

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (150, 300))

pygame.display.update()

clock = pygame.time.Clock()

hero_rect = pygame.Rect(150, 300, 102, 126)

while True:
    clock.tick(60)

    # 获取动作事件列表
    event_list = pygame.event.get()
    for event in event_list:
        # 判断事件类型是否是用户点击了关闭按钮
        if event.type == pygame.QUIT:
            print("退出游戏")
            pygame.quit()
            # 直接退出系统
            exit()

    hero_rect.y -= 1
    if hero_rect.y + hero_rect.height <= 0:
        hero_rect.y = 700

    screen.blit(bg, (0, 0))
    screen.blit(hero, hero_rect)

    pygame.display.update()

pygame.quit()

 

9、精灵和精灵组

 在刚刚完成的案例中,图像加载、位置变化、绘制图像都需要程序员编写代码分别处理。

  游戏初始化(设置游戏窗口、绘制图像初始位置、设置游戏时钟)

  游戏循环(设置刷新帧率、检测用户交互、更新所有图像位置、更新屏幕显示)

为了简化开发步骤,pygame提供了两个类:

 

pygame.sprite.Sprite 存储图像image和位置rect的对象

  属性:

  image 要显示的图像

  rect 图像要显示在屏幕的位置

  方法:

  update(*args) 在每次刷新屏幕时,更新精灵位置

  kill() 从所有组中删除

注意:pygame.sprite.Sprite 并没有提供 imagerect 两个属性,需要程序员从 pygame.sprite.Sprite 派生子类,并在 子类 的 初始化方法 中,设置 imagerect 属性

 

pygame.sprite.Group 精灵组,可以包含多个精灵对象

  方法:

  __init__(self, *精灵)

  add(*sprites) 向组中增加精灵

  sprites() 返回所有精灵列表

  update(*args) 让组中所有精灵自动调用update方法

  draw(Surface) 将组中所有精灵的image,绘制到Surface的rect位置

注意:仍然需要调用 pygame.display.update() 才能在屏幕看到最终结果

 

游戏初始化:创建精灵、创建精灵组

游戏循环:精灵组.update()、精灵组.draw()、pygame.display.update()

 

10、派生精灵子类

定义GameSprite继承自pygame.sprite.Sprite

在重写初始化方法时,一定要先 super() 一下父类的 __init__ 方法,保证父类中实现的 __init__ 代码能够被正常执行

GameSprite

  属性:

  image 精灵图像,使用image_name加载

  rect 精灵大小,默认使用图像大小

  speed 精灵移动速度,默认为1

  方法:

  __init__(self, image_name, speed=1)

  update(self) 每次更新屏幕时在游戏内循环调用,让精灵的self.rect.y += self.speed

注意:imageget_rect() 方法,可以返回 pygame.Rect(0, 0, 图像宽, 图像高) 的对象 

import pygame


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""
    
    def __init__(self, image_name, speed=1):
        # 调用父类初始化方法
        super().__init__(self)
        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed
    
    def update(self, *args):
        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed

 

11、使用精灵和精灵组创建敌机

使用 from 导入 plane_sprites 模块,from 导入的模块可以直接使用

在游戏初始化创建精灵对象和精灵组对象

在游戏循环中让精灵组分别调用 update()draw(screen) 方法

from plane_sprites import *
import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (150, 300))

pygame.display.update()

# 创建游戏时钟对象
clock = pygame.time.Clock()
# 记录英雄初始位置
hero_rect = pygame.Rect(150, 300, 102, 126)

# 创建敌机的精灵
enemy1 = GameSprite("./images/enemy1.png")
enemy2 = GameSprite("./images/enemy1.png", 2)
# 创建敌机的精灵组
enemy_group = pygame.sprite.Group(enemy1, enemy2)

while True:
    # 设置刷新频率
    clock.tick(60)

    # 检测用户交互
    event_list = pygame.event.get()
    for event in event_list:
        if event.type == pygame.QUIT:
            print("退出游戏...")
            pygame.quit()
            quit()

    # 修改飞机位置
    hero_rect.y -= 1
    # 判断飞机位置
    if hero_rect.y + hero_rect.height <= 0:
        hero_rect.y = 700

    # 重新绘制图像
    screen.blit(bg, (0, 0))
    screen.blit(hero, hero_rect)

    # 让精灵组调用两个方法
    # update - 让组中的所有精灵更新位置
    enemy_group.update()
    # draw - 在screen上绘制所有的精灵
    enemy_group.draw(screen)

    pygame.display.update()


pygame.quit()

 

二、飞机大战

1、框架搭建

游戏主程序可以分为:

  游戏初始化:

  设置游戏窗口

  创建游戏时钟

  创建精灵、精灵组

  游戏循环:

  设置刷新频率

  事件监听

  碰撞检测

  更新/绘制精灵组

  更新屏幕显示

 

根据明确的职责,设计PlaneGame类

  属性:

  screen

  clock

  精灵组或精灵

  方法:

  __init__(self):

  __create_sprites(self):

  start_game(self):

  __event_handler(self):

  __check_collide(self):

  __update_sprites(self):

  __game_over():

 

  • 游戏初始化 —— __init__() 会调用以下方法:

方法职责
__create_sprites(self)创建所有精灵和精灵组
  • 游戏循环 —— start_game() 会调用以下方法:

方法职责
__event_handler(self)事件监听
__check_collide(self)碰撞检测 —— 子弹销毁敌机、敌机撞毁英雄
__update_sprites(self)精灵组更新和绘制
__game_over()游戏结束

 

plane_main文件:游戏主程序(封装主游戏类、创建游戏对象、启动游戏)

plane_sprites文件:封装所有精灵子类、提供游戏相关工具

 

plane_main.py

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""
    def __init__(self):

        print("游戏初始化...")

        # 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)

        # 创建游戏时钟
        self.clock = pygame.time.Clock()

        # 调用私有方法,创建精灵和精灵组
        self.__create_sprites()

    def __create_sprites(self):

        pass

    def start_game(self):

        print("游戏开始...")

        while True:
            # 1.设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)

            # 2.事件监听
            self.__event_handler()

            # 3.碰撞检测
            self.__check_collide()

            # 4.精灵组更新绘制
            self.__update_sprites()

            # 5.更新显示
            pygame.display.update()

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()

    def __check_collide(self):
        pass

    def __update_sprites(self):
        pass

    @staticmethod
    def __game_over():
        print("游戏结束...")
        pygame.quit()
        exit()


if __name__ == '__main__':

    # 创建游戏对象
    game = PlaneGame()

    # 启动游戏
    game.start_game()

plane_sprites.py

import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):
        # 调用父类初始化方法
        super().__init__()
        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self, *args):
        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed

 

2、背景图像

交替滚动实现思路:

创建两张背景图像精灵,两张图像一起向下方移动

  self.rect.y += self.speed

当任意背景精灵的 rect.y>=屏幕的高度,说明已经移动到屏幕下方,将这张图片设置到屏幕的上方

  rect.y = -rect.height

 

GameSprite的update方法没有针对移出屏幕进行判断,我们需要派生子类,重写方法:

class Background(GameSprite):
    """游戏背景精灵"""
    def update(self):
        # 1.调用父类的方法实现
        super().update()
        # 2.判断是否移出屏幕
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height

 

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background("./images/background.png")
        bg2 = Background("./images/background.png")
        bg2.rect.y = -bg2.rect.height
        self.back_group = pygame.sprite.Group(bg1, bg2)
    def __update_sprites(self):

        self.back_group.update()
        self.back_group.draw(self.screen)

上方代码存在问题:

在主程序中,创建的两个背景精灵,传入了相同的图像文件路径

创建 第二个背景精灵 时,在主程序中设置背景精灵的图像位置,而根据面向对象设计原则,应该由精灵自己负责

利用初始化方法,简化背景精灵的创建:

  • 直接指定 背景图片

  • is_alt 判断是否是另一张图像

    • False 表示 第一张图像,需要与屏幕重合

    • True 表示 另一张图像,在屏幕的正上方

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)
        self.back_group = pygame.sprite.Group(bg1, bg2)
class Background(GameSprite):
    """游戏背景精灵"""
    def __init__(self, is_alt=False):
        # 调用父类方法实现精灵的创建
        super().__init__("./images/background.png")
        # 判断是否是替换图像,如果是,设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):
        # 1.调用父类的方法实现
        super().update()
        # 2.判断是否移出屏幕
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height

 

3、定时器

定时器:

pygame 中可以使用 pygame.time.set_timer() 来添加定时器

set_timer(eventid, milliseconds) -> None

所谓定时器,就是每隔一段时间,去执行一些动作

set_timer 可以创建一个事件,可以在游戏循环的事件监听方法中捕获到该事件

第 1 个参数事件代号需要基于常量 pygame.USEREVENT 来指定,USEREVENT 是一个整数,再增加的事件可以使用 USEREVENT + 1 指定,依次类推...

第 2 个参数是事件触发间隔的毫秒值(1000毫秒=1秒)

 

定时器事件的监听:

通过 pygame.event.get() 可以获取当前时刻所有的 事件列表

遍历列表 并且判断 event.type 是否等于 eventid,如果相等,表示 定时器事件 发生

 

定义并监听创建敌机的定时器事件

pygame 的 定时器 使用套路非常固定:

  定义 定时器常量 —— eventid

# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT

  在 初始化方法 中,调用 set_timer 方法 设置定时器事件

        # 设置定时器事件
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)

  在 游戏循环 中,监听定时器事件

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            # 监听创建敌机的定时器事件
            elif event.type == CREATE_ENEMY_EVENT:
                print("敌机出场...")

 

4、设计敌机类

敌机的特点:

每隔一秒出现一架

向屏幕下方飞行,速度各不相同

敌机出现的水平位置也不相同

敌机从屏幕下方飞出,不会再回到屏幕中

 

初始化方法:

指定敌机图片

随机敌机的初始位置和初始速度

update():

判断是否飞出屏幕,如果是,从精灵组删除

class Enemy(GameSprite):
    """敌机精灵"""
    def __init__(self):
        # 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 指定敌机的初始随机速度
        
        # 指定敌机的初始随机位置

    def update(self):
        # 调用父类方法,保持垂直方向的飞行
        super().update()
        # 判断是否飞出屏幕,需要删除飞出的精灵
        if self.rect.y > SCREEN_RECT.height:
            print("飞出屏幕...")

 

__create_sprites,添加 敌机精灵组。敌机是定时被创建的,因此在初始化方法中,不需要创建敌机

        # 创建敌机精灵组
        self.enemy_group = pygame.sprite.Group()

__event_handler,创建敌机,并且 添加到精灵组。调用 精灵组 的 add 方法可以 向精灵组添加精灵

            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)

__update_sprites,让 敌机精灵组调用 updatedraw 方法

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

 

实现随机敌机位置和速度:

    def __init__(self):
        # 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 指定敌机的初始随机速度
        self.speed = random.randint(1, 3)
        # 指定敌机的初始随机位置
        self.rect.x = random.randint(0, SCREEN_RECT.width-self.rect.width)
        # bottom属性:底边bottom=y+height
        # 可以把bottom设为0,实现敌机从-height位置飞入的效果
        self.rect.bottom = 0

模块导入顺序

1) 官方标准模块导入
2) 第三方模块导入
3)应用程序模块导入

销毁飞出屏幕的敌机:

    def update(self):
        # 调用父类方法,保持垂直方向的飞行
        super().update()
        # 判断是否飞出屏幕,需要删除飞出的精灵
        if self.rect.y > SCREEN_RECT.height:
            self.kill()  # 从所有的精灵组中移除并自动销毁

    # 会在对象被销毁时调用
    def __del__(self):
        print("敌机飞出 %s" % self.rect)

 

5、设计英雄和子弹类

英雄需求:

游戏启动后,英雄出现在屏幕的水平中间位置,距离屏幕底部 120 像素

英雄每隔 0.5 秒发射一次子弹,每次连发三枚子弹

英雄默认不会移动,需要通过 左/右 方向键,控制英雄在水平方向移动

子弹需求:

子弹从英雄的正上方发射沿直线向上方飞行

飞出屏幕后,需要从精灵组中删除

 

Hero

bullets属性:

  记录所有子弹精灵

初始化方法:

  指定英雄图片

  初始速度=0

  定义bullets子弹精灵组保存子弹精灵

重写update:

  英雄需要水平移动

  保证不能移出屏幕

fire方法:

  用于发射子弹

 

Bullet

初始化方法:

  指定子弹图片

  初始速度=-2(子弹向上方飞行)

重写update:

  判断是否飞出屏幕,飞出就从精灵组删除

 

绘制英雄:

class Hero(GameSprite):
    """英雄精灵"""
    def __init__(self):
        # 调用父类方法,设置英雄图像和速度
        super().__init__("./images/me1.png", 0)
        # 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.height - 120
        # 创建英雄的精灵和精灵组
        self.hero = Hero()  # 要在其他方法中使用,所以定义成属性
        self.hero_group = pygame.sprite.Group(self.hero)
        self.hero_group.update()
        self.hero_group.draw(self.screen)

 

6、键盘按键捕获

在Python中针对键盘按键的捕获有两种方式:

1)判断event.type == pygame.KEYDOWN(KEYDOWN事件表示用户按下了某个键)(K_RIGHT表示向右方向键)

elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
    print("向右移动...")

2)首先使用pygame.key.get_pressed()返回所有按键元组,再通过键盘常量,判断元组中某一个键是否被按下(如果被按下,对应数值为1,没有按下就是0)

# 返回所有按键的元组,如果某个键被按下,对应的值会是1
keys_pressed = pygame.key.get_pressed()
# 判断是否按下了方向键
if keys_pressed[pygame.K_RIGHT]:
    print("向右移动...")

 

第一种方式用户必须要抬起按键才算一次按键事件,操作灵活性大打折扣;

第二种方式用户可以按住方向键不放,实现朝某个方向持续移动;

 

7、英雄移动

在Hero类中重写update方法:

  让英雄的rect.x与速度speed叠加

  不需要调用父类方法,父类方法只实现了垂直运动

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

在__event_handler方法中根据左右方向键设置英雄的速度

  向右 2

  向左-2

  没有按键或其他0

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            # 监听创建敌机的定时器事件
            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)
            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            #     print("按下右方向键")

        # 返回所有按键元组
        keys_pressed = pygame.key.get_pressed()
        # 如果被按下,值为1
        if keys_pressed[pygame.K_RIGHT]:
            self.hero.speed = 2
        elif keys_pressed[pygame.K_LEFT]:
            self.hero.speed = -2
        else:
            self.hero.speed = 0

英雄边界控制:

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

        # 控制英雄不能离开屏幕
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

 

8、发射子弹

英雄每隔0.5秒发射一次,每次连发三枚子弹

pygame的定时器使用套路:

  定义定时器常量 eventid

  在初始化方法中,set_timer设置定时器事件

  在游戏循环中,监听定时器事件

    def fire(self):
        print("发射子弹")
# 英雄发射子弹的定时器常量
HERO_FIRE_EVENT = pygame.USEREVENT + 1
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)
            elif event.type == HERO_FIRE_EVENT:
                # 监听发射子弹事件
                self.hero.fire()

Bullet类:

  初始化方法:指定图片和初始速度-2

  update:子弹飞出屏幕就删除

class Bullet(GameSprite):
    """子弹精灵"""
    def __init__(self):
        # 调用父类方法设置子弹图片,设置初始速度
        super().__init__("./images/bullet1.png", -2)
    
    def update(self):
        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()
        # 判断子弹是否飞出
        if self.rect.bottom < 0:
            self.kill()
    
    def __del__(self):
        print("子弹销毁")

 

Hero 的 初始化方法 中创建 子弹精灵组 属性

    def __init__(self):
        # 调用父类方法,设置英雄图像和速度
        super().__init__("./images/me1.png", 0)
        # 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.height - 120
        # 创建子弹的精灵组
        self.bullets = pygame.sprite.Group()

修改 plane_main.py__update_sprites 方法,让 子弹精灵组 调用 updatedraw 方法

        self.hero.bullets.update()
        self.hero.bullets.draw(self.screen)

实现 fire() 方法:

  创建子弹精灵

  设置初始位置 —— 在 英雄的正上方

  子弹 添加到精灵组

    def fire(self):
        # 创建子弹精灵
        bullet = Bullet()
        # 设置精灵位置
        bullet.rect.bottom = self.rect.y - 20
        bullet.rect.centerx = self.rect.centerx
        # 将精灵添加到精灵组
        self.bullets.add(bullet)

一次发射三枚子弹:

    def fire(self):
        for i in (0, 1, 2):
            # 创建子弹精灵
            bullet = Bullet()
            # 设置精灵位置
            bullet.rect.bottom = self.rect.y - i * 20
            bullet.rect.centerx = self.rect.centerx
            # 将精灵添加到精灵组
            self.bullets.add(bullet)

 

9、碰撞检测

1)pygame.sprite.groupcollide():两个精灵组 中 所有的精灵 的碰撞检测

groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict

  • 如果将 dokill 设置为 True,则 发生碰撞的精灵将被自动移除

  • collided 参数是用于 计算碰撞的回调函数

    • 如果没有指定,则每个精灵必须有一个 rect 属性

# 子弹摧毁敌机
pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)

 

2)pygame.sprite.spritecollide():判断 某个精灵 和 指定精灵组 中的精灵的碰撞

spritecollide(sprite, group, dokill, collided = None) -> Sprite_list

  • 如果将 dokill 设置为 True,则 指定精灵组 中 发生碰撞的精灵将被自动移除

  • collided 参数是用于 计算碰撞的回调函数

    • 如果没有指定,则每个精灵必须有一个 rect 属性

  • 返回 精灵组 中跟 精灵 发生碰撞的 所有精灵列表

    def __check_collide(self):
        # 子弹摧毁敌机
        pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
        # 敌机撞毁英雄
        enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)
        # 判断列表是否有内容
        if len(enemies) > 0:
            # 让英雄牺牲
            print("英雄牺牲了...")
            self.hero.kill()
            # 结束游戏
            PlaneGame.__game_over()

 

三、飞机大战代码

plane_main.py

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""
    def __init__(self):

        print("游戏初始化...")

        # 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)

        # 创建游戏时钟
        self.clock = pygame.time.Clock()

        # 调用私有方法,创建精灵和精灵组
        self.__create_sprites()

        # 设置定时器事件
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)
        self.back_group = pygame.sprite.Group(bg1, bg2)

        # 创建敌机精灵组
        self.enemy_group = pygame.sprite.Group()

        # 创建英雄的精灵和精灵组
        self.hero = Hero()  # 要在其他方法中使用,所以定义成属性
        self.hero_group = pygame.sprite.Group(self.hero)

    def start_game(self):

        print("游戏开始...")

        while True:
            # 1.设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)

            # 2.事件监听
            self.__event_handler()

            # 3.碰撞检测
            self.__check_collide()

            # 4.精灵组更新绘制
            self.__update_sprites()

            # 5.更新显示
            pygame.display.update()

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            # 监听创建敌机的定时器事件
            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)
            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            #     print("按下右方向键")
            elif event.type == HERO_FIRE_EVENT:
                # 监听发射子弹事件
                self.hero.fire()

        # 返回所有按键元组
        keys_pressed = pygame.key.get_pressed()
        # 如果被按下,值为1
        if keys_pressed[pygame.K_RIGHT]:
            self.hero.speed = 2
        elif keys_pressed[pygame.K_LEFT]:
            self.hero.speed = -2
        else:
            self.hero.speed = 0

    def __check_collide(self):
        # 子弹摧毁敌机
        pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
        # 敌机撞毁英雄
        enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)
        # 判断列表是否有内容
        if len(enemies) > 0:
            # 让英雄牺牲
            print("英雄牺牲了...")
            self.hero.kill()
            # 结束游戏
            PlaneGame.__game_over()

    def __update_sprites(self):

        self.back_group.update()
        self.back_group.draw(self.screen)

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

        self.hero_group.update()
        self.hero_group.draw(self.screen)

        self.hero.bullets.update()
        self.hero.bullets.draw(self.screen)

    @staticmethod
    def __game_over():
        print("游戏结束...")
        pygame.quit()
        exit()


if __name__ == '__main__':

    # 创建游戏对象
    game = PlaneGame()

    # 启动游戏
    game.start_game()

 

plane_sprites.py

import random
import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 英雄发射子弹的定时器常量
HERO_FIRE_EVENT = pygame.USEREVENT + 1


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):
        # 调用父类初始化方法
        super().__init__()
        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self):
        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed


class Background(GameSprite):
    """游戏背景精灵"""
    def __init__(self, is_alt=False):
        # 调用父类方法实现精灵的创建
        super().__init__("./images/background.png")
        # 判断是否是替换图像,如果是,设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):
        # 1.调用父类的方法实现
        super().update()
        # 2.判断是否移出屏幕
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height


class Enemy(GameSprite):
    """敌机精灵"""
    def __init__(self):
        # 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 指定敌机的初始随机速度
        self.speed = random.randint(1, 3)
        # 指定敌机的初始随机位置
        self.rect.x = random.randint(0, SCREEN_RECT.width-self.rect.width)
        # bottom属性:底边bottom=y+height
        # 可以把bottom设为0,实现敌机从-height位置飞入的效果
        self.rect.bottom = 0

    def update(self):
        # 调用父类方法,保持垂直方向的飞行
        super().update()
        # 判断是否飞出屏幕,需要删除飞出的精灵
        if self.rect.y > SCREEN_RECT.height:
            self.kill()  # 从所有的精灵组中移除并自动销毁

    # 会在对象被销毁时调用
    def __del__(self):
        # print("敌机飞出 %s" % self.rect)
        pass


class Hero(GameSprite):
    """英雄精灵"""
    def __init__(self):
        # 调用父类方法,设置英雄图像和速度
        super().__init__("./images/me1.png", 0)
        # 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.height - 120
        # 创建子弹的精灵组
        self.bullets = pygame.sprite.Group()

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

        # 控制英雄不能离开屏幕
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

    def fire(self):
        for i in (0, 1, 2):
            # 创建子弹精灵
            bullet = Bullet()
            # 设置精灵位置
            bullet.rect.bottom = self.rect.y - i * 20
            bullet.rect.centerx = self.rect.centerx
            # 将精灵添加到精灵组
            self.bullets.add(bullet)


class Bullet(GameSprite):
    """子弹精灵"""
    def __init__(self):
        # 调用父类方法设置子弹图片,设置初始速度
        super().__init__("./images/bullet1.png", -2)

    def update(self):
        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()
        # 判断子弹是否飞出
        if self.rect.bottom < 0:
            self.kill()

    # def __del__(self):
    #     print("子弹销毁")

 

转载于:https://www.cnblogs.com/ysysyzz/p/11087311.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值