项目-飞机大战

项目-飞机大战

使用的模块-pygame

  • pygame 就是一个 Python 模块,专为电子游戏设计
  • 官方网站:https://www.pygame.org/
    • 提示:要学习第三方模块,通常最好的参考资料就在官方网站
网站栏目 内容
GettingStarted 在各平台安装模块的说明
Docs pygame 模块所有 子类 的参考手册

安装 pygame

1
$ sudo pip3 install pygame

验证安装

1
$ python3 -m pygame.examples.aliens

pygame的基本应用

初始化和退出

  • 要使用 pygame 提供的所有功能之前,需要调用 init 方法
  • 在游戏结束前需要调用一下 quit 方法
方法 说明
pygame.init() 导入并初始化所有 pygame 模块,使用其他模块之前,必须先调用 init 方法
pygame.quit() 卸载所有 pygame 模块,在游戏结束之前调用!

坐标系

  • 原点左上角 (0, 0)
  • x 轴 水平方向向 ,逐渐增加
  • y 轴 垂直方向向 ,逐渐增加

002_游戏窗口和坐标系

  • 在游戏中,所有可见的元素 都是以 矩形区域 来描述位置的
    • 要描述一个矩形区域有四个要素:(x, y) (width, height)
  • pygame 专门提供了一个类 pygame.Rect 用于描述 矩形区域
1
Rect(x, y, width, height) -> Rect

pygame.Rect

创建游戏的主窗口

  • pygame 专门提供了一个 模块 pygame.display 用于创建、管理 游戏窗口
方法 说明
pygame.display.set_mode() 初始化游戏显示窗口
pygame.display.update() 刷新屏幕内容显示,稍后使用

set_mode 方法

1
set_mode(resolution=(0,0), flags=0, depth=0) -> Surface
  • 作用 —— 创建游戏显示窗口
  • 参数
    • resolution 指定屏幕的 ,默认创建的窗口大小和屏幕大小一致
    • flags 参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递
    • depth 参数表示颜色的位数,默认自动匹配
  • 返回值
    • 暂时 可以理解为 游戏的屏幕游戏的元素 都需要被绘制到 游戏的屏幕
  • 注意:必须使用变量记录 set_mode 方法的返回结果!因为:后续所有的图像绘制都基于这个返回结果
1
2
# 创建游戏主窗口
screen = pygame.display.set_mode((480, 700))

图像的绘制

  • 在游戏中,能够看到的 游戏元素 大多都是 图像
    • 图像文件 初始是保存在磁盘上的,如果需要使用,第一步 就需要 被加载到内存
  • 要在屏幕上 看到某一个图像的内容,需要按照三个步骤:
    1. 使用 pygame.image.load() 加载图像的数据
    2. 使用 游戏屏幕 对象,调用 blit 方法 将图像绘制到指定位置
    3. 调用 pygame.display.update() 方法更新整个屏幕的显示

加载和显示图像

函数 功能
bg = pygame.image.load() 加载图片
screen.blit(bg,(0,0)) 从(0,0)开始绘制图片
pygame.display.update() 屏幕刷新显示图片

时钟

动画的效果其实就是图像按照一定的帧率移动导致的,所以需要时钟来进行控制

  • pygame专门提供了一个类pygame.time.Clock` 可以非常方便的设置屏幕绘制速度 —— 刷新帧率
  • 要使用 时钟对象 需要两步:
    • 1)在 游戏初始化 创建一个 时钟对象
    • 2)在 游戏循环 中让时钟对象调用 tick(帧率) 方法
  • tick 方法会根据 上次被调用的时间,自动设置 游戏循环 中的延时

事件监听

事件 event

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

监听

  • 游戏循环 中,判断用户 具体的操作

只有 捕获 到用户具体的操作,才能有针对性的做出响应

代码实现

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

定时器

因为敌机的出现是随机的,所以我们需要一个定时器每隔一段时间就自动出现一架飞机

  • pygame 中可以使用 pygame.time.set_timer() 来添加 定时器
  • 所谓 定时器,就是 每隔一段时间,去 执行一些动作
1
set_timer(eventid, milliseconds) -> None
  • set_timer 可以创建一个 事件
  • 可以在 游戏循环事件监听 方法中捕获到该事件
  • 第 1 个参数 事件代号 需要基于常量 pygame.USEREVENT 来指定
    • USEREVENT 是一个整数,再增加的事件可以使用 USEREVENT + 1 指定,依次类推…
  • 第 2 个参数是 事件触发 间隔的 毫秒值

定时器事件的监听

  • 通过 pygame.event.get() 可以获取当前时刻所有的 事件列表
  • 遍历列表 并且判断 event.type 是否等于 eventid,如果相等,表示 定时器事件 发生

精灵和精灵组

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

  • pygame.sprite.Sprite —— 存储 图像数据 image位置 rect对象
  • pygame.sprite.Group

pygame.Sprite.png

精灵

  • 在游戏开发中,通常把 显示图像的对象 叫做精灵 Sprite
  • 精灵 需要 有 两个重要的属性
    • image 要显示的图像
    • rect 图像要显示在屏幕的位置
  • 默认的 update() 方法什么事情也没做
    • 子类可以重写此方法,在每次刷新屏幕时,更新精灵位置
  • 注意pygame.sprite.Sprite 并没有提供 imagerect 两个属性
    • 需要程序员从 pygame.sprite.Sprite 派生子类
    • 并在 子类初始化方法 中,设置 imagerect 属性

精灵组

  • 一个 精灵组 可以包含多个 精灵 对象
  • 调用 精灵组 对象的 update() 方法
    • 可以 自动 调用 组内每一个精灵update() 方法
  • 调用 精灵组 对象的 draw(屏幕对象) 方法
    • 可以将 组内每一个精灵image 绘制在 rect 位置

游戏框架

主游戏类PlaneGame

游戏主程序

  • 游戏初始化 —— __init__() 会调用以下方法:
方法 职责
__create_sprites(self) 创建所有精灵和精灵组
  • 游戏循环 —— start_game() 会调用以下方法:
方法 职责
__event_handler(self) 事件监听
__check_collide(self) 碰撞检测 —— 子弹销毁敌机、敌机撞毁英雄
__update_sprites(self) 精灵组更新和绘制
__game_over() 游戏结束

精灵组的创建

精灵组确定

  • 创建精灵组方法
1
2
3
4
5
6
7
8
9
def __create_sprites(self):
"""创建精灵组"""

# 背景组
self.back_group = pygame.sprite.Group()
# 敌机组
self.enemy_group = pygame.sprite.Group()
# 英雄组
self.hero_group = pygame.sprite.Group()
  • 更新精灵组方法
1
2
3
4
5
6
7
def __update_sprites(self):
"""更新精灵组"""

for group in [self.back_group, self.enemy_group, self.hero_group]:

group.update()
group.draw(self.screen)

背景图片

背景图片交替滚动

  1. 创建两张背景图像精灵
    • 1完全和屏幕重合
    • 2 张在 屏幕的正上方
  2. 两张图像 一起向下方运动
    • self.rect.y += self.speed
  3. 任意背景精灵rect.y >= 屏幕的高度 说明已经 移动到屏幕下方
  4. 移动到屏幕下方的这张图像 设置到 屏幕的正上方
    • rect.y = -rect.height

敌机精灵组

派生Enemy子类

  • 初始化方法
    • 指定 敌机图片
    • 随机 敌机的 初始位置初始速度
  • 重写 update() 方法
    • 判断 是否飞出屏幕,如果是,从 精灵组 删除

需要实现的功能

  • 新建 Enemy 继承自 GameSprite
  • 重写 初始化方法,直接指定 图片名称
  • 随机速度随机位置 的指定由random模块来随机化
  • 重写 update 方法,判断是否飞出屏幕

创建敌机精灵组

  1. __create_sprites,添加 敌机精灵组
    • 敌机是 定时被创建的,因此在初始化方法中,不需要创建敌机
  2. __event_handler,创建敌机,并且 添加到精灵组
    • 调用 精灵组add 方法可以 向精灵组添加精灵
  3. __update_sprites,让 敌机精灵组 调用 updatedraw 方法

pygame.SpriteII

随机

使用 pygame.Rect 提供的 bottom 属性,在指定敌机初始位置时,会比较方便

  • bottom = y + height
  • y = bottom - height

代码实现

  • 修改 初始化方法,随机敌机出现 速度位置
1
2
3
4
5
6
7
8
9
10
11
12
13
def __init__(self):

# 1. 调用父类方法,创建敌机精灵,并且指定敌机的图像
super().__init__("./images/enemy1.png")

# 2. 设置敌机的随机初始速度 1 ~ 3
self.speed = random.randint(1, 3)

# 3. 设置敌机的随机初始位置
self.rect.bottom = 0

max_x = SCREEN_RECT.width - self.rect.width
self.rect.x = random.randint(0, max_x)

英雄战机精灵

需求

  1. 游戏启动后,英雄 出现在屏幕的 水平中间 位置,距离 屏幕底部 120 像素
  2. 英雄 每隔 0.5 秒发射一次子弹,每次 连发三枚子弹
  3. 英雄 默认不会移动,需要通过 左/右 方向键,控制 英雄 在水平方向移动

创建英雄战机

1
2
3
4
5
6
7
8
9
10
class Hero(GameSprite):
"""英雄精灵"""

def __init__(self):

super().__init__("./images/me1.png", 0)

# 设置初始位置
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.bottom - 120

屏幕上绘制英雄

  1. __create_sprites,添加 英雄精灵英雄精灵组
    • 后续要针对 英雄碰撞检测 以及 发射子弹
    • 所以 英雄 需要 单独定义成属性
  2. __update_sprites,让 英雄精灵组 调用 updatedraw 方法
1
2
3
4
5
6
7
8
9
10
11
def __create_sprites():
#...
self.hero = Hero()
self.hero_group = pygame.sprite.Group(self.hero)
#...

def __update_sprites():
#...
self.hero_group.update()
self.hero_group.draw(self.screen)
#...

战机的移动

pygame 中针对 键盘按键的捕获,有 两种 方式

  • 第一种方式 判断 event.type == pygame.KEYDOWN,用户 必须要抬起按键 才算一次 按键事件操作灵活性会大打折扣
  • 第二种方式

    1. 首先使用 pygame.key.get_pressed() 返回 所有按键元组

    2. 通过 键盘常量,判断元组中 某一个键是否被按下 —— 如果被按下,对应数值为 1

      第二种方式 用户可以按住方向键不放,就能够实现持续向某一个方向移动了,操作灵活性更好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def update(self):

# 飞机水平移动
self.rect.x += self.speed

def __event_handler():
# 获取用户按键
keys_pressed = pygame.key.get_pressed()

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

原理就是通过按键的事件检测来赋予hero.speed来控制左移还是右移,通过hero.update方法来进行刷新

子弹精灵组

  1. Hero初始化方法 中创建 子弹精灵组 属性
  2. 修改 plane_main.py__update_sprites 方法,让 子弹精灵组 调用 updatedraw 方法
  3. 实现 fire() 方法
    • 创建子弹精灵
    • 设置初始位置 —— 在 英雄的正上方
    • 子弹 添加到精灵组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def __create_sprites():
# 创建子弹的精灵组
self.bullets = pygame.sprite.Group()

class Hero(GameSprites):
def fire(self):

# 1. 创建子弹精灵
bullet = Bullet()

# 2. 设置精灵的位置
bullet.rect.bottom = self.rect.y - 20
bullet.rect.centerx = self.rect.centerx

# 3. 将精灵添加到精灵组
self.bullets.add(bullet)

子弹

碰撞检测

pygame` 提供了 两个非常方便 的方法可以实现碰撞检测:

pygame.sprite.groupcollide()

  • 两个精灵组所有的精灵 的碰撞检测
1
groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict
  • 如果将 dokill 设置为 True,则 发生碰撞的精灵将被自动移除
  • collided 参数是用于 计算碰撞的回调函数
    • 如果没有指定,则每个精灵必须有一个 rect 属性

pygame.sprite.spritecollide()

  • 判断 某个精灵指定精灵组 中的精灵的碰撞
1
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
  • 如果将 dokill 设置为 True,则 指定精灵组发生碰撞的精灵将被自动移除
  • collided 参数是用于 计算碰撞的回调函数
    • 如果没有指定,则每个精灵必须有一个 rect 属性
  • 返回 精灵组 中跟 精灵 发生碰撞的 精灵列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def __check_collide(self):

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

# 2. 敌机撞毁英雄
enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)

# 判断列表时候有内容
if len(enemies) > 0:

# 让英雄牺牲
self.hero.kill()

# 结束游戏
PlaneGame.__game_over()